내일배움캠프 Unity 47일차 TIL - 팀 오오오오오 - 개발일지

Wooooo·2024년 1월 3일
0

내일배움캠프Unity

목록 보기
49/94

[오늘의 키워드]

플레이어 캐릭터에게 FSM을 적용했다.
이제 앉기, 서기가 가능하다.

카메라를 빠르게 흔들면 총기의 transform이 카메라의 transform과 동기화 되지 못하고 지터링이 발생하던 버그를 고쳤다.

Weapon 객체에 애니메이터를 적용해서 ADS 시점 변환 연출을 구현했다.


[캐릭터에 FSM 적용]

[FSM 인터페이스]

public interface IState
{
    void OnStateEnter();
    void OnStateStay();
    void OnStateExit();
}

public interface IStateMachine<T> where T : IState
{
    void StateTransition(T nextState);
}

FSM을 만들기 위해, 상태 쪽과 상태머신 쪽 인터페이스를 만들어줬다.
상태머신은 제네릭으로 IState를 구현하는 자료형만 넘겨받을 수 있게 조건을 걸어줬다.

[StateMachine]

using UnityEngine;

public class StateMachine<T> : MonoBehaviour, IStateMachine<T> where T : IState
{
    protected T _currentState;

    public virtual void StateTransition(T nextState)
    {
        _currentState?.OnStateExit();
        _currentState = nextState;
        _currentState?.OnStateEnter();
    }

    protected virtual void Update()
    {
        _currentState?.OnStateStay();
    }
}

상태머신의 최상위 클래스다. 현재의 상태와 각 상태의 로직 호출, 상태 전이 등을 구현해줬다.
이 클래스를 상속받아서 세부적인 상태 머신을 필요에 따라 구현할 수 있도록 고민해봤다.
PlayerStateMachine은 이 클래스를 상속받아 구현했다.

[PlayerStateBase]

public class PlayerStateBase : IState
{
    protected PlayerStateMachine _machine;
    protected PlayerController _controller;
    protected InputManager _input;
    protected Transform _cameraTransform;

    public PlayerStateBase(PlayerStateMachine machine)
    {
        _machine = machine;
        _controller = machine.Controller;
        _input = machine.Input;
        _cameraTransform = machine.CameraTransform;
    }

    public virtual void OnStateEnter()
    {
    }

    public virtual void OnStateExit()
    {
    }

    public virtual void OnStateStay()
    {
    }
}

플레이어의 상태를 구현하기 위해, 플레이어의 정보를 갖고 있을 수 있도록 상태의 Base를 만들어줬다.

[PlayerStateMachine]

using System.Collections.Generic;
using UnityEngine;

public class PlayerStateMachine : StateMachine<PlayerStateBase>
{
    public PlayerController Controller { get; private set; }
    public InputManager Input { get; private set; }
    public Transform CameraTransform { get; private set; }

    public enum PlayerState
    {
        Stand,
        Sit,
    }

    private Dictionary<PlayerState, PlayerStateBase> _stateDict;

    public Dictionary<PlayerState, PlayerStateBase> StateDictionary => _stateDict;

    private void Awake()
    {
        Initialize();
    }

    public void Initialize()
    {
        Controller = GetComponent<PlayerController>();
        Input = InputManager.Instance;
        CameraTransform = Camera.main.transform;
        _stateDict = new()
        {
            { PlayerState.Stand, new PlayerStandState(this) },
            { PlayerState.Sit, new PlayerSitState(this) },
        };
        StateTransition(_stateDict[PlayerState.Stand]);
    }
}

PlayerStateBase를 제네릭으로 넘겨받는 PlayerStateMachine을 작성했다.
딕셔너리와 열거형을 이용해서 상태 객체를 관리하도록 했다.

[PlayerStateBase를 상속받는 세부 상태 구현]

플레이어는 앉기, 서기 상태가 있을 수 있다. 필요하다면 이후에 점프, 낙하 상태가 필요할 수 있으므로 PlayerStateBase를 상속받는 PlayerGroundState를 작성하고, PlayerGroundState를 상속받는 PlayerStandStatePlayerSitState를 작성했다.

이후, 점프와 낙하 상태의 구현이 필요하다면 PlayerAirState를 만들고 PlayerJumpStatePlayerFallState를 작성하면 될 것 같다.

public class PlayerGroundState : PlayerStateBase
{
    public PlayerGroundState(PlayerStateMachine machine) : base(machine) { }

    public override void OnStateStay()
    {
        _controller.Move();
        if (_input.ADSTrigger)
            _controller.ChangeADS();
    }
}

public class PlayerStandState : PlayerGroundState
{
    public PlayerStandState(PlayerStateMachine machine) : base(machine) { }

    public override void OnStateEnter()
    {
        _controller.Stand();
    }

    public override void OnStateStay()
    {
        base.OnStateStay();
        if (_input.SitKeyDown)
            _machine.StateTransition(_machine.StateDictionary[PlayerStateMachine.PlayerState.Sit]);
    }
}

public class PlayerSitState : PlayerGroundState
{
    public PlayerSitState(PlayerStateMachine machine) : base(machine) { }

    public override void OnStateEnter()
    {
        _controller.Sit();
    }

    public override void OnStateStay()
    {
        base.OnStateStay();
        if (_input.SitKeyUp)
            _machine.StateTransition(_machine.StateDictionary[PlayerStateMachine.PlayerState.Stand]);
    }
}

[Depth Only Camera로 FPS 총기 렌더링]

[트러블 슈팅]

기존에는 Player 객체 아래에 Weapon이라는 객체를 만들고, Cimemachine을 이용해 1인칭 시점을 구현하고 그 MainCamera에 딱 붙여서 총기를 렌더링하게 했다.

그 과정에서 카메라의 회전값만큼 캐릭터와 총기모델을 회전하게 해줬는데, 카메라를 빠르게 흔들면 총기에 지터링이 발생했다.

아무래도, 카메라의 회전값과 총기모델의 회전값을 동기화하는 시점이 달라서 발생하는 문제같아서 LateUpdate() 메서드에서도 동기화해보고, 마우스 인풋값에 따라 카메라를 회전시켜주는 시네머신 확장 클래스의 PostPipelineStageCallback() 메서드에서도 동기화 해봤지만 결과는 모두 같았다.

[해결]

MainCamera의 자식 오브젝트로 Weapon객체를 넣어줬다.
그리고, 카메라를 하나 더 만들어서 Weapon만 렌더링 되도록 컬링마스크를 건 Depth-Only Camera를 만들었다.


[구현 영상]

이동, 앉기, 서기, ADS전환, 카메라에임 모두 잘 동작한다.

profile
game developer

0개의 댓글