๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ
728x90
 

 

 

๋ชจ๋“ˆํ™”์˜ ํ•„์š”์„ฑ์„ ๋Š๋ผ๋‹ค

 

๊ธฐ์กด PlayerController ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด, ๊ธฐ์กด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•ด์„œ ํ™•์žฅ์ด ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

์ €ํฌ ๊ฒŒ์ž„์—๋Š” ๋Œ€์‹œ, ๋Œ€์‹œ ๊ณต๊ฒฉ, ์ฐจ์ง€ ๊ณต๊ฒฉ, ๊ธฐ๋ณธ ๊ณต๊ฒฉ ์ฝค๋ณด 3ํƒ€ ๋“ฑ๋“ฑ ํ•œ ์ƒํƒœ์—์„œ ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋ถ„๊ธฐ๋˜๋Š” ๋™์ž‘๋“ค์ด ๋งŽ์•˜์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ง€๊ธˆ ์ฝ”๋“œ ๊ตฌ์กฐ๋กœ ๊ณ„์† ํ™•์žฅํ•˜๋‹ค๊ฐ€๋Š” ์„ธ๊ณ„ ์ œ์ผ์˜ ์ŠคํŒŒ๊ฒŒํ‹ฐ๋ฅผ ๋งŒ๋“ค ๊ฒƒ ๊ฐ™์•„์„œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ด์ „์— ๋””์ž์ธ ํŒจํ„ด(Degisn Pattern)๋“ค์„ ๋ณด๋˜ ์ค‘, ์ƒํƒœ ํŒจํ„ด(State Pattern)์„ ์ ์šฉํ•˜๋ฉด Controller์˜ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ๊ต‰์žฅํžˆ ์‰ฌ์šธ ๊ฒƒ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์–ด๋–ค ์‹์œผ๋กœ ๊ตฌ์กฐ๋ฅผ ๋ฐ”๊พธ์—ˆ๋Š”์ง€ ์•„๋ž˜์—์„œ ๋ณด๋ฉด์„œ ์ฒœ์ฒœํžˆ ์„ค๋ช…ํ•ด ๋“œ๋ฆด๊ฒŒ์š”.

 

 

 


1. ๋ชจ๋“  ์ƒํƒœ๋“ค์˜ ๊ธฐ์›์ด ๋˜๋Š” ๋ถ€๋ชจ ์ถ”์ƒ ํด๋ž˜์Šค, BaseState ์ •์˜ํ•˜๊ธฐ

 

๊ฐ ์ƒํƒœ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

  • ํ˜„์žฌ ์ƒํƒœ์—์„œ ํ–‰๋™์˜ ์ฃผ์ฒด๊ฐ€ ๋˜๋Š” Controller
  • ์ƒํƒœ์— ์ง„์ž…ํ–ˆ์„ ๋•Œ, ์‹คํ–‰๋˜๋Š” OnEnterState()
  • ํ˜„์žฌ ์ƒํƒœ์—์„œ ๊ณ„์† ๊ฐฑ์‹ ๋˜์–ด์•ผ ํ•˜๋Š” ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” OnUpdateState()
  • ํ˜„์žฌ ์ƒํƒœ์—์„œ ๋ฌผ๋ฆฌ์™€ ๊ด€๋ จํ•˜์—ฌ ์—…๋ฐ์ดํŠธํ•˜๋Š” OnFixedUpdate()
  • ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ข…๋ฃŒํ•  ๋•Œ, ์‹คํ–‰๋˜๋Š” OnExitState()

 

 

์œ„์™€ ๊ฐ™์ด ์ •๋ฆฌ๋ฅผ ํ•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถ”์ƒ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CharacterController
{
    public abstract class BaseState
    {
        protected PlayerController Controller { get; private set; }

        public BaseState(PlayerController controller)
        {
            this.Controller = controller;
        }

        public abstract void OnEnterState();
        public abstract void OnUpdateState();
        public abstract void OnFixedUpdateState();
        public abstract void OnExitState();
    }
}

 

