본문 바로가기

개발 프로젝트/Unity - WeaponGameProject

WeaponGameProject - Player 클래스 설계 (3) (bool 타입을 통한 플레이어 상태 구현)

이번에 소개할 Combat Class는 플레이어의 상태 Bool타입으로 관리하는 클래스입니다.

 

그런데 왜 Bool 타입으로 상태 클래스를 설계했는지 질문 하신다면,

정확한 지적입니다. 이 클래스는 잘못 설계되었습니다.

 

지금까지 RPG 게임을 제작해보지 않았던 저에겐

Bool 타입으로 상태를 관리한다는게 어떤 후폭풍을 낳는지 몰랐습니다.

 

먼저 Combat 클래스에 대해 설명하겠습니다.

이 클래스는 플레이어의 전투 상태(Combat)를 관리하는 클래스입니다.

 

키 입력 큐(Key Input Class)에서 분화되는 State 3개

 

 

왼쪽 마우스를 입력하면 기본 공격

오른쪽 마우스를 입력하면 무기 던지기

왼쪽 쉬프트를 입력하면 Bash를 시전합니다.

 

Combat Class는 3가지 행동을 모두 구현하는 것이 아닌

공격과 Bash를 Trigger하는 클래스입니다.

Bash , 기본 공격 , 무기 던지기는 각각의 Behavior를 담당하는 3개의 클래스로 선언됩니다.

3개의 클래스들은 Combat Class를 인스턴스화 하여 상태를 파악하고 행동을 취합니다.

 

정리하면 Key Input Class가 Combat Class에 영향을 주고

Combat Class는 플레이어의 행동의 Trigger를 발동합니다.

public class Combat : MonoBehaviour{
	
    public bool HoldInProgress { get; private set; } = false;
    public bool ThrowInProgress { get; private set; } = false;
    public bool ThrowTrigger { get; private set; } = false;
    public bool BashTrigger { get; private set; } = false;
	private bool lastHoldBool = false;
    private PlayerInput _playerInput;


	private void CheckThrowProgress()
    {
        if (_playerInput.IsSpecialKeyHold)      // 누르고 있는지 검사
        {
            //HoldInProgress = true;
        }
        else
        {
            HoldInProgress = false;
        }


        if (!HoldInProgress)                // 눌렀다가 뗐을 때 던짐
        {
            if (lastHoldBool)
            {
                lastHoldBool = false;
                ThrowTrigger = true;
                ThrowInProgress = true;
                AnimThrow();
            }
        }
    }

    
    private void CheckThrowEnd()
    {
        if (ThrowInProgress)
        {
            if (BashTrigger)
            {
                ThrowInProgress = false;                    //던지기 작업중 배쉬 발동시 던지기 작업 끝내기
            }
            if (_animator.GetCurrentAnimatorStateInfo(0).IsName("RFA_Movement"))
            {
                ThrowInProgress = false;
            }
        }

    }
}

 

위 코드는 ComBat Class에서  무기 던지기 상태를 관리하는 함수입니다.

인스턴스로 생성된 PlayerInput의 Bool을 파악하여

Combat 클래스 내부에 있는 Private bool을 Set합니다.

 

그리고 무기 던지기 운동을 관리하는 클래스(아래 Manage Weapon Class)는

Combat 클래스에 선언 되어있는 Bool을 Get하여 동작합니다.

 

public class ManageWeapon : MonoBehavior{
   [SerializeField] private Transform _firePos;
    private GameObject copiedWeapon;
    private Combat _combat;
    private PlayerInput _playerInput;
    
    private void Start()
    {
        _combat = GetComponent<Combat>();
        _playerInput = GetComponent<PlayerInput>();
    }
    
	private void LateUpdate()
    {
        if (_combat.ThrowTrigger)
        {
            OnThrow();
            _combat.ChangeBoolThrowTrigger();
        }

    }

    private void OnThrow()
    {
        CreateWeapon();
        _isHaveWeapon = !_isHaveWeapon;
    }
    
     private void CreateWeapon()
    {
        copiedWeapon = Instantiate(_weapon, _firePos.position, _firePos.rotation);
        copiedWeapon.transform.rotation = _firePos.rotation;
        copiedWeapon.GetComponent<Weapon>().ChangeBoolThrown();
    }
}

