1. ์ ์ ๋ ฅ(Input Buffer)์ด ํ์ํ ์ด์
๊ฒ์์์ ๋ก์ง์ ์คํ ๋จ์๋ ํ๋ ์(Frame)์ ๋๋ค. ๊ฒ์์ ํ๋ค๋ณด๋ฉด FPS(Frames Per Second)๋ ์์น๋ฅผ ๋ณผ ์ ์๋๋ฐ, 1์ด์ ๋ช ํ๋ ์์ด ์คํ๋๋์ง๋ฅผ ๋ํ๋ ๋๋ค. ์ข์ ๊ณ ์ฌ์ ์ฅ๋น์ธ ๊ฒฝ์ฐ์๋ 200 ํ๋ ์ ์ด์์ ์์น๋ฅผ ๋ณด์ฌ์ฃผ๊ณค ํฉ๋๋ค.
์ฐ๋ฆฌ ๊ฒ์์์ ๋์ ์ ๋๋ฉ์ด์ ์ด 30fps์ด ์กฐ๊ธ ๋์ต๋๋ค. ์ด๊ฒ์ ์ ์ ๋ ฅ์ ํตํ ๋ฒํผ๊ฐ ์๋ค๋ฉด, ํ๋ ์ด์ด๊ฐ ์ ์งง์ ์๊ฐ ๋ด์ ํ์ด๋ฐ์ ๋ง์ถ์ด ์ ๋ ฅ์ ํด์ผ ํ๋ค๋ ์๋ฏธ๊ฐ ๋ฉ๋๋ค. ๋์๋ฅผ ํ๋๋ฐ ๊ทธ ์ ๋์ ์ง์ค๋ ฅ์ ์ฐ๊ฒ ๋๋ฉด ํผ๋ก๋๊ฐ ๋นจ๋ฆฌ ์์ด๊ฒ ๋๊ฒ ์ฃ .
์ฒ ๊ถ๊ณผ ๊ฐ์ ๊ฒฉํฌ ๊ฒ์์ ์ปค๋งจ๋(Command)๋ผ๋ ๊ฒ ์กด์ฌํ๋๋ฐ, ์ ๋ ฅ ๋ฒํผ๊ฐ ์กด์ฌํ๊ธฐ์ ํธ์ํ๊ฒ ์ฝค๋ณด๋ฅผ ์ ๋ ฅํ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ ๊ฒ์์๋ ์ด๋ฌํ ๊ฒ ํ์ํ์ฌ ๋ฃ์ด๋ณผ ์๊ฐ์ ํ๊ฒ ๋์์ฃ .
๊ทธ๋ฆฌ๊ณ ๊ธฐ์กด์ ๊ตฌํํ๋ ๋์๋ ์ ์ ๋ ฅ์ด ์์๊ธฐ์ ๋์ ์ ๋๋ฉ์ด์ ์ ์ผ์ ํ๋ ์ ์ฌ์ด์์๋ง ์ฌ์ ๋ ฅ์ ๋ฐ๊ฒ
ํ์๋๋ฐ, ๋ฒํผ๊ฐ ์์ผ๋ ๋ฐ๋ก ๋ชจ์ ์ด ์ฌ์คํ์ด ๋์ด ๋๋ ๋๊ธฐ๋ ๋ฏํ ๋ถ์์ฐ์ค๋ฌ์ด ์์ง์๋ ๋ณด์ฌ์คฌ์์ต๋๋ค.
์ด๊ฒ๋ ํด๊ฒฐํ ๊ฒ๋๋ค.
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ํ์ฌ ๋ ์ด์ ๋ฐ์ง ๋ง์์ผ ํ๊ฒ ์ฃ .
๊ทธ๋ ๋ค๋ฉด ๋์ ๋ฒํผ ์ ๋ ฅ์ด ๊ฐ๋ฅํ ๊ตฌ๊ฐ๊ณผ ๋์ ์ ๋๋ฉ์ด์ ์ด ๋๋ฌ์ ๋์ ์ฒ๋ฆฌ๋ ๋๊ฐ ํด์ค๊น์?
์ ๋๋ฉ์ด์ ์ด๋ฒคํธ ํจ์๋ฅผ ํตํด ์ฒ๋ฆฌํ์์ต๋๋ค.
๋นจ๊ฐ์์ผ๋ก ๋๊ทธ๋ผ๋ฏธ ์น ์ ๋๋ฉ์ด์ ์ด๋ฒคํธ๊ฐ ๋์ ๋ฒํผ ์ ๋ ฅ์ ๊ฐ๋ฅํ๊ฒ ํด์ฃผ๋ ํจ์๋ฅผ ํธ์ถํด์ค๋๋ค.
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์ ์์๋ ์ ๊ฐ (์ผ์ชฝ, ์ค๋ฅธ์ชฝ), (์ผ์ชฝ, ์) ์ ๋ ฅ์ ์ฐจ๋ก๋๋ก ์ค ๋ชจ์ต์ ๋๋ค. ๋ฒํผ์ ๋ค์ ๋์์ ๋ฐฉํฅ ์ ๋ณด๊ฐ ์ ์ฅ๋์ด ์๊ณ , ํ์ฌ ๋์๊ฐ ๋๋๊ณ ๋ฐ๋ก ๋ฒํผ์ ์๋ ๋ฐฉํฅ์ผ๋ก ๋์๋ฅผ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.