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

 

 

1. ์„ ์ž…๋ ฅ(Input Buffer)์ด ํ•„์š”ํ•œ ์ด์œ 

 

๊ฒŒ์ž„์—์„œ ๋กœ์ง์˜ ์‹คํ–‰ ๋‹จ์œ„๋Š” ํ”„๋ ˆ์ž„(Frame)์ž…๋‹ˆ๋‹ค. ๊ฒŒ์ž„์„ ํ•˜๋‹ค๋ณด๋ฉด FPS(Frames Per Second)๋ž€ ์ˆ˜์น˜๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, 1์ดˆ์— ๋ช‡ ํ”„๋ ˆ์ž„์ด ์‹คํ–‰๋˜๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์ข‹์€ ๊ณ ์‚ฌ์–‘ ์žฅ๋น„์ธ ๊ฒฝ์šฐ์—๋Š” 200 ํ”„๋ ˆ์ž„ ์ด์ƒ์˜ ์ˆ˜์น˜๋ฅผ ๋ณด์—ฌ์ฃผ๊ณค ํ•ฉ๋‹ˆ๋‹ค.

 

์šฐ๋ฆฌ ๊ฒŒ์ž„์—์„œ ๋Œ€์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด 30fps์ด ์กฐ๊ธˆ ๋„˜์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์„ ์ž…๋ ฅ์„ ํ†ตํ•œ ๋ฒ„ํผ๊ฐ€ ์—†๋‹ค๋ฉด, ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ € ์งง์€ ์‹œ๊ฐ„ ๋‚ด์— ํƒ€์ด๋ฐ์„ ๋งž์ถ”์–ด ์ž…๋ ฅ์„ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋ฏธ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๋Œ€์‹œ๋ฅผ ํ•˜๋Š”๋ฐ ๊ทธ ์ •๋„์˜ ์ง‘์ค‘๋ ฅ์„ ์“ฐ๊ฒŒ ๋˜๋ฉด ํ”ผ๋กœ๋„๊ฐ€ ๋นจ๋ฆฌ ์Œ“์ด๊ฒŒ ๋˜๊ฒ ์ฃ .

 

์ฒ ๊ถŒ๊ณผ ๊ฐ™์€ ๊ฒฉํˆฌ ๊ฒŒ์ž„์€ ์ปค๋งจ๋“œ(Command)๋ผ๋Š” ๊ฒŒ ์กด์žฌํ•˜๋Š”๋ฐ, ์ž…๋ ฅ ๋ฒ„ํผ๊ฐ€ ์กด์žฌํ•˜๊ธฐ์— ํŽธ์•ˆํ•˜๊ฒŒ ์ฝค๋ณด๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ ๊ฒŒ์ž„์—๋„ ์ด๋Ÿฌํ•œ ๊ฒŒ ํ•„์š”ํ•˜์—ฌ ๋„ฃ์–ด๋ณผ ์ƒ๊ฐ์„ ํ•˜๊ฒŒ ๋˜์—ˆ์ฃ .

 

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด์— ๊ตฌํ˜„ํ–ˆ๋˜ ๋Œ€์‹œ๋Š” ์„ ์ž…๋ ฅ์ด ์—†์—ˆ๊ธฐ์— ๋Œ€์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ์ผ์ • ํ”„๋ ˆ์ž„ ์‚ฌ์ด์—์„œ๋งŒ ์žฌ์ž…๋ ฅ์„ ๋ฐ›๊ฒŒ

ํ•˜์˜€๋Š”๋ฐ, ๋ฒ„ํผ๊ฐ€ ์—†์œผ๋‹ˆ ๋ฐ”๋กœ ๋ชจ์…˜์ด ์žฌ์‹คํ–‰์ด ๋˜์–ด ๋š๋š ๋Š๊ธฐ๋Š” ๋“ฏํ•œ ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์šด ์›€์ง์ž„๋„ ๋ณด์—ฌ์คฌ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ๋„ ํ•ด๊ฒฐํ•  ๊ฒ๋‹ˆ๋‹ค.

 

๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” N๋‹จ ๋Œ€์‹œ

 

 

 


2. DashState ํด๋ž˜์Šค ์ˆ˜์ •ํ•˜๊ธฐ

 

