주녘공부일지

[더 챌린저스] 캐릭터 본문

GameDevelopment/[Unity] Project

[더 챌린저스] 캐릭터

주녘 2024. 9. 9. 14:07

1. 캐릭터를 제어하는 싱글톤 클래스

- 내 캐릭터와 상대 캐릭터가 존재하기 때문에 제어권을 가진 '나의 캐릭터'만 제어하는 기능을 함

- 캐릭터를 생성하고 생성 시 Action 대리자 메서드를 등록해 이벤트 발생을 감지

    public void PlayerCommand(ENUM_PLAYER_STATE nextState, CharacterParam param = null)
    {
        if (activeCharacter == null || !activeCharacter.isControl)
            return;

        switch (nextState)
        {
            case ENUM_PLAYER_STATE.Idle:
                activeCharacter.Idle();
                break;
            case ENUM_PLAYER_STATE.Move:
                activeCharacter.Move(param);
                break;
            case ENUM_PLAYER_STATE.Dash:
                activeCharacter.Dash();
                break;
            case ENUM_PLAYER_STATE.Jump:
                activeCharacter.Jump();
                break;
            case ENUM_PLAYER_STATE.Attack:
                activeCharacter.Attack(param);
                break;
            case ENUM_PLAYER_STATE.Skill:
                activeCharacter.Skill(param);
                break;
            case ENUM_PLAYER_STATE.Hit:
                Debug.LogError("PlayerCommand() : Hit 명령이 들어옴");
                break;
            case ENUM_PLAYER_STATE.Die:
                activeCharacter.Die();
                break;
        }
    }

2. 캐릭터 파라미터 클래스

- 캐릭터의 상태를 변경하는 명령에 필요한 데이터를 담은 파라미터 클래스

+ Serialize(), Deserialize() : 브로드캐스트 할 때 패킷에 커스텀 클래스를 넣을 수 없어 바이트화 하기 위해 추가된 코드

[Serializable]
public class CharacterParam : PhotonCustomType { }

[Serializable]
public class CharacterMoveParam : CharacterParam
{
    public float moveDir;
    
    public CharacterMoveParam(float _moveDir)
    {
        moveDir = _moveDir;
    }
}

// 기본공격, 점프공격
[Serializable]
public class CharacterAttackParam : CharacterParam
{
    public ENUM_ATTACKOBJECT_NAME attackTypeName;
    public bool reverseState;

    public CharacterAttackParam(ENUM_ATTACKOBJECT_NAME _attackTypeName, bool _reverseState)
    {
        attackTypeName = _attackTypeName;
        reverseState = _reverseState;
    }
    
    public new static object Deserialize(byte[] data)
    {
        string jsonData = Encoding.UTF8.GetString(data);
        return JsonUtility.FromJson<CharacterAttackParam>(jsonData);
    }
    
    public new static byte[] Serialize(object customObject)
    {
        var param = (CharacterAttackParam)customObject;
        string jsonData = JsonUtility.ToJson(param);
        return Encoding.UTF8.GetBytes(jsonData);
    }
}

// 캐릭터의 스킬 번호 ( 0 ~ 4 ) 0 : Dash
[Serializable]
public class CharacterSkillParam : CharacterParam
{
    public int skillNum;

    public CharacterSkillParam(int _skillNum)
    {
        skillNum = _skillNum;
    }
    
    public new static object Deserialize(byte[] data)
    {
        string jsonData = Encoding.UTF8.GetString(data);
        return JsonUtility.FromJson<CharacterSkillParam>(jsonData);
    }
    
    public new static byte[] Serialize(object customObject)
    {
        var param = (CharacterSkillParam)customObject;
        string jsonData = JsonUtility.ToJson(param);
        return Encoding.UTF8.GetBytes(jsonData);
    }
}

3. 캐릭터 애니메이션 제어

1) 애니메이터 컨트롤러

- 복잡한 구조도를 가능한 편하게 확인하고 수정할 수 있도록 특정 모션들은 서브 머신으로 묶어서 관리

- bool 변수와 Trigger 변수로 두 번 이상 체크를 함으로써 안정성 향상

  ex) isHit : 피격상태 / HitTrigger : 피격 1회

 

캐릭터 애니메이터의 베이스 레이어

2) 애니메이터 오버라이드 컨트롤러

- 매우 복잡한 캐릭터의 애니메이터 컨트롤러를 재사용하기 위해 사용

- 원본 애니메이터 컨트롤러와 동일한 로직을 사용하지만 원본 대신 새롭게 할당된 애니메이션을 재생

