public abstract class BaseState{ protected PlayerStateMachine Machine; public BaseState(PlayerStateMachine machine) { this.Machine = machine; } public abstract void StartState(); public abstract void HandleInput(); public abstract void LogicUpdate(); public abstract void EndState(); }
2. FSM 만들기
public class PlayerStateMachine : MonoBehaviour{ private Dictionary<Type, BaseState> states; private BaseState currentState; public Player Player; public void Init(Player player) { Player = player; } // Start is called before the first frame update void Start() { states = new Dictionary<Type, BaseState>() { { typeof(IdleState), new IdleState(this) }, { typeof(MoveState), new MoveState(this) }, }; ChangeState<IdleState>(); } // Update is called once per frame void Update() { currentState.HandleInput(); currentState.LogicUpdate(); } // 호출 예시 : ChangeState<IdleState>(); public void ChangeState<T>() where T : BaseState { currentState?.EndState(); currentState = states[typeof(T)]; currentState.StartState(); } }
3. 실제 상태 만들기
Idle 상태
public class IdleState : BaseState{ public IdleState(PlayerStateMachine machine) : base(machine) { } public override void StartState() { Machine.Player.Animator.IdleAnimation(); Machine.Player.Move.rb.velocity = Vector2.zero; } public override void HandleInput() { var moveInput = Machine.Player.Input.Player.Move.ReadValue<Vector2>(); if (moveInput.x != 0) { Machine.ChangeState<MoveState>(); return; } } public override void LogicUpdate() { } public override void EndState() { Machine.Player.Animator.AddEmptyAnimation(); } }
Move 상태
public class MoveState : BaseState{ public MoveState(PlayerStateMachine machine) : base(machine) { } public override void StartState() { Machine.Player.Animator.MoveAnimation(); } public override void HandleInput() { var moveInput = Machine.Player.Input.Player.Move.ReadValue<Vector2>(); if (moveInput.x == 0) { Machine.ChangeState<IdleState>(); return; } } public override void LogicUpdate() { Machine.Player.Move.Move(); } public override void EndState() { Machine.Player.Animator.AddEmptyAnimation(); } }
애니메이션을 붙이자
public class PlayerAnimator : MonoBehaviour{ #region 애니메이션들 [Header("Animation References")] public AnimationReferenceAsset idle; public AnimationReferenceAsset move; public AnimationReferenceAsset attack; [Header("Animation Mix Settings")] public float mixEmptyDuration; public float delayEmpty; #endregion private const int MainAnimationTrack = 0; [HideInInspector] public SkeletonAnimation skeletonAnimation; public Player Player; public void Init(Player player) { Player = player; } private void Awake() { skeletonAnimation = GetComponent<SkeletonAnimation>(); if (skeletonAnimation == null) { Debug.LogError("SkeletonAnimation is null"); return; } } public void IdleAnimation() { skeletonAnimation.AnimationState.SetAnimation(MainAnimationTrack, idle, true); } public void MoveAnimation() { skeletonAnimation.AnimationState.SetAnimation(MainAnimationTrack, move, true); } public void AttackAnimation() { // 공격 모션 끝나면 Idle로 돌아올 수 있도록 함 var entry = skeletonAnimation.AnimationState.SetAnimation(MainAnimationTrack, attack, false); entry.Complete += (trackEntry) => { Player.StateMachine.ChangeState<IdleState>(); }; } public void AddEmptyAnimation() { skeletonAnimation.AnimationState.AddEmptyAnimation(MainAnimationTrack, mixEmptyDuration, delayEmpty); } }
좌우로 움직일 수 있게 해보자
public class PlayerMove : MonoBehaviour{ #region 이동 관련 변수 [Header("Move Settings")] public float moveSpeed; public bool isLeft = false; #endregion public Rigidbody2D rb; private Player player; public void Init(Player player) { this.player = player; } public void Move() { var moveInput = player.Input.Player.Move.ReadValue<Vector2>(); Turn(moveInput.x < 0); rb.velocity = new Vector2(moveInput.x * moveSpeed, rb.velocity.y); } public void Turn(bool isLeft) { this.isLeft = isLeft; player.Animator.skeletonAnimation.Skeleton.ScaleX = isLeft ? -1 : 1; } }