유니티/게임 프로젝트

2D RPG - (12 - 2) 카운터 어택 구현하기

monstro 2024. 12. 31. 18:40
728x90
반응형

지난 포스트에 이어 이번에는 플레이어 코드를 구현해보록 하겠습니다.

 

1) PlayerController

public class PlayerController : BaseCharacterController
{
	.
	.
	.

    #region States
	.
	.
	.
    public PlayerStateCounterAttack _counterAttackState { get; private set; }
    #endregion

    // Player Input
	.
	.
	.
    [SerializeField]
    InputAction _counterAttackAction;

	.
	.
	.

    // Attack Info
    [Header("Attack Details")]
	.
	.
	.
    public float _counterAttackDuration;
    public bool _isCounterAttackClicked;
	
	.
	.
	.
    
    // InputSystem 활성화
    private void OnEnable()
    {
		.
		.
		.
        
        _counterAttackAction.performed += DoCounterAttack;
        _counterAttackAction.canceled += DoStopCounterAttack;
        _counterAttackAction.Enable();
    }

    // InputSystem 비활성화
    private void OnDisable()
    {
		.
		.
		.

        _counterAttackAction.performed -= DoCounterAttack;
        _counterAttackAction.canceled -= DoStopCounterAttack;
        _counterAttackAction.Disable();
    }

    // Controller의 Awake에서는 StateMachine과 StateMachine에서 사용할 State를 설정
    protected override void Awake()
    {
		.
		.
		.
        _counterAttackState = new PlayerStateCounterAttack(this, _stateMachine, "CounterAttack");
    }

	.
	.
	.

    void DoCounterAttack(InputAction.CallbackContext value)
    {
        _isCounterAttackClicked = value.ReadValueAsButton();
    }

    void DoStopCounterAttack(InputAction.CallbackContext value)
    {
        _isCounterAttackClicked = value.ReadValueAsButton();
    }

	.
	.
	.
}

 

PlayerController에 카운터 어택을 수행하기 위한 여러 구성들을 추가하였습니다.

 

2) PlayerState

public class PlayerState
{
    .
    .
    .
    
    // Player Input
	.
	.
	.
    protected bool _isCounterAttacking;
	.
	.
	.
    // State에서 매 프레임마다 진행
    public virtual void Update()
    {
		.
		.
		.
        _isCounterAttacking = _controller._isCounterAttackClicked;
    }

	.	
	.
	.
}

 

플레이어의 상태의 조상 클래스인 PlayerState에서

카운터 어택의 입력을 전달하기 위해 위와 같이 구조를 수정하였습니다.

 

3) PlayerStateCounterAttack

public class PlayerStateCounterAttack : PlayerState
{
    public PlayerStateCounterAttack(PlayerController inController, PlayerStateMachine inStateMachine, string inParamName) 
        : base(inController, inStateMachine, inParamName)
    {

    }

    // 카운터 어택이 성공해야만 카운터 어택 성공 애니메이션을 재생하므로 다음과 같이
    // 카운터 어택 상태로 들어가는 경우, 해당 애니메이터 인자를 false로 설정
    public override void Enter()
    {
        base.Enter();

        _stateTimer = _controller._counterAttackDuration;
        _controller._animator.SetBool("SuccesfulCounterAttack", false);
    }

    public override void Exit()
    {
        base.Exit();
    }

    public override void Update()
    {
        base.Update();

        // 카운터 어택동안 이동하지 못하도록 막음
        _controller.SetZeroVelocity();

        // 카운터 어택을 하는 동안 원 형태로 오버랩된 대상들을 저장
        Collider2D[] colliders = Physics2D.OverlapCircleAll(_controller._attackCheck.position, _controller._attackCheckRadius);

        foreach (Collider2D hit in colliders)
        {
            if (hit.GetComponent<EnemyController>() != null)
            {
                // 저장된 대상들이 스턴할 수 있는 상태라면,
                if (hit.GetComponent<EnemyController>().DoDefineCanBeStunned())
                {
                    // _stateTimer를 10초로 설정하여 카운터 어택 상태를 유지하고
                    // 카운터 어택이 성공하였으므로 카운터 어택 성공 애니메이션을 재생
                    _stateTimer = 10.0f;
                    _controller._animator.SetBool("SuccesfulCounterAttack", true);
                }
            }
        }

        // _stateTimer가 0보다 작거나, 카운터 어택 애니메이션의 마지막 프레임에 설정된
        // Animation Event가 호출되었다면, 상태를 다시 Idle로 바꿈
        if (_stateTimer < 0 || _triggerCalled)
            _stateMachine.ChangeState(_controller._idleState);
    }
}

 

플레이어의 카운터 어택을 수행하기 위한 PlayerStateCounterAttack을 추가하였습니다.

로직은 위와 같이 구성되었습니다.

 

4) PlayerStateGrounded

public class PlayerStateGrounded : PlayerState
{
	.
	.
	.

    public override void Update()
    {
		.
		.
		.
        
        if (_isCounterAttacking)
            _stateMachine.ChangeState(_controller._counterAttackState);

		.
		.
		.
    }
}

 

마지막으로 플레이어가 땅을 딛고 서 있는 PlayerStateGrounded의 경우 

PlayerStateCounterAttack으로 전환하기 위한 조건을 위와 같이 설정하였습니다.

 

마지막으로 에디터에서 애니메이터에 설정된 애니메이션의 전환을 확인하도록 하겠습니다.

 

위에서 C.A는 CounterAttack을,

S.C.A는 SuccesfulCounterAttack을 의미합니다.

728x90
반응형