์ด์ œ ์ด BaseState ํด๋ž˜์Šค๋ฅผ ์ƒ์† ๋ฐ›์•„ ์ด๋™(Move), ๋Œ€์‹œ(Dash), ๊ณต๊ฒฉ(Attack)๊ณผ ๊ฐ™์€ ๊ฐ ์ƒํƒœ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋“ค์„ ๊ตฌํ˜„ํ•˜๋ฉด ๋˜๊ฒ ๋„ค์š”! ๊ฐ ์ƒํƒœ๋“ค์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฑด ๋’ค์— ํ•˜๊ธฐ๋กœ ํ•˜๊ณ , ์ด์ œ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ์ „ํ™˜์‹œ์ผœ์ฃผ๋Š” ํด๋ž˜์Šค์ธ StateMachine์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

 

 


2. StateMachine ํด๋ž˜์Šค ์ •์˜ํ•˜๊ธฐ

 

StateMachine ํด๋ž˜์Šค๋Š” ํ•„์š”ํ•œ ์ƒํƒœ๋“ค์„ Dictionary๋กœ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

namespace CharacterController
{
    public enum StateName
    {
        MOVE = 100,
        DASH,
        ATTACK,
    }
}
using System.Collections.Generic;
using System.Diagnostics;

namespace CharacterController
{
    public class StateMachine
    {
        public BaseState CurrentState { get; private set; }  // ํ˜„์žฌ ์ƒํƒœ
        private Dictionary<StateName, BaseState> states =
                                            new Dictionary<StateName, BaseState>();


        public StateMachine(StateName stateName, BaseState state)
        {
            AddState(stateName, state);
            CurrentState = GetState(stateName);
        }

        public void AddState(StateName stateName, BaseState state)  // ์ƒํƒœ ๋“ฑ๋ก
        {
            if (!states.ContainsKey(stateName))
            {
                states.Add(stateName, state);
            }
        }

        public BaseState GetState(StateName stateName)  // ์ƒํƒœ ๊บผ๋‚ด์˜ค๊ธฐ
        {
            if (states.TryGetValue(stateName, out BaseState state))
                return state;
            return null;
        }

        public void DeleteState(StateName removeStateName)  // ์ƒํƒœ ์‚ญ์ œ
        {
            if (states.ContainsKey(removeStateName))
            {
                states.Remove(removeStateName);
            }
        }

        public void ChangeState(StateName nextStateName)    // ์ƒํƒœ ์ „ํ™˜
        {
            CurrentState?.OnExitState();   //ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ข…๋ฃŒํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ ,
            if (states.TryGetValue(nextStateName, out BaseState newState)) // ์ƒํƒœ ์ „ํ™˜
            {
                CurrentState = newState;
            }
            CurrentState?.OnEnterState();  // ๋‹ค์Œ ์ƒํƒœ ์ง„์ž… ๋ฉ”์†Œ๋“œ ์‹คํ–‰
        }

        public void UpdateState()
        {
            CurrentState?.OnUpdateState();
        }

        public void FixedUpdateState()
        {
            CurrentState?.OnFixedUpdateState();
        }
    }
}

 

์ด์ œ ์ค€๋น„๋Š” ๋‹ค ํ–ˆ์œผ๋‹ˆ, Player์™€ PlayerController ํด๋ž˜์Šค๋ฅผ ์กฐ๊ธˆ ์ •๋ฆฌํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 


3. Player์™€ PlayerController ํด๋ž˜์Šค ์ •๋ฆฌํ•˜๊ธฐ

 

Player ํด๋ž˜์Šค

 

Player ํด๋ž˜์Šค๋Š” ์‹ฑ๊ธ€ํ†ค(Singleton) ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒŒ์ž„ํ•˜๋Š” ๋‚ด๋‚ด ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ง€๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์ฃ .