- 애니메이션 클립 이벤트와 스테이트 머신 비헤이비어(후술)를 통해 해당 모션에 대한 추가적인 기능을 함

 

애니메이터 오버라이드 컨트롤러 - Wizard

3) 스테이트 머신 비헤이비어

유니티에서 제공해주는 클래스로 스크립트를 상태 머신에 연결할 수 있음

- 모션의 상태 변경을 감지하기 위해 로직을 따로 작성할 필요 없이 해당 모션에서 수행할 특정 로직을 구현

public class DeadLanding : StateMachineBehaviour
{
    ActiveCharacter activeCharacter;

    public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        if (activeCharacter == null)
            activeCharacter = animator.transform.gameObject.GetComponent<ActiveCharacter>();

        CoroutineHelper.StartCoroutine(IDeadEffect());
    }

    private IEnumerator IDeadEffect()
    {
        SpriteRenderer charSpriteRenderer = activeCharacter.GetComponent<SpriteRenderer>();
        if (charSpriteRenderer == null)
            yield break;

        Color color = charSpriteRenderer.color;
        yield return new WaitForSeconds(1.5f);

        while (color.a > 0.1f)
        {
            color.a -= 0.01f;
            charSpriteRenderer.color = color;
            yield return null;
        }

        color.a = 0f;
        charSpriteRenderer.color = color;
        activeCharacter.EndGame();
    }
}

4. 캐릭터 스테이트 관리 고찰

복잡한 구조도를 가능한 편하게 확인하고 수정할 수 있도록 특정 모션들은 서브 머신으로 묶어서 관리

- 특히 Any State로 사용할 필요가 있는 Dash, Die, Skill(Attack) 등의 모션은 특히 신경써서 처리

 -> 해당 트랜지션의 조건 파라미터만 충족하면 어느 상태든 넘어가버리기 때문에 불 변수와 트리거 변수를 이용해 더블체크

1) 파라미터 분류 규칙

- IsXX 변수 (Bool) : 해당 상태로 전환할 수 있다는 의미

 -> 서브 스테이트 모션으로 전환, 탈출 조건 등에 사용

 

- XXState 변수 (Bool) : 해당 상태라는 의미

 -> 중복으로 같은 스테이트에 들어가지 않게 하기 위한 조건 등에 사용

 

- XXTrigger 변수 (Trigger) : 해당 상태로 전환하라는 의미

 -> 보통 bool변수와 함께 사용되며, AnyState로 연결된 트랜지션 조건에는 무조건 사용

 

- 기타 (int) : 방향 값이나 고유한 번호를 받기 위해 사용

 -> SkillType : 스킬 고유 번호 // DirX : X축 방향 값 ( -1, 0, 1 )

 

BaseLayer & Parameters

2) Sub State Machine - Jump

점프 상태의 돌입과 끝을 관리하는 서브 스테이트

- 초기에는 JumpAttack은 다른 서브스테이트머신(Attack)에 두고 점프 모션도 BaseLayer에 있었지만,

- 점프상태에서만 할 수 있다는 점과 착지모션 추가, 점프업다운상태 분리 등의 분할된 모션이 증가된 이유로 서브 스테이트로 묶어서 관리함

 

SubState - Jump

3) Sub State Machine - Attack

기본공격 3타와 스킬을 관리하는 서브 스테이트

 

- 기본 공격은 반드시 3타를 하지 않아도 1타, 2타 직후에 공격 키가 입력상태가 아니라면 Idle, Move 상태로 전환할 수 있음 ( 단, 살짝의 딜레이 존재 )

 flow) 공격 키 입력 상태 판단 -> 공격 사용가능 여부 판단 -> 공격 불 타입 True -> 공격 1회 트리거 On

 

- Skill은 캐릭터별로 지정된 스킬번호를 가지고 있음

 flow) 스킬 사용가능 여부 판단 -> 스킬 번호 세팅 -> 스킬 트리거 On

 

SubState - Attack

4) Sub State Machine - Hit

히트 상태에서의 보정, 히트 상태에서의 착지, 일어나는 모션 등을 관리하는 서브 스테이트

- 다단 히트가 가능한 게임으로써 히트 모션이 여러개 필요해 Hit 모션에서 Hit가 들어오면 다른 모션이 재생되게 하였고 트리거를 히트 1회로 처리했음

- 히트를 당해 공중에 떠있는 상태와 캐릭터가 스스로 공중에 떠있는 상태(점프키 입력)에 대한 파라미터 변수는 공용으로 사용

 

SubState - Hit