Manage Weapon 클래스는 무기를 관리하는 클래스로

무기 던지기 행동을 담당하는 클래스입니다.

이 클래스는 Combat Class의 ThrowTrigger를 Get하여 무기를 생성합니다.

 

즉 무기를 던지는 행위는 PlayerInput 클래스로부터 키 입력 Bool을 Get하고

Combat 클래스의 Bool을 Get하여 발동할 수 있습니다.

 

이처럼 클래스를 3단계로 나눈 이유는

각각의 상태를 관리하는 클래스를 통해

클래스 사이의 참조가 직관적이며 행동의 과정을 독립적으로 선언할 수 있기 때문입니다.

또한 Unity의 플로우 차트에 존재하는

Update의 선후 관계를 직관적으로 나타낼 수 있습니다.

 

 

 public class Combat : MonoBehaviour{
 
 	private PlayerInput _playerInput;
    private ManageWeapon _manageWeapon;
    public Animator _animator;
    private bool lastHoldBool = false;
    private bool lastBashBool = false;
    public bool AttackInProgress { get; private set; } = false;
    public bool HoldInProgress { get; private set; } = false;
    public bool ThrowInProgress { get; private set; } = false;
    public bool ThrowTrigger { get; private set; } = false;

    public bool BashHold { get; private set; } = false;

    public bool BashInProgress { get; private set; } = false;
    public bool BashTrigger { get; private set; } = false;
    
    private void Start()
    {
        _playerInput = GetComponent<PlayerInput>();
        _manageWeapon = GetComponent<ManageWeapon>();   
        HoldInProgress = false;
    }
 
 	private void Update()
    {

        if (_playerInput.AttackInput && !AttackInProgress)
        {
            AnimAttack();
        }
        else if (_playerInput.SpecialAttackInput && _manageWeapon.IsHaveWeapon && !HoldInProgress && !ThrowInProgress && !BashHold)        // 처음 던지기 눌렀을 때
        {
            HoldInProgress = true;
            lastHoldBool = true;
            AnimHoldStart();
        }
        else if (_playerInput.BashInput && !HoldInProgress && !BashHold)
        {
            BashHold = true;
            lastBashBool = true;
        }

        CheckBashHold();

        CheckThrowProgress();

        CheckThrowEnd();                    // 던지기 동작 끝나는지 체크


    }
 }

위는 Combat Class 코드의 Update 구문 일부입니다.

길게 나열되어 있는 PlayerInput Class의 Bool 변수를 보니 현기증이 나는군요.

저는 Bool 타입으로 "HoldInProgress" , "ThrowInProgress"와 같이

진행상태를 기록하는 Bool을 사용하여 현재의 상태를 나누었습니다.

 

 하지만 다들 예상하듯이 Bool 타입으로 상태를 관리하는 방법

새로운 상태를 추가하는 작업이 굉장히 어려워지며

상태 전환의 기준점이 되는 지점을 관리하기가 어려워 추천되지 않는 방식입니다.

 

 플레이어의 공격 상태 , 무기 던지기 상태까지 2개의 상태를 만드는 것은 수월했으나

3번째 상태 즉 Bash 상태를 추가하는 일은 여간 힘든 일이 아니었습니다.

그렇기에 Combat Class는 제가 감추고 싶은 코드이자, 발전이 필요한 미성숙한 클래스입니다.

 

 만약 이런 Bool 타입 State 클래스에 새로운 상태를 추가해야한다면?

끔찍하겠죠 다른 방법을 찾아야합니다.

그래서 저는 이 클래스에 추가적인 기능을 작성하는 것을 그만두었습니다.

 

지금은 플레이어의 상태를 나누는 공격 , 무기 던지기 , Bash 상태까지 구현되어있으므로,

더 추가할 상태는 없긴하지만 새롭게 작성될 클래스는 이러한 형태여선 절대 안됩니다.

이러한 생각을 통해 Class에 대한 공부를 다시 하고 State Machine에 대한 연구로 이어지지만

이는 나중의 얘기입니다.