์‹ฑ๊ธ€ํ†ค์ด๊ธฐ์— ๋‹ค๋ฅธ ํด๋ž˜์Šค ์–ด๋””์—์„œ๋„ ์ „์—ญ์ ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ฐ ์ƒํƒœ ํด๋ž˜์Šค์—์„œ ํ–‰๋™์„ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ๋ฐ ํด๋ž˜์Šค๋“ค์€ ์—ฌ๊ธฐ์— ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. Rigidbody, Animator, StateMachine ๋“ฑ๋“ฑ ๋ง์ด์ฃ . ๋‹ค๋งŒ ์™ธ๋ถ€์—์„œ ์ˆ˜์ •์€ ํ•จ๋ถ€๋กœ ๋ชปํ•˜๊ฒŒ ์ฝ๊ธฐ ์ „์šฉ ํ”„๋กœํผํ‹ฐ๋กœ ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

 

์ด์ œ Player ํด๋ž˜์Šค๋Š” ๋ง ๊ทธ๋Œ€๋กœ ํ”Œ๋ ˆ์ด์–ด์˜ ์ •๋ณด๋“ค๋งŒ ๋‹ด๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค๊ฐ€ ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

*์ƒํƒœ ํŒจํ„ด ์ง„ํ–‰ํ•˜๊ณ , ์ด๊ฒƒ ์ €๊ฒƒ ์ข€ ๋งŽ์ด ๋งŒ๋“ค๊ณ  ์ˆ˜์ •ํ•œ ํ›„์— ๊ธ€ ์“ฐ๋Š” ๊ฑฐ๋ผ ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ฑธ๋Ÿฌ๋‚ด๊ธฐ๊ฐ€ ํž˜๋“œ๋„ค์š”..ใ„ทใ„ท

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CharacterController;

public class Player : MonoBehaviour
{
    public static Player Instance { get { return instance; } }
    public StateMachine stateMachine { get; private set; }
    public Rigidbody rigidBody { get; private set; }
    public Animator animator { get; private set; }
    public CapsuleCollider capsuleCollider { get; private set; }

    private static Player instance;


    #region #์บ๋ฆญํ„ฐ ์Šคํƒฏ
    public float MaxHP     { get { return maxHP; } }
    public float CurrentHP { get { return currentHP; } }
    public float Armor     { get { return armor; } }
    public float MoveSpeed { get { return moveSpeed; } }
    public int DashCount { get { return dashCount; } }

    [Header("์บ๋ฆญํ„ฐ ์Šคํƒฏ")]
    [SerializeField] protected float maxHP;
    [SerializeField] protected float currentHP;
    [SerializeField] protected float armor;
    [SerializeField] protected float moveSpeed;
    [SerializeField] protected int dashCount;
    #endregion

    #region #Unity ํ•จ์ˆ˜
    void Awake()
    {
        if(instance == null)
        {
            instance = this;
            rigidBody = GetComponent<Rigidbody>();
            animator = GetComponent<Animator>();
            capsuleCollider = GetComponent<CapsuleCollider>();
            DontDestroyOnLoad(gameObject);
            return;
        }
        DestroyImmediate(gameObject);
    }

    void Start()
    {
        InitStateMachine();
    }

    void Update()
    {
        stateMachine?.UpdateState();
    }

    void FixedUpdate()
    {
        stateMachine?.FixedUpdateState();
    }
    #endregion

    public void OnUpdateStat(float maxHP, float currentHP, float armor, float moveSpeed, int dashCount)
    {
        this.maxHP = maxHP;
        this.currentHP = currentHP;
        this.armor = armor;
        this.moveSpeed = moveSpeed;
        this.dashCount = dashCount;
    }

    private void InitStateMachine()
    {
       // ์•„์ง ์ƒํƒœ๋“ค์„ ์•ˆ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ, ๋งŒ๋“ค๊ณ  ์—ฌ๊ธฐ์—์„œ ๋“ฑ๋กํ•˜๋„๋ก ํ•ฉ์‹œ๋‹ค.
       ...
    }
}

 

๊ฐ ์ƒํƒœ์—์„œ ์—…๋ฐ์ดํŠธ ๋˜์–ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„๋“ค์€ ๊ฐ Unity ๋‚ด์žฅ ํ•จ์ˆ˜๋“ค์—์„œ ์‹คํ–‰์‹œ์ผœ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

void Update()
{
    stateMachine?.UpdateState();
}

