ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 캐릭터 기본 클래스 구현
    게임 클라이언트 개발/MakeDNF 2024. 3. 4. 01:02

    이번 글에선 캐릭터의 공격, 이동, 점프 등 각 기능을 어떻게 설계하고 구현했는지에 대해 작성하고자 한다.

    전체적인 Character 설계도

     

    캐릭터를 설계하기 위해 유한 상태 머신을 참고했다. 캐릭터를 컨트롤하기 위한 유한 상태 머신 클래스로 Character를, 캐릭터의 각 행동(유한 상태 머신에선 상태라고 표현함)을 표현하기 위한 베이스 클래스로 CharacterBehaviour를 선언했다.

     

    캐릭터의 행동은 크게 두 가지로 분류하여 구현하였다. On/Off 형식으로 키고 끌 수 있는 행동으론 캐릭터가 이동하는 행동 MoveBehaviour, 점프하는 행동 JumpBehaviour가 있으며, 유한 상태 머신 사이클로 존재하는 행동으론 아무것도 안하는 행동 IdleBehaviour, 캐릭터가 공격하는 행동 AttackBehaviour, 캐릭터가 피격됬을 때 행동 HitBehaviour를 추가했다.

     

    유한 상태 머신에서 On/Off?

    유한 상태 머신은 캐릭터가 이동할 땐 이동 상태, 공격할 땐 공격 상태와 같이 현재 상태라고 불리는 하나의 상태만을 가질 수 있다. 하지만 캐릭터가 점프하면서 이동할 경우라면? 캐릭터 이동 로직을 이동 상태와 점프 상태에 중복해서 작성하거나 이동 점프 상태라는 새로운 상태를 추가해야한다.

    // 캐릭터가 이동 중을 표현하는 상태 클래스
    public class MoveState : CharacterState
    {
        public void OnFixedUpdate()
        {
            Move();
        }
        
        // 캐릭터를 이동하는 함수
        public void Move(Vector3 moveDir);
    }
    
    // 캐릭터가 점프할 경우를 표현하는 상태 클래스
    public class JumpState : CharacterState
    {
        public void OnFixedUpdate()
        {
            Move();
        }
        
        // Warning : MoveBehaviour에서 작성한 코드를 중복해서 또 작성해야 함.
        public void Move(Vector3 moveDir);
    }

     

    또한 캐릭터가 스킬 사용 중에 이동을 할 수도 있고, 점프를 할 수도 있을 것이다. 이런 예외 케이스가 다수 존재하며, 예외 케이스마다 상태를 추가하기엔 중복되는 코드의 양이 많아진다.

     

    이를 해결하기 위해 MoveBehaviour와 JumpBehaviour는 Bool 변수를 통한 On/Off로 구현하여 싸이클 내 각 행동에서 이동과 점프를 제어할 수 있도록 하였다.

    // Character.cs
    // 캐릭터가 움직일 수 있는지를 가리키는 플래그 변수
    public virtual bool CanMove
    {
        set { moveBehaviour.CanMove = value; }
        get => moveBehaviour.CanMove;
    }
    
    // 플레이어가 조이스틱을 움직였을때 호출되는 이벤트 함수
    public void OnJoystickMoved(Vector3 direction)
    {
        if (!CanMove) return;
    
        moveBehaviour.Move(direction);
    }
    
    // SkillState.cs
    // 플레이어가 스킬을 사용할 때 처음 호출되는 이벤트 함수
    public override void OnStart()
    {
        character.CanMove = false;
        character.CanJump = false;
        
        // .. 스킬 사용을 위한 변수 초기화 및 애니메이터 파라미터 설정 로직 작성
    }
    
    // 플레이어가 스킬 사용을 종료할 때 호출되는 이벤트 함수
    public override void OnComplete()
    {
        character.CanMove = true;
        character.CanJump = true;
        
        // .. 변수 초기화나 애니메이터 파라미터 초기화 로직 작성
    }

     

    CharacterBehaviour

    캐릭터의 각 행동을 표현하기 위한 베이스 클래스로 이를 상속받아 다양한 행동을 정의한다. 맴버 변수로는 컨트롤러인 Character 클래스를 갖고 있으며, 프로퍼티로 행동 클래스 타입의 해쉬 코드를 갖고 있다. 

    // CharacterBehaviour.cs
    // 캐릭터 행동을 정의하기 위한 베이스 클래스
    public abstract class CharacterBehaviour : MonoBehaviour
    {
        protected Character character = null;
    
        // 캐릭터 행동을 구분하기 위한 해쉬 코드 프로퍼티
        public abstract int BehaviourCode { get; }
    }
    
    // IdleBehaviour.cs
    // 캐릭터가 아무 것도 하지 않는 상태를 표현하는 클래스
    public class IdleBehaviour : CharacterBehaviour
    {
        public override int BehaviourCode => typeof(IdleBehaviour).GetHashCode();
    }

     

    이는 캐릭터가 싸이클 중 현재 어떤 행동 상태인지 확인해야하는 상황이 자주 발생하는데, 파라미터로 클래스를 비교하는 것보다 int 형 타입의 변수를 비교하는 것이 효율적이라고 생각했기 때문이다. 참조 타입인 클래스로 비교할 경우 잘못된 참조를 비교할 수도 있고 Fake Null 문제가 발생할 수도 있다.

    // Dodge.cs
    // 캐릭터가 앞으로 굴러 회피하는 스킬
    // 캐릭터가 아무것도 안하는 상태이거나 피격 중에 사용할 수 있음
    // 또는 공격 중이라면, 현재 사용하고 있는 스킬이 캔슬 가능한 경우에 사용할 수 있음
    public override bool CheckCanUseSkill(Skill activeSkill)
    {
        return character.CurBehaviourCode != BehaviourCodeList.ATTACK_BEHAVIOUR_CODE
          || !CancelList.Contains(activeSkill.SkillCode);
    }

     

    각 행동들은 맴버 함수로 여러 이벤트 함수를 갖고 있다. 이는 유한 상태 머신에서 착안한 것으로 싸이클에 존재하는 행동에만 해당한다. 행동에 진입했을때, 업데이트 주기마다, 행동이 완료됬을때, 혹은 행동을 캔슬하고 다른 행동으로 오버라이딩했을때를 구현한다.

    // CharacterBehaviour.cs
    // 행동이 시작할 때 1회 호출되는 이벤트 함수
    public virtual void OnStart() { }
    
    // 매 프레임이 업데이트될 때 호출되는 이벤트 함수
    // Unity 사이클에선 Update 문에서 호출됨
    public virtual void OnUpdate() { }
    
    // 매 Fixed Update마다 호출되는 이벤트 함수
    // Unity 사이클에선 FixedUpdate 문에서 호출됨
    public virtual void OnFixedUpdate() { }
    
    // 모든 Update가 끝난 이후 호출되는 이벤트 함수
    // Unity 사이클에선 LateUpdate 문에서 호출됨
    public virtual void OnLateUpdate() { }
    
    // 행동이 완료됬을때 호출되는 이벤트 함수
    public virtual void OnComplete() { }
    
    // 행동이 다른 행동에 의해 캔슬됬을때(오버라이딩됬을때) 호출되는 함수
    public virtual void OnCancel() { }

     

    IdleBehaviour

    IdleBehaviour는 말 그대로 캐릭터가 아무것도 안하는 행동을 표현하는 클래스이다. IdleBehaviour에서 캐릭터가 공격할 경우 AttackBehaviour로, 캐릭터가 피격됬을 경우 HitBehaviour로 현재 행동이 바뀐다. 현재는 추상 클래스를 구현하는 코드 외에는 아무 것도 작성되있지 않지만, 추후 캐릭터의 전직이나 직업에 따라 애니메이션을 설정하는 코드가 작성될 예정이다.

     

    MoveBehaviour

    MoveBehaviour는 캐릭터가 이동하는 행동을 표현하는 클래스이다. 플레이어가 조이스틱으로 입력한 DNF 좌표계로 변환된 방향을 기반으로 캐릭터를 이동시키며, 플레이어의 이동 속도를 위한 변수와 캐릭터가 이동할 수 있는지, 뒤를 돌아볼 수 있는지에 대한 플래그 변수를 갖고 있다.

     

    JumpBehaviour

    JumpBehaviour는 캐릭터가 점프하는 행동을 표현하는 클래스이다. 던전 앤 파이터에선 캐릭터가 점프하는 순간과 땅에 착지하는 순간 잠깐의 딜레이가 존재한다. 이를 위해 JumpBehaviour를 다시 PreDelay - JumpUp - JumpDown - PostDelay로 나눴다.

    캐릭터가 점프 시와 착지 시 잠깐의 딜레이가 존재한다.

     

    애니메이터도 각 Phase에 맞게 나눠서 설정했고, 캐릭터의 DNFRigidbody 컴포넌트의 Velocity.y 값으로 캐릭터가 상승 중인지 하강 중인지를 확인했다.

    캐릭터 점프 애니메이션 플로우

     

    딜레이 동안에도 캐릭터는 점프 행동 상태이기 때문에 실제 캐릭터가 공중에 있는지를 체크하기 위해 플래그 변수와 캐릭터가 땅에 닿아있는지를 더블 체크하였다. 

    // JumpBehaviour.cs
    // 캐릭터가 점프 중인지 반환하는 플래그 변수
    public bool IsJump => phase != EPhase.NONE;
    
    // BaseAttack.cs
    // 캐릭터가 기본 공격을 할 수 있는지 체크하는 함수
    public override bool CheckCanUseSkill(Skill activeSkill)
    {
        //.. Another condition for using the base attack
        
        // 캐릭터가 점프 중이 아니거나 점프 중이라면 공중에 있는지 체크
        if (character.IsJump && character.DNFRigidbody.IsGround) return false;
    
        return true;
    }

     

    AttackBehaviour

    AttackBehaviour는 캐릭터가 공격하는 행동을 표현하는 클래스이다. 플레이어가 스킬 키를 입력했을 경우 해당 키에 등록된 스킬을 사용하며 스킬 사용이 완료되면 IdleBehaviour로 현재 상태가 바뀐다. 각 이벤트 함수에 맞게 스킬 또한 상태 패턴을 적용하였고 거기에 맞게 애니메이션을 설정하였다. 

    캐릭터 공격 애니메이션 플로우

     

    HitBehaviour

    HitBehaviour는 캐릭터가 피격됬을 경우 행동을 표현하는 클래스이다. 피격 시 넉백 효과와 경직 효과를 설정할 수 있고, 경직 효과 중엔 사용할 수 있는 스킬을 제외하고 캐릭터를 조작할 수 없다. 경직 효과가 끝나면 IdleBehaviour로 현재 상태가 바뀐다. 

    캐릭터 피격 애니메이션 플로우

     

Designed by Tistory.