์ €๋Š” ๋Œ€์‹œ ์„ ์ž…๋ ฅ์„ ํ(Queue)๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ์„ ํ–ˆ์„ ๋‹น์‹œ์— ๋Œ€ํ•œ ๋ฐฉํ–ฅ ์ •๋ณด๋งŒ ํ•„์š”ํ•˜๊ธฐ์— Vector3๋งŒ ์ €์žฅํ•˜๋Š” Queue๋ฅผ ์„ ์–ธํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

Queue<Vector3> inputDirectionBuffer = new Queue<Vector3>();

 

๊ทธ๋ฆฌ๊ณ  ์ „๋ฐ˜์ ์œผ๋กœ ์ˆ˜์ •์„ ๋งŽ์ด ํ•˜์˜€์ฃ .

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

namespace CharacterController
{
    public class DashState : BaseState
    {
        public int CurrentDashCount { get; set; } = 0;
        public bool CanAddInputBuffer { get; set; }     // ๋ฒ„ํผ ์ž…๋ ฅ์ด ๊ฐ€๋Šฅํ•œ๊ฐ€?
        public bool CanDashAttack { get; set; }
        public bool IsDash { get; set; }
        public int Hash_DashTrigger { get; private set; }
        public int Hash_IsDashBool { get; private set; }
        public int Hash_DashPlaySpeedFloat { get; private set; }
        public Queue<Vector3> inputDirectionBuffer { get; private set; }

        public const float DEFAULT_ANIMATION_SPEED = 2f;
        public readonly float dashPower;
        public readonly float dashTetanyTime;
        public readonly float dashCooltime;

        public DashState(float dashPower, float dashTetanyTime, float dashCoolTime)
        {
            inputDirectionBuffer = new Queue<Vector3>();
            this.dashPower = dashPower;
            this.dashTetanyTime = dashTetanyTime;
            this.dashCooltime = dashCoolTime;
            Hash_DashTrigger = Animator.StringToHash("Dash");
            Hash_IsDashBool = Animator.StringToHash("IsDashing");
            Hash_DashPlaySpeedFloat = Animator.StringToHash("DashPlaySpeed"); 
        }

        public override void OnEnterState()
        {
            IsDash = true;
            CanAddInputBuffer = false;
            CanDashAttack = false;
            Player.Instance.animator.applyRootMotion = false;
            Dash();
        }

        private void Dash()
        {
            Vector3 dashDirection = inputDirectionBuffer.Dequeue();
            dashDirection = (dashDirection == Vector3.zero) ? Player.Instance.Controller.transform.forward : dashDirection;

            Player.Instance.animator.SetBool(Hash_IsDashBool, true);
            Player.Instance.animator.SetTrigger(Hash_DashTrigger);
            Player.Instance.Controller.LookAt(new Vector3(dashDirection.x, 0f, dashDirection.z));

            float dashAnimationPlaySpeed = DEFAULT_ANIMATION_SPEED + (Player.Instance.MoveSpeed * MoveState.CONVERT_UNIT_VALUE - MoveState.DEFAULT_CONVERT_MOVESPEED) * 0.1f;
            Player.Instance.animator.SetFloat(Hash_DashPlaySpeedFloat, dashAnimationPlaySpeed);
            Player.Instance.rigidBody.velocity = dashDirection * (Player.Instance.MoveSpeed * MoveState.CONVERT_UNIT_VALUE) * dashPower;
        }

        public override void OnUpdateState()
        {

        }

        public override void OnFixedUpdateState()
        {

        }

        public override void OnExitState()
        {
            Player.Instance.rigidBody.velocity = Vector3.zero;
            Player.Instance.animator.applyRootMotion = true;
            Player.Instance.animator.SetBool(Hash_IsDashBool, false);
        }
    }
}

 

Queue์—์„œ ํ•˜๋‚˜๋ฅผ ๊ฐ€์ ธ์™€์„œ(Dequeue) ํ•ด๋‹น ๋ฐฉํ–ฅ์œผ๋กœ ๋Œ€์‹œ๋ฅผ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด์ฃ . ํ‚ค ์ž…๋ ฅ ์ฒ˜๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

// PlayerController