void FixedUpdate()
{
    stateMachine?.FixedUpdateState();
}

 

 

 

PlayerController ํด๋ž˜์Šค

 

PlayerController ํด๋ž˜์Šค๋Š” ๊ธฐ์กด์—๋Š” ์ž…๋ ฅ๋„ ๋ฐ›๊ณ , ๊ฑฐ๊ธฐ์— ๋”ฐ๋ฅธ ํ–‰๋™๋„ ์ˆ˜ํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํด๋ž˜์Šค ๊ทœ๋ชจ๊ฐ€ ์ข€ ์ปธ์Šต๋‹ˆ๋‹ค.

์ด๋™, ๋Œ€์‹œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋“ค๋„ ์—ฌ๊ธฐ์— ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด์ฃ . ์ด์ œ PlayerController ํด๋ž˜์Šค๋Š” ๋™์ž‘์€ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ , ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ ์ •๋ณด๋งŒ ๋ฐ›์•„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;
using CharacterController;


[RequireComponent(typeof(Player))]
public class PlayerController : MonoBehaviour
{
    public Player player { get; private set; }
    public Vector3 inputDirection { get; private set; } // ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์œผ๋กœ ๋“ค์–ด์˜จ ์ด๋™ ๋ฐฉํ–ฅ
    public Vector3 calculatedDirection { get; private set; } // ๊ฒฝ์‚ฌ ์ง€ํ˜• ๋“ฑ์„ ๊ณ„์‚ฐํ•œ ๋ฐฉํ–ฅ
    public Vector3 gravity { get; private set; }

    #region #๊ฒฝ์‚ฌ ์ฒดํฌ ๋ณ€์ˆ˜
    [Header("๊ฒฝ์‚ฌ ์ง€ํ˜• ๊ฒ€์‚ฌ")]
    [SerializeField, Tooltip("์บ๋ฆญํ„ฐ๊ฐ€ ๋“ฑ๋ฐ˜ํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๊ฒฝ์‚ฌ ๊ฐ๋„์ž…๋‹ˆ๋‹ค.")]
    float maxSlopeAngle;
    [SerializeField, Tooltip("๊ฒฝ์‚ฌ ์ง€ํ˜•์„ ์ฒดํฌํ•  Raycast ๋ฐœ์‚ฌ ์‹œ์ž‘ ์ง€์ ์ž…๋‹ˆ๋‹ค.")]
    Transform raycastOrigin;

    private const float RAY_DISTANCE = 2f;
    private RaycastHit slopeHit;
    private bool isOnSlope;
    #endregion

    #region #๋ฐ”๋‹ฅ ์ฒดํฌ ๋ณ€์ˆ˜
    [Header("๋•… ์ฒดํฌ")]
    [SerializeField, Tooltip("์บ๋ฆญํ„ฐ๊ฐ€ ๋•…์— ๋ถ™์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ CheckBox ์‹œ์ž‘ ์ง€์ ์ž…๋‹ˆ๋‹ค.")]
    Transform groundCheck;
    private int groundLayer;
    private bool isGrounded;
    #endregion

    #region #UNITY_FUNCTIONS
    void Start()
    {
        player = GetComponent<Player>();
        groundLayer = 1 << LayerMask.NameToLayer("Ground");
    }

    void Update()
    {
        calculatedDirection = GetDirection(player.MoveSpeed * MoveState.CONVERT_UNIT_VALUE);
        ControlGravity();
    }
    #endregion

    public void OnDashInput(InputAction.CallbackContext context)
    {
        if (context.performed)
        {
            // ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„, ์—ฌ๊ธฐ์„œ๋Š” ์ƒํƒœ ์ „ํ™˜๋งŒ ํ•ด์ฃผ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
        }
    }

    protected Vector3 GetMouseWorldPosition()
    {
        Vector3 mousePosition = Mouse.current.position.ReadValue();
        Ray ray = Camera.main.ScreenPointToRay(mousePosition);
        Debug.DrawRay(ray.origin, ray.direction * 1000f, Color.red, 5f);

        if (Physics.Raycast(ray, out RaycastHit HitInfo, Mathf.Infinity))
        {
            Vector3 target = HitInfo.point;
            Vector3 myPosition = new Vector3(transform.position.x, 0f, transform.position.z);
            target.Set(target.x, 0f, target.z);
            return (target - myPosition).normalized;
        }
        return Vector3.zero;
    }

