오늘은 Weapon Class에 상태를 추가하겠습니다.
게임의 핵심이 되는 기술인 Bash와 Weapon이 상호작용하여
Weapon의 상태를 변화하는 것입니다.
Bash 기술은 Player 기준으로 일정 범위 안의 Weapon을 스캔하여
Weapon의 Position으로 도약하는 기술입니다.
Bash 기술은 다음을 요구합니다.
1. Player에게 부착되어 Weapon을 감지하는 Collider가 필요함.
2. 1의 Colldier의 부피가 Bash Key Input이 홀드된 상태면 부피가 증가해야함
3. Weapon이 1의 Colldier와 상호작용하여 상태 변환이 일어나야함
먼저 1번을 수행할 WeaponDetectCollider 클래스를 설계합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponDetectCollider : MonoBehaviour
{
[SerializeField] private Transform _springArmTr;
private SphereCollider _sphereCollider;
private GameObject _colliderMesh;
private float _colliderMaxRadius = 20.0f;
private float _colliderDefaultRadius = 20.0f;
private bool _isActive = false;
public bool IsActive { get => _isActive; }
public float ColliderMaxRadius { get => _colliderMaxRadius; }
public float ColliderDefaultRadius { get => _colliderDefaultRadius; }
private int layerMaskWeapon = 0;
private void Start()
{
layerMaskWeapon = LayerMask.GetMask("Weapon");
_sphereCollider = GetComponent<SphereCollider>();
_colliderMesh = transform.GetChild(0).gameObject;
}
private void ChangeActive(bool bActive)
{
gameObject.SetActive(bActive);
}
public void SetOnDetectCollider()
{
_isActive = true;
ChangeActive(_isActive);
}
public void SetOffDetectCollider()
{
_sphereCollider.radius = 1.0f;
_isActive = false;
ChangeActive(_isActive);
}
public void ExpandRadius()
{
if (_isActive)
{
_sphereCollider.radius += 0.1f;
_colliderMesh.transform.localScale = Vector3.one * (_sphereCollider.radius * 2);
}
}
}
WeaponDetectCollider는 플레이어가 KeyInput(LShift 버튼)을 통해 조절하는 클래스이므로
PlayerInput Class와 Combat Class에 구문을 추가하여 상태를 관리합니다.
using UnityEngine;
public class PlayerInput : MonoBehaviour
{
.
.
.
public bool IsBashKeyHold { get => CheckBashKeyHold(); }
public bool BashInput { get => _bashInput;}
private void Update()
{
.
.
_bashInput = Input.GetKeyDown(KeyCode.LeftShift);
.
.
}
private bool CheckBashKeyHold()
{
if (Input.GetKeyDown(KeyCode.LeftShift))
{
isBashPressed = true;
}
if (Input.GetKeyUp(KeyCode.LeftShift))
{
isBashPressed = false;
}
return isBashPressed;
}
}
using UnityEngine;
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(ManageWeapon))]
public class Combat : MonoBehaviour
{
.
.
private bool lastBashBool = false;
public bool BashHold { get; private set; } = false;
.
.
private void CheckBashHold()
{
if (!_playerInput.IsBashKeyHold) // 누르고 있는지 검사
{
BashHold = false;
}
if (!BashHold) // 눌렀다가 뗐을 때 발동
{
if (lastBashBool)
{
lastBashBool = false;
ChangeBoolBashTrigger();
}
}
}
}
PlayerInput 클래스에서 Lshift Key를 홀드하는지 get하고
Combat 클래스의 BashHold bool을 변화 시킵니다.
그리고 Combat 클래스에 존재하는 BashHold bool을 참조하여 실제 세계에서의 움직임을 구현합니다.
앞선 게시물에서 설명했듯이 플레이어의 움직임은 반드시 PlayerInput과 Combat의 참조로 관리합니다.
Combat의 BashHold를 참조하여
WeaponDetectCollider의 크기를 조절하는 BashSkill Class를 작성합니다
using UnityEngine;
[RequireComponent(typeof(Combat))]
public class BashSkill : MonoBehaviour
{
[SerializeField] private GameObject WeaponDetectCollider;
private Combat _combat;
private WeaponDetectCollider _WeaponDetectCollider;
private bool isBash = false;
private void Start()
{
_combat = GetComponent<Combat>();
_WeaponDetectCollider = WeaponDetectCollider.GetComponent<WeaponDetectCollider>();
}
private void FixedUpdate()
{
if (_combat.BashHold)
{
if (!_WeaponDetectCollider.IsActive)
{
SetOnBashCollider();
}
if (WeaponDetectCollider.GetComponent<SphereCollider>().radius < _WeaponDetectCollider.ColliderMaxRadius)
{
ExpandBashCollider();
}
}
if (_combat.BashTrigger)
{
SetOffBashTrigger();
SetOffBashCollider();
}
}
private void SetOnBashCollider()
{
_WeaponDetectCollider.SetOnDetectCollider();
_WeaponDetectCollider.PlaySound();
}
private void SetOffBashCollider()
{
_WeaponDetectCollider.SetOffDetectCollider();
}
private void ExpandBashCollider()
{
if (_WeaponDetectCollider.IsActive)
{
_WeaponDetectCollider.ExpandRadius();
}
}
private void SetOffBashTrigger()
{
_combat.ChangeBoolBashTrigger();
}
}
}
BashSkill Class는 WeaponDetectCollider Class에 존재하는 크기를 바꾸는 함수를 실행시킵니다.
LShift 키를 입력했을 때 WeaponDetectCollider가 활성화되고 준비를 합니다.
LShift 키를 홀드하면 WeaponDetectCollider는 BashSkill이 관리하는 maxRadius까지 크기가 증가합니다.
WeaponDetectColldier의 설정과 행동 루틴이 구현되었으니
이제 Weapon이 WeaponDetectCollider를 감지했을 때 상태를 변환시키면 되겠지요.
Weapon 클래스의 자식 오브젝트로
Collider 상호작용을 위한 WeaponCollider를 설계합니다.
(클래스 이름이 헷갈릴 수도 있겠네요. WeaponDetectCollider와 별개입니다.)
단 WeaponColldier가 WeaponDetectCollider과 매 프레임마다 상호작용을 하기에
가능하다면 최적화를 할 필요가 있다고 판단했습니다.
왜냐하면 무기의 상태를 매 번 바꿀 필요까진 없다고 생각했기 때문입니다.
이를 위해 TriggerStay를 활용하여 상호작용 횟수를 줄이는 방식을 사용했습니다.
public class WeaponCollider : MonoBehaviour {
private bool _bCanBash = false;
private bool _bBashStay = false;
public bool IsCanBash { get => _bCanBash; }
private void FixedUpdate()
{
CheckBashColliderDetect(); // Update에서 사용시 버그 발생 (Trigger Stay 작동 원리 참고)
}
private void CheckBashColliderDetect()
{
if (_bBashStay)
{
_bCanBash = true;
_bBashStay = false;
}
else
{
_bCanBash = false;
}
}
private void OnTriggerStay(Collider other)
{
if (other.tag == "BashCollider")
{
_bBashStay = true; //bBashStay(OnTriggerStay) 불린 SideEffect 주의
}
}
}
https://forum.unity.com/threads/how-often-is-ontriggerstay-called.89619/
How often is OnTriggerStay called?
I have created a script in which you create a zone and any objects inside that zone gets a force applied to them. Works like a wind tunnel you could...
forum.unity.com
유니티에서 제공하는 OnTriggerStay는 Unity의 Update에서 작동하는 함수가 아닙니다.
이는 Unity에 내장되어 있는 개별적인 타이머가 Tick함에 따라 동작하는 함수입니다.
이 Tick의 시간 간격은 Update의 프레임 처리속도보다 느리고 FixedUpdate보단 빠릅니다.
그러므로 OnTriggerStay는 가급적 프레임 사이의 시간 간격이 넓은 FixedUpdate에서 사용되는 것이 권장됩니다.
WeaponCollider Class의 원리는 간단합니다.
WeaponCollider가 WeaponDetectColldier와 접촉했을 때
클래스 내부에 _bBashStay를 변환시키고 이는 _bCanBash를 변환시킵니다.
Weapon Class는 단순하게 WeaponColldier 내부에 존재하는 _bCanBash를 Get하여 상태를 변환합니다.
이제 Weapon Class에 WeaponCollider의 Bool을 Get하는 구문을 추가합니다.
public class Weapon : MonoBehaviour {
private bool _isCanBash = false;
public bool isCanBash { get => _isCanBash; }
private void FixedUpdate()
{
CheckWeaponBashStatus();
ChangeWeaponColor();
}
private void CheckWeaponBashStatus()
{
if (_isCanBash != _weaponCollider.IsCanBash)
{
if (_weaponCollider.IsCanBash)
{
SetOnBashBool();
}
else
{
SetOffBashBool();
}
}
}
private void SetOnBashBool()
{
_isCanBash = true;
}
private void SetOffBashBool()
{
_isCanBash= false;
}
private void ChangeWeaponColor()
{
if (_isTriggerBash)
{
weaponColor.material = _weaponMat[3];
}
else if (_isCanBash)
weaponColor.material = _weaponMat[1];
else
weaponColor.material = _weaponMat[0];
}
}
Weapon은 WeaponCollider의 Bool을 Get하여
Weapon의 상태를 변화시킵니다.
최종적으로 Player가 발동한 WeaponDetectCollider에 접촉한 무기들은
_ isCanBash가 활성화 되어서, Bash가 가능한 상태를 유지할 수 있게 됩니다.
이러한 코드 형태의 장점은 Combat Class에서 부터 상태가 나뉘어져 있어서,
이 상태를 기반으로 움직임을 구현하는 방법이 간단해집니다.
앗 그나저나 또 Boolean으로 상태 변환, 상태 유지를 담당하는게 아니냐구요?
맞습니다. 또 그 딜레마입니다. 클래스 구조를 보면 솔직히 어지럽군요. 제가 이런 코드를 작성했다니..
얼른 7월 8월에 작성한 코드 리뷰를 마무리하고 그나마 세련된 코드를 리뷰하고 싶습니다.. ㅠㅠ
'개발 프로젝트 > Unity - WeaponGameProject' 카테고리의 다른 글
WeaponGameProject - Weapon 클래스 설계 (3) (Bash와 상호작용하는 Weapon의 상태 구현 - 2) (0) | 2023.11.29 |
---|---|
WeaponGameProject - Weapon 클래스 설계 (1) (SphereCast를 활용하는 감지 클래스) (1) | 2023.11.27 |
WeaponGameProject - Player 클래스 설계 (3) (bool 타입을 통한 플레이어 상태 구현) (1) | 2023.11.26 |
WeaponGameProject - Player 클래스 설계 (2) (Spring Arm Camera 구현) (1) | 2023.11.23 |
WeaponGameProject - Player 클래스 설계 (1) (Key Input을 클래스로 만들자) (1) | 2023.11.23 |