public void OnDashInput(InputAction.CallbackContext context)
{
    // ๋Œ€์‹œ ํ‚ค์ธ ์ŠคํŽ˜์ด์Šค ๋ฐ”๋ฅผ ๋ˆŒ๋ €๋‹ค ๋–ผ์—ˆ์„ ๊ฒฝ์šฐ์— ์‹คํ–‰๋˜๋„๋ก ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
    if (context.performed && context.interaction is PressInteraction)
    {
        // ๋Œ€์‹œ ์ž…๋ ฅ์„ ๋ง‰์•„์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์žˆ์„ ๊ฒฝ์šฐ return;
   
        
        if (dashState.CurrentDashCount >= player.DashCount)
            return;

        // ๋Œ€์‹œ ์ค‘์— ๋ฒ„ํผ์— ์ž…๋ ฅ ๊ฐ€๋Šฅํ•œ ํ”„๋ ˆ์ž„์ผ ๋•Œ ์ž…๋ ฅ ๋ฐ›์„ ๊ฒฝ์šฐ
        if (dashState.CanAddInputBuffer && isGrounded)
        {
            dashState.CurrentDashCount++;
            dashState.inputDirectionBuffer.Enqueue(calculatedDirection);
            return;
        }

        // Idle ์ƒํƒœ์—์„œ ๋Œ€์‹œ๋ฅผ ์ž…๋ ฅ๋ฐ›์„ ๊ฒฝ์šฐ
        if (!dashState.IsDash && isGrounded)
        {
            dashState.CurrentDashCount++;
            dashState.inputDirectionBuffer.Enqueue(calculatedDirection);
            player.stateMachine.ChangeState(StateName.DASH);
        }
    }
}

 

Idle ์ƒํƒœ์—์„œ ๋Œ€์‹œ ํ‚ค๋ฅผ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ์—๋Š” Enqueue()๋ฅผ ํ•˜๊ณ , ๋Œ€์‹œ ์ƒํƒœ๋กœ ์ „ํ™˜์„ ํ•ด์ค๋‹ˆ๋‹ค.

๋Œ€์‹œ ์ƒํƒœ๋กœ ์ „ํ™˜์„ ํ–ˆ์œผ๋‹ˆ Dequeue๋ฅผ ํ†ตํ•ด ๋ฐ”๋กœ ๋ฝ‘์•„์™€์„œ ๋Œ€์‹œ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๊ฒ ์ง€์š”.

 

๋Œ€์‹œ ์ค‘์ด๊ณ , ๋ฒ„ํผ์— ์ž…๋ ฅ์ด ๊ฐ€๋Šฅํ•œ ํ”„๋ ˆ์ž„ ๊ตฌ๊ฐ„์ผ ๊ฒฝ์šฐ์—๋Š” ๋Œ€์‹œ ์นด์šดํŠธ ์ฆ๊ฐ€์™€ Enqueue()๋งŒ ํ•ด์ค๋‹ˆ๋‹ค.

๋Œ€์‹œ ์นด์šดํŠธ๊ฐ€ ๋‹ค ์ฐผ๋‹ค๋ฉด returnํ•˜์—ฌ ๋” ์ด์ƒ ๋ฐ›์ง€ ๋ง์•„์•ผ ํ•˜๊ฒ ์ฃ .

 

 

๊ทธ๋ ‡๋‹ค๋ฉด ๋Œ€์‹œ ๋ฒ„ํผ ์ž…๋ ฅ์ด ๊ฐ€๋Šฅํ•œ ๊ตฌ๊ฐ„๊ณผ ๋Œ€์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚ฌ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋Š” ๋ˆ„๊ฐ€ ํ•ด์ค„๊นŒ์š”?

์• ๋‹ˆ๋ฉ”์ด์…˜ ์ด๋ฒคํŠธ ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

Dash ์• ๋‹ˆ๋ฉ”์ด์…˜

 

๋นจ๊ฐ„์ƒ‰์œผ๋กœ ๋™๊ทธ๋ผ๋ฏธ ์นœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ด๋ฒคํŠธ๊ฐ€ ๋Œ€์‹œ ๋ฒ„ํผ ์ž…๋ ฅ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์ค๋‹ˆ๋‹ค.

dashState = Player.Instance.stateMachine.GetState(StateName.DASH) as DashState;