    protected Vector3 GetDirection(float currentMoveSpeed)
    {
        isOnSlope = IsOnSlope();
        isGrounded = IsGrounded();
        Vector3 calculatedDirection =
            CalculateNextFrameGroundAngle(currentMoveSpeed) < maxSlopeAngle ?
            inputDirection : Vector3.zero;

        calculatedDirection = (isGrounded && isOnSlope) ?
           AdjustDirectionToSlope(calculatedDirection) : calculatedDirection.normalized;
        return calculatedDirection;
    }

    protected void ControlGravity()
    {
        gravity = Vector3.down * Mathf.Abs(player.rigidBody.velocity.y);

        if (isGrounded && isOnSlope)
        {
            gravity = Vector3.zero;
            player.rigidBody.useGravity = false;
            return;
        }
        player.rigidBody.useGravity = true;
    }

    private float CalculateNextFrameGroundAngle(float moveSpeed)
    {
        // ๋‹ค์Œ ํ”„๋ ˆ์ž„ ์บ๋ฆญํ„ฐ ์•ž ๋ถ€๋ถ„ ์œ„์น˜
        Vector3 nextFramePlayerPosition =
           raycastOrigin.position + inputDirection * moveSpeed * Time.fixedDeltaTime;

        if (Physics.Raycast(nextFramePlayerPosition, Vector3.down, 
            out RaycastHit hitInfo, RAY_DISTANCE, groundLayer))
        {
            return Vector3.Angle(Vector3.up, hitInfo.normal);
        }
        return 0f;
    }

    public bool IsGrounded()
    {
        Vector3 boxSize = new Vector3(transform.lossyScale.x, 0.4f, transform.lossyScale.z);
        return Physics.CheckBox(groundCheck.position, boxSize, Quaternion.identity,
               groundLayer);
    }

    public bool IsOnSlope()
    {
        Ray ray = new Ray(transform.position, Vector3.down);
        if (Physics.Raycast(ray, out slopeHit, RAY_DISTANCE, groundLayer))
        {
            var angle = Vector3.Angle(Vector3.up, slopeHit.normal);
            return angle != 0f && angle < maxSlopeAngle;
        }
        return false;
    }

    public void OnMoveInput(InputAction.CallbackContext context)
    {
        Vector2 input = context.ReadValue<Vector2>();
        inputDirection = new Vector3(input.x, 0f, input.y);
    }

    public void LookAt(Vector3 direction)
    {
        if (direction != Vector3.zero)
        {
            Quaternion targetAngle = Quaternion.LookRotation(direction);
            transform.rotation = targetAngle;
        }
    }

    protected Vector3 AdjustDirectionToSlope(Vector3 direction)
    {
        Vector3 adjustVelocityDirection =
            Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;
        return adjustVelocityDirection;
    }
}

 

์šฐ๋ฆฌ๊ฐ€ ์ด์ „ ๊ธ€๋“ค์—์„œ ๋งŒ๋“ค์—ˆ๋˜ ๊ฒฝ์‚ฌ๋กœ ์ง€ํ˜• ๊ฒ€์‚ฌ, ๋•…์— ๋ถ™์–ด ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌ, ์ค‘๋ ฅ ์ ์šฉ์€ ์—ฌ๊ธฐ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉ๋˜๋„๋ก ๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ์บ๋ฆญํ„ฐ์— PlayerController ์Šคํฌ๋ฆฝํŠธ๋งŒ ๋ถ™์ด๋ฉด ์ž๋™์ ์œผ๋กœ ํ•ด๋‹น ๊ธฐ๋Šฅ๋“ค์ด ์ง€์›๋˜๋„๋ก ๋ง์ด์ฃ .

 

๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฐ›์•„์„œ ๋ฐ›์€ ์ •๋ณด๋“ค๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ด๋™, ๋Œ€์‹œ์™€ ๊ฐ™์€ ์ƒํƒœ๋ฅผ ์ •์˜ํ•˜์—ฌ ๊ฑฐ๊ธฐ์—์„œ ํ–‰๋™์„ ๊ตฌํ˜„ํ•˜๋˜, ํ–‰๋™์— ํ•„์š”ํ•œ ์ •๋ณด๋Š” ์—ฌ๊ธฐ์—์„œ ๊ฐ€์ ธ์˜ฌ ๊ฒ๋‹ˆ๋‹ค.

 

 

 


4. ์ƒํƒœ ์ •์˜ํ•˜์—ฌ ๋“ฑ๋กํ•˜๊ธฐ

 

์ด๋™ ์ƒํƒœ(MoveState) ์ •์˜ํ•˜์—ฌ ๋“ฑ๋กํ•˜๊ธฐ

 

์šฐ๋ฆฌ๊ฐ€ ๊ธฐ์กด์— PlayerController์—์„œ ์ด๋™์— ํ•„์š”ํ–ˆ๋˜ ์ •๋ณด๋“ค ์žˆ์ฃ ? ๊ทธ๊ฑธ ์ด ํด๋ž˜์Šค์—์„œ ์„ ์–ธํ•˜๊ณ  ๋˜‘๊ฐ™์ด ์ ์–ด์ค„ ๊ฒ๋‹ˆ๋‹ค.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.GraphView;

namespace CharacterController
{
    public class MoveState : BaseState
    {
        public const float CONVERT_UNIT_VALUE = 0.01f;
        public const float DEFAULT_CONVERT_MOVESPEED = 3f;
        public const float DEFAULT_ANIMATION_PLAYSPEED = 0.9f;
        private int hashMoveAnimation;

        public MoveState(PlayerController controller) : base(controller)
        {
            hashMoveAnimation = Animator.StringToHash("Velocity");
        }

        protected float GetAnimationSyncWithMovement(float changedMoveSpeed)
        {
            if (Controller.inputDirection == Vector3.zero)
            {
                return -DEFAULT_ANIMATION_PLAYSPEED;
            }

            // (๋ฐ”๋€ ์ด๋™ ์†๋„ - ๊ธฐ๋ณธ ์ด๋™์†๋„) * 0.1f
            return (changedMoveSpeed - DEFAULT_CONVERT_MOVESPEED) * 0.1f;
        }

        public override void OnEnterState()
        {
            // ํ•„์š”์—†๋Š” ๋ถ€๋ถ„์ด์ง€๋งŒ, ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋Š” ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋น„์–ด ๋‘”๋‹ค.
        }

        public override void OnUpdateState()
        {
            // ํ•„์š”์—†๋Š” ๋ถ€๋ถ„์ด์ง€๋งŒ, ์ถ”์ƒ ๋ฉ”์†Œ๋“œ๋Š” ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ๋น„์–ด ๋‘”๋‹ค.
        }

        // ์ด๋™์€ Rigidbody ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ, FixedUpdate()์—์„œ ๊ตฌํ˜„ํ•ด์ค๋‹ˆ๋‹ค.
        public override void OnFixedUpdateState()
        {
            float currentMoveSpeed = Controller.player.MoveSpeed * CONVERT_UNIT_VALUE;
            float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED + GetAnimationSyncWithMovement(currentMoveSpeed);
            Controller.LookAt(Controller.inputDirection);
            Player.Instance.rigidBody.velocity = Controller.calculatedDirection * currentMoveSpeed + Controller.gravity;
            Player.Instance.animator.SetFloat(hashMoveAnimation, animationPlaySpeed);
        }

        // ์ด๋™ ์ƒํƒœ๋ฅผ ์ข…๋ฃŒํ•  ๋•Œ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ๋ฌผ๋ฆฌ ์†๋„๋ฅผ ์ดˆ๊ธฐํ™” ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
        public override void OnExitState()
        {
            Player.Instance.animator.SetFloat(hashMoveAnimation, 0f);
            Player.Instance.rigidBody.velocity = Vector3.zero;
        }
    }
}

 