public void OnCanDashAttack()
{
    dashState.CanDashAttack = true;
}

 

ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋™๊ทธ๋ผ๋ฏธ ์นœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ด๋ฒคํŠธ๊ฐ€ ๋Œ€์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚ฌ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์ค๋‹ˆ๋‹ค.

private Coroutine dashCoolTimeCoroutine;


public void OnFinishedDash()
{
    if (!dashAttackState.IsDashAttack)
    {
        dashState.CanDashAttack = false;

        // ๋ฒ„ํผ์— ์„ ์ž…๋ ฅ์œผ๋กœ ๋„ฃ์—ˆ๋˜ ๊ฒŒ ๋‚จ์•„์žˆ๋‹ค๋ฉด, ๋‹ค์‹œ Dash๋กœ ์ƒํƒœ ์ „ํ™˜
        if (dashState.inputDirectionBuffer.Count > 0)
        {
            Player.Instance.stateMachine.ChangeState(StateName.DASH);
            return;
        }

        // ์—†๋‹ค๋ฉด, ๋ฒ„ํผ ์ž…๋ ฅ์„ ์ข…๋ฃŒํ•˜๊ณ  ๋Œ€์‹œ ์ฟจํƒ€์ž„ ์‹œ์ž‘
        dashState.CanAddInputBuffer = false;
        dashState.OnExitState();

        if (dashCoolTimeCoroutine != null)
            StopCoroutine(dashCoolTimeCoroutine);
        dashCoolTimeCoroutine = 
        StartCoroutine(CheckDashReInputLimitTime(dashState.dashCooltime));
     }
}


private IEnumerator CheckDashReInputLimitTime(float limitTime)
{
    float timer = 0f;

    while (true)
    {
        timer += Time.deltaTime;
            
        if(timer > limitTime)
        {
            dashState.IsDash = false;
            dashState.CurrentDashCount = 0;
            Player.Instance.stateMachine.ChangeState(StateName.MOVE);
            break;
        }
        yield return null;
    }
}

 

์ด๋ ‡๊ฒŒ ํ•ด์ฃผ๋ฉด, ๋Œ€์‹œ ์„ ์ž…๋ ฅ ๋ฒ„ํผ ๋งŒ๋“ค๊ธฐ๊ฐ€ ๋๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋กœ ํ•œ ๋ฒˆ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

 

 

 


3. ๊ฒฐ๊ณผ

 

๋ฒ„ํผ์— ์ž…๋ ฅ์ด ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ธฐ ์œ„ํ•ด, ์• ๋‹ˆ๋ฉ”์ด์…˜ ์†๋„๋ฅผ ๋Šฆ์ถ”์–ด ์ดฌ์˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

 

Idle ์ƒํƒœ์—์„œ Dash๋กœ ์ „ํ™˜๋  ๋•Œ๋Š” "๋Œ€์‹œ ์ฒ˜์Œ ๋ฐœ๋™"์ด๋ž€ ๋กœ๊ทธ๊ฐ€ ์ฐํž™๋‹ˆ๋‹ค.

Dash ์ค‘์— ์„ ์ž…๋ ฅ ๋ฒ„ํผ ์ž…๋ ฅ์„ ๋ฐ›๊ฒŒ ๋˜๋ฉด, "๋Œ€์‹œ ๋ฒ„ํผ์— ์ถ”๊ฐ€"๋ž€ ๋กœ๊ทธ๊ฐ€ ์ฐํž™๋‹ˆ๋‹ค.

 

์œ„์˜ GIF์˜ ์˜ˆ์‹œ๋Š” ์ œ๊ฐ€ (์™ผ์ชฝ, ์˜ค๋ฅธ์ชฝ), (์™ผ์ชฝ, ์œ„) ์ž…๋ ฅ์„ ์ฐจ๋ก€๋Œ€๋กœ ์ค€ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค. ๋ฒ„ํผ์— ๋‹ค์Œ ๋Œ€์‹œ์˜ ๋ฐฉํ–ฅ ์ •๋ณด๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ๊ณ , ํ˜„์žฌ ๋Œ€์‹œ๊ฐ€ ๋๋‚˜๊ณ  ๋ฐ”๋กœ ๋ฒ„ํผ์— ์žˆ๋˜ ๋ฐฉํ–ฅ์œผ๋กœ ๋Œ€์‹œ๋ฅผ ํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

728x90
๋ฐ˜์‘ํ˜•