PlayerController์—์„œ ์ด๋™ ๊ตฌํ˜„ํ–ˆ๋˜ ๊ฑธ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™€์„œ ์ ์€ ๊ฒ๋‹ˆ๋‹ค. ์ด์ œ ์ด๊ฑธ StateMachine์— ์ƒํƒœ๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ์ค์‹œ๋‹ค.

// Player ํด๋ž˜์Šค

private void InitStateMachine()
{
    PlayerController controller = GetComponent<PlayerController>();
    stateMachine = new StateMachine(StateName.MOVE, new MoveState(controller)); // ๋“ฑ๋ก
}

 

์ด๋ ‡๊ฒŒ ๋“ฑ๋กํ•ด์คฌ์œผ๋ฉด ์ด์ œ ๋์ž…๋‹ˆ๋‹ค. ์‹คํ–‰ํ•ด์„œ ํ•œ ๋ฒˆ ์ž˜ ๋˜๋Š”์ง€ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ฃ .

 

์ด๋™ ์ƒํƒœ(Move State)

 

๋‹น์—ฐํžˆ ๊ธฐ๋Šฅ์ ์œผ๋กœ ๋ณ€ํ•œ ๊ฑด ์—†์Šต๋‹ˆ๋‹ค. ๋‹จ์ง€ ์œ ์ง€๋ณด์ˆ˜ ๋ฐ ํ™•์žฅ์„ ์šฉ์ดํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด์ง€์š”. ๋Œ€์‹œ๋„ ํ•œ ๋ฒˆ ๋˜‘๊ฐ™์ด ์ ์šฉํ•˜์—ฌ ๋ด…์‹œ๋‹ค.

 

 

๋Œ€์‹œ ์ƒํƒœ(DashState) ์ •์˜ํ•˜์—ฌ ๋“ฑ๋กํ•˜๊ธฐ

 

DashState ํด๋ž˜์Šค๋„ ์ •์˜ํ•˜์—ฌ ํ–‰๋™์„ ๊ตฌํ˜„ํ•ด์ฃผ๋„๋ก ํ•ฉ์‹œ๋‹ค.

์ฝ”๋“œ๋ฅผ ์“ฐ๋‹ค๋ณด๋‹ˆ, ๋Œ€์‹œ(Dash)์— ํ•„์š”ํ•œ ๋ณ€์ˆ˜๋“ค์ด ์ €๋ ‡๊ฒŒ ๋งŽ๋”๊ตฐ์š”. ์ €๊ฒŒ ๋‹ค PlayerController ํด๋ž˜์Šค ์•ˆ์— ์žˆ์—ˆ์œผ๋‹ˆ ์–ผ๋งˆ๋‚˜ ํด๋ž˜์Šค๊ฐ€ ํž˜๋“ค์—ˆ๊ฒ ์Šต๋‹ˆ๊นŒ... ์•„, ๊ทธ๋ฆฌ๊ณ  Animator์˜ Apply RootMotion์„ ๋Œ€์‹œ ์ค‘์ผ ๋•Œ๋Š” ๋„๊ณ  ํ‰์ƒ ์‹œ์—๋Š” ์ผœ๋Š” ๊ฑธ๋กœ ๊ตฌํ˜„ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๊ฒŒ์‹œํ•  ๊ณต๊ฒฉ ์• ๋‹ˆ๋ฉ”์ด์…˜๋“ค์ด RootMotion์ด ํ•„์š”ํ•œ ์• ๋“ค์ด๋ผ ์ด๋ ‡๊ฒŒ ์กฐ์น˜๋ฅผ ์ทจํ–ˆ์–ด์š”!

์ด์ œ PlayerController์—์„œ ๋Œ€์‹œ ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด ๋Œ€์‹œ ์ƒํƒœ๋กœ ์ „ํ™˜ํ•ด์ฃผ๋Š” ์ฝ”๋“œ๋งŒ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋˜๊ฒ ๋„ค์š”.

N๋‹จ ๋Œ€์‹œ๋„ ๊ตฌํ˜„์„ ํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ, ์ €๋Š” ์ž๊ธฐ ์ƒํƒœ์—์„œ ์ž๊ธฐ ์ƒํƒœ๋กœ์˜ ์ „ํ™˜์„ ๋ง‰์•„๋†“์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋Œ€์‹  ์ž…๋ ฅ์—์„œ ์กฐ๊ฑด์„ ๋ณด๊ณ  ๋งŒ์กฑํ•˜๋ฉด ์ƒํƒœ๋ฅผ ์ „ํ™˜ํ•˜๋„๋ก ํ•ด๋†จ์ฃ .

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด isAvailableDash ๋ณ€์ˆ˜์— ๋‹ด์•„๋†“์€ ์กฐ๊ฑด๋“ค์ด ๋ฐ”๋กœ ๊ทธ ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. StateMachine์— ๋“ฑ๋กํ•˜๊ณ  ํ•œ ๋ฒˆ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ฃ . ์ด๋™ ์ƒํƒœ(MoveState)์™€ ๋Œ€์‹œ ์ƒํƒœ(DashState)๋ฅผ ๋งค๋„๋Ÿฝ๊ฒŒ ์˜ค๊ณ  ๊ฐ€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋Œ€์‹œ ์ค‘์—๋Š” ์ด๋™ ํ‚ค ์ž…๋ ฅ์ด ๋จนํžˆ๋ฉด ์•ˆ ๋˜๋Š”๋ฐ, ๋Œ€์‹œ ์ƒํƒœ์ผ ๋•Œ๋Š” ์ด๋™ ์ƒํƒœ ์ฝ”๋“œ๋“ค์ด ์‹คํ–‰๋˜์ง€ ์•Š์œผ๋‹ˆ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋™ ์ƒํƒœ๋ฅผ ๊ตฌํ˜„ํ•˜์˜€๊ณ , ๊ฑฐ๊ธฐ์— ๋Œ€์‹œ ์ƒํƒœ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ธฐ๋Šฅ ํ™•์žฅ์„ ํ•˜์˜€์ง€๋งŒ ์ด๋™ ์ƒํƒœ ์ฝ”๋“œ๋“ค์€ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ํ™•์žฅ์ด ์–ผ๋งˆ๋‚˜ ํŽธํ•ด์กŒ๋Š”์ง€ ์ฒด๊ฐํ•  ์ˆ˜ ์žˆ์—ˆ๋„ค์š”.

 

๊ธฐ์กด์˜ ๋Œ€์‹œ ์ƒํƒœ๋Š” ํ•˜๋“œ ์ฝ”๋”ฉ์ ์ธ ๋ถ€๋ถ„์ด ๋งŽ์•˜๊ณ , ๋ถ€์ž์—ฐ์Šค๋Ÿฝ๊ณ , ์ž…๋ ฅ ๋ฒ„ํผ๊ฐ€ ์—†์–ด ์‚ฌ์šฉ์ž๊ฐ€ N๋‹จ ๋Œ€์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค. ์ด์— ๋”ฐ๋ผ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜์˜€๊ณ , ๐Ÿ”—ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€ ๋งํฌ๋ฅผ ์—ฌ๊ธฐ์— ์ฒจ๋ถ€ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

 

์ด๊ฒƒ์œผ๋กœ ์ƒํƒœ ํŒจํ„ด์„ ์ด์šฉํ•˜์—ฌ ์ฝ”๋“œ ๊ธฐ๋Šฅ ํ™•์žฅ์„ ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ ์šฉํ•œ ๋‚ด์šฉ๋“ค์„ ๊ธ€๋กœ ์จ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ๊ธ€์—๋Š” ๋ฌด๊ธฐ ํƒˆ์žฅ์ฐฉ ๊ธฐ๋ณธ ํ‹€๊ณผ ๊ธฐ๋ณธ 3ํƒ€ ์ฝค๋ณด ๊ณต๊ฒฉ์ด ์ฃผ์ œ๊ฐ€ ๋˜๊ฒ ๋„ค์š”.

 

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

 

 

728x90
๋ฐ˜์‘ํ˜•