Unity 3D/유니티 짱
유니티짱 08) 유니티짱 무기 장착 및 전투 모션 추가
ybbro
2023. 5. 18. 17:14
그 동안 유니티짱 고유 애니메이션이 아닌 다른 애니메이션을 적용하기 위해 많은 시도를 하였습니다.
전투에 사용할 다른 애니메이션을 받아와서 적용해보니 머리카락이 계속 문제가 되었습니다.
팔이 머리카락을 뚫고 나오는 현상이 발생하여 이를 해결하기 위해 유니티짱_다이나믹 모델로 변경하여 머리카락 위치를 수정하였습니다.
우선 무기를 하나 골라 모델링을 받아왔고
지난 포스트에서 했던 유니티짱 카툰 셰이더를 적용하여 카툰 그래픽처럼 보이게 조정하였습니다.
그리고 자식 오브젝트로 라이트를 주어 플레이어가 보기 좋게끔 표시해줍니다.
유저 조작을 통해 무기를 장착할 수 있도록 해보겠습니다.
무기를 장착할 위치를 오른손의 자식 오브젝트로 넣어주고
기존 유니티짱 스크립트에 아이템과 상호작용이 가능하게끔 하는 메서드를 추가하였습니다.
(아래 코드 블럭 void Interaction() 참조)
그리고 전투용 모션이 없는 관계로 유니티 에셋 스토어에서 대검 전투 애니메이션을 다운받아 서브 머신 스테이트를 사용하여 비무장과 대검을 든 전투 모션을 따로 구분하였습니다.
그 외 잡다한 버그도 여럿 수정하였습니다.
using UnityEngine;
namespace UnityChan
{
// 필요 컴포넌트들
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(CapsuleCollider))]
[RequireComponent(typeof(Rigidbody))]
public class UnityChanControlScriptWithRgidBody_Custom : MonoBehaviour
{
[HideInInspector] public bool isFarFromFloor;
// 현재 회전의 기준이 되는 값 (ThirdPersonCamera_Custom 에서 변경)
[HideInInspector] public float currentDirection = 0f;
// 애니메이션의 현재 상태
public AnimatorStateInfo currentBaseState;
// 애니메이터 각 상태에 대한 참조
// 비무장 상태
// 대기
[HideInInspector] public int idleState = Animator.StringToHash("Base Layer.Unarmed.Idle");
// 걷기, 달리기 구분
[HideInInspector] public int walkState = Animator.StringToHash("Base Layer.Unarmed.Walk");
[HideInInspector] public int runState = Animator.StringToHash("Base Layer.Unarmed.Run");
// 점프 애니메이션을 상황에 맞게 쓰기 위해
// 점프 후 최고점까지 체공, 공중에 뜬 상태, 착지 3단계로 분할
[HideInInspector] public int jumpState = Animator.StringToHash("Base Layer.Unarmed.Jump");
[HideInInspector] public int fallState = Animator.StringToHash("Base Layer.Unarmed.Falling");
[HideInInspector] public int landState = Animator.StringToHash("Base Layer.Unarmed.Landing");
// 휴식 모션 구분
public readonly int restState01 = Animator.StringToHash("Base Layer.Unarmed.Rest01");
public readonly int restState02 = Animator.StringToHash("Base Layer.Unarmed.Rest02");
public readonly int restState03 = Animator.StringToHash("Base Layer.Unarmed.Rest03");
// 슬라이딩 추가
[HideInInspector] public int slideState = Animator.StringToHash("Base Layer.Unarmed.Slide");
// 공격
public readonly int[] attackState = new int[4]
{ Animator.StringToHash("Base Layer.Two_Handed.Attack1"),
Animator.StringToHash("Base Layer.Two_Handed.Attack2"),
Animator.StringToHash("Base Layer.Two_Handed.Attack3"),
Animator.StringToHash("Base Layer.Two_Handed.DashAttack") };
// 무기를 장착할 위치 -> 코드가 길어져서 public으로 받아오도록
public Transform WeaponPosition;
// 스크립트로 조작할 유니티짱에 포함된 컴포넌트
private CapsuleCollider col;
private Rigidbody rb;
private Animator anim;
// 레이캐스트에 쓸 빛을 발사할 발 아주 살짝 위의 포지션
Transform RayPosition;
// CapsuleCollider에 설정된 콜라이더 Height, Center의 초기 값
float orgColHight;
Vector3 orgVectColCenter;
// 슬라이딩 중 콜라이더 Height, Center 값
const float slideColHeight = 0.25f;
readonly Vector3 slideColCenter = Vector3.up * (slideColHeight / 2f);
// 바닥에 붙어있음을 판정하는 높이
const float onFloorHeight = 0.01f;
// 착지 모션을 시작하는 바닥으로부터의 높이
const float landHeight = 0.8f;
// 낙하 카메라 무빙을 시작할 바닥으로부터의 높이
const float fallFarStandard = 5f;
// 아이템 탐색 범위
const float itemDetectRadius = 2f;
// 전체 애니메이션 재생 속도 (게임의 템포에 맞게 변화)
float animSpeed = 1.5f;
// 걸을 때의 속도
float walkSpeed = 5f;
// 달릴 때의 속도
float runSpeed = 10f;
// 점프, 슬라이드 초기 속력, 감속의 정도를 나타내는 변수
float initSpeed, speedRate;
// 착지, 슬라이딩 중 감속 비율
float[] decreaseRate = new float[2] { 5f, 1.5f };
// 대시 공격 중 감속 비율
const float dashAttack_decreaseRate = 0.03f;
// 점프, 슬라이드의 초기 방향
Vector3 initDirection;
// 매 Update()에서 최종 계산된 속도
Vector3 velocity;
// 중력가속도 9.81m/s^2
const float gravity_coefficient = 9.81f;
float vel_Y_Current = 0f;
// 자유 낙하할 때 최대 속력(m/s) : 선형 공기저항의 영향을 받을 때 53m/s에 근사
const float vel_Y_Limit = 53f;
// idle - rest 애니메이션 간 카운터
float restCount = 0f;
const float restDelay = 6f;
// 키보드 입력값
float h;
float v;
bool jump;
bool slide;
// 중력 적용 여부 (잠깐 중력을 꺼줄 때 씀)
bool isGravityEnable = true;
// 발판을 밟고 있을 때
bool isOnFloor = true;
// 공격 키 입력 후 잠시동안 공격을 못하는 상태라면 Attack 값을 false로 바꿔주기 위한 카운트
// 예를 들어 공격 키를 입력하고 공격이 발동하지 않은 상태에서 피격당해서 경직/다운이 걸리면
// 경직/다운 모션 이후 그 공격을 이어 하려하기 때문에 입력을 취소해 줄 필요가 있다
float attackClearCount = 0f;
const float attackClearDelay = 1f;
// !!!!! 키 설정에서 바꿀 수 있게끔 나중에 수정
// 달리기 키 (이동 키와 함께 누르면 달리기)
KeyCode run_key = KeyCode.LeftShift;
// 슬라이딩 키 (이동 중에만 사용 가능)
KeyCode Slide_key = KeyCode.Mouse1;
// 상호작용 키
KeyCode Interaction_Key = KeyCode.E;
KeyCode Attack_Key = KeyCode.Mouse0;
LayerMask itemLayer;
private void Awake()
{
// 유니티짱을 참조하는 스크립트가 많으니 접근하기 쉽게 게임매니저의 static 변수에 할당
GameManager.unityChan = this;
}
void Start()
{
// 필요한 컴포넌트 가져오기
anim = GetComponent<Animator>();
col = GetComponent<CapsuleCollider>();
rb = GetComponent<Rigidbody>();
RayPosition = transform.Find("RayPosition");
// 콜라이더 height, center 기본값을 받아오기
orgColHight = col.height;
orgVectColCenter = col.center;
// 애니메이션 재생 속도를 처음 1번만 설정하게끔
// 중간에 변경하는 부분이 없기에 Start로 이동하여 성능 개선
anim.speed = animSpeed;
itemLayer = 1 << LayerMask.NameToLayer("Items");
// !!!!! 나중에 저장 데이터가 생기면 무기 상태에 따라 애니메이션 선택하게끔 바꿔야 함
ChangeToUnarmed();
}
private void Update()
{
// 유니티짱 발 밑 오브젝트 탐색
FloorDetect();
// 상호작용 키 입력 여부
InterAction();
// 현재 애니메이션 상태
currentBaseState = anim.GetCurrentAnimatorStateInfo(0);
// 점프 중, 슬라이드 중이 아닐 때만
if (currentBaseState.fullPathHash != jumpState
&& currentBaseState.fullPathHash != fallState
&& currentBaseState.fullPathHash != landState
&& currentBaseState.fullPathHash != slideState)
{
// 공격 키 입력 처리
AttackEntered();
// 대시 공격 중에는 살짝 미끄러지면서 정지
if (currentBaseState.fullPathHash == attackState[3])
{
velocity = Vector3.Lerp(velocity, Vector3.zero, dashAttack_decreaseRate);
anim.SetBool("Move", false);
}
// 공격 3연타 모션 중에는 움직이지 않도록
else if (currentBaseState.fullPathHash == attackState[0] ||
currentBaseState.fullPathHash == attackState[1] ||
currentBaseState.fullPathHash == attackState[2])
{
// 방향키 입력에 따라 회전
MoveInput();
velocity = Vector3.zero;
anim.SetBool("Move", false);
}
// 공격 모션이 아닐때만, 이동 조작이 가능하게끔
else
{
// 1. 이동 및 회전 -> 이동 중에만 슬라이딩이 가능
MoveEntered();
// 2. 점프
JumpEntered();
}
}
// 현재 재생 중인 애니메이션에 따라 처리할 조건
ByAnimState();
}
private void FixedUpdate()
{
// 속도에 따라 위치 이동 구문 수정
// 포지션을 직접 움직이면 다른 오브젝트를 뚫고 지나가기에 리지드바디에 속도를 주도록 개선
// 속도에 영향을 미치는 요건이 여럿이기에 이를 적용한 최종 속도를 부여
// 중력이 적용하고, 바닥에서 떨어졌다면 -> y축 방향으로 중력 가속도 적용
if (isGravityEnable && !isOnFloor)
{
// 낙하 최대 속도가 되지 않았다면
if (vel_Y_Current < vel_Y_Limit)
// 중력가속도에 맞게 속도 증가
vel_Y_Current += gravity_coefficient;
// y축 아래 방향으로 속도를 부여
velocity -= Vector3.up * vel_Y_Current;
}
rb.velocity = velocity;
}
// 캐릭터와 발 아래 땅과의 거리가 얼마인가에 따라 처리할 내용
void FloorDetect()
{
// 캐릭터 발 약간 위에서 아래 방향으로 빛 발사
Ray ray = new Ray(RayPosition.position, Vector3.down);
RaycastHit hitInfo = new RaycastHit();
if (Physics.Raycast(ray, out hitInfo))
{
float distance = hitInfo.distance;
if (distance < onFloorHeight)
{
isOnFloor = true;
// 몬스터 콜라이더 위로 타고 올라가지 않게 Y축 포지션 이동 제한 추가
rb.constraints = RigidbodyConstraints.FreezePositionY
| RigidbodyConstraints.FreezeRotation;
// y축 방향 속력 초기화
vel_Y_Current = 0f;
}
// 일정 거리 이상이라면 중력가속도를 제대로 적용받게끔
else
{
isOnFloor = false;
// 중력을 받아서 아래로 떨어지려면 Y축 포지션 이동 제한 해제
rb.constraints = RigidbodyConstraints.FreezeRotation;
}
// 발 밑의 오브젝트와의 거리가 일정 미만이라면, 착지 모션
if (distance < landHeight)
{
anim.SetBool("Land", true);
anim.SetBool("Fall", false);
}
// 발 밑의 오브젝트와의 거리가 일정 이상이라면
else
{
// 낙하 모션
anim.SetBool("Land", false);
anim.SetBool("Fall", true);
// 낙하 카메라 무브를 시작할 높이 판정
if (distance > fallFarStandard)
isFarFromFloor = true;
else
isFarFromFloor = false;
}
}
// 레이를 쏴서 맞는 물체가 없을 경우
else
{
isOnFloor = false;
// 중력을 받아서 아래로 떨어지려면 Y축 포지션 이동 제한 해제
rb.constraints = RigidbodyConstraints.FreezeRotation;
// 낙하 모션
anim.SetBool("Land", false);
anim.SetBool("Fall", true);
}
}
// 콜라이더 사이즈 초기화
void resetCollider()
{
col.height = orgColHight;
col.center = orgVectColCenter;
}
void AttackEntered()
{
// 공격 키가 눌러졌다면
if(Input.GetKeyDown(Attack_Key))
{
// 공격 애니메이션 재생 예약
// 연달아 공격할 경우 다음 공격 모션 예약
anim.SetBool("Attack",true);
}
// 공격 키가 눌러진 이후, 공격 모션에 들어가지 않고
if(anim.GetBool("Attack"))
{
// 설정된 시간이 지났다면, 카운트 초기화 및 공격 취소
attackClearCount += Time.deltaTime;
if (attackClearCount > attackClearDelay)
{
AttackStartEvent();
}
}
}
// 이동이 가능한 상태에서 이동 키가 입력되었을 때
void MoveEntered()
{
MoveInput();
// 키 입력에 따라 걷기/달리기 애니메이션
anim.SetBool("Running", Input.GetKey(run_key));
// 키 입력에 따라 이동 bool 변경
if (Mathf.Abs(h) > 0.1f || Mathf.Abs(v) > 0.1f)
anim.SetBool("Move", true);
else
{
anim.SetBool("Move", false);
// 이동 애니메이션 재생 중이 아닐때는 움직이지 않도록
velocity = Vector3.zero;
}
}
void MoveInput()
{
// 이동 키 입력 값을 받아옴
h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");
// 키 입력에 대응하여 회전
PlayerRotate();
}
// 카메라가 보는 각도와 입력값에 맞게 유니티짱 회전
void PlayerRotate()
{
if (v > 0.1f)
{
if (h > 0.1f)
transform.eulerAngles = Vector3.up *
(currentDirection + Mathf.Lerp(0f, 45f, h));
else if (h < -0.1f)
transform.eulerAngles = Vector3.up *
(currentDirection - Mathf.Lerp(0f, 45f, -h));
else
transform.eulerAngles = Vector3.up * currentDirection;
}
else if (v < -0.1f)
{
if (h > 0.1f)
transform.eulerAngles = Vector3.up *
(currentDirection + 180f - Mathf.Lerp(0f, 45f, h));
else if (h < -0.1f)
transform.eulerAngles = Vector3.up *
(currentDirection + 180f + Mathf.Lerp(0f, 45f, -h));
else
transform.eulerAngles = Vector3.up * (currentDirection + 180f);
}
else if (h > 0.1f)
transform.eulerAngles = Vector3.up * (currentDirection + 90f);
else if (h < -0.1f)
transform.eulerAngles = Vector3.up * (currentDirection - 90f);
}
// 점프할 수 있는 상태에서 점프 키가 입력되었을 때, 점프 시작과 동시에 1번만 처리할 구문
void JumpEntered()
{
// 점프가 잘 안먹히던데 고쳐보자
// 원인 : Fixed Update에 있던 키 입력 인식을 Update()로 빼서 해결
// 애니메이션 상태가 locoState일 때만 점프 가능 이라는 구문 삭제
// (제자리 점프, 후진 중 점프 등 다양한 경우에 대응하기 위함)
// 점프 애니메이션 재생 시작
jump = Input.GetButton("Jump");
if (jump)
{
anim.SetBool("Jump", true);
if (anim.GetBool("Running"))
initSpeed = runSpeed;
else
initSpeed = walkSpeed;
// 현재 속도 방향을 정규화 (크기는 1, 방향에 따른 비율만)
initDirection = rb.velocity.normalized;
}
}
void InterAction()
{
// 플레이어로부터 탐색 범위 반경 내 모든 아이템 오브젝트들의 콜라이더들을 찾아서 배열로 반환
Collider[] items = Physics.OverlapSphere(transform.position, itemDetectRadius, itemLayer);
// 탐색한 아이템이 하나라도 있다면
if (!items.Length.Equals(0))
{
// 플레이어로부터 가장 가까운 거리의 아이템 콜라이더 인덱스
int minIndex = 0;
// 가장 가까운 아이템과의 거리
float minDist = float.MaxValue;
// 가장 가까운 아이템 갱신
for (int i = 0; i < items.Length; i++)
{
float dist = Vector3.Distance(transform.position, items[i].transform.position);
if (dist < minDist)
{
minIndex = i;
minDist = dist;
}
}
// 상호작용 키 입력이 있다면
if (Input.GetKey(Interaction_Key))
{
// 바닥에 떨어진 아이템을 하이라이트 처리해주던 빛을 비활성화
items[minIndex].transform.Find("ItemLight").gameObject.SetActive(false);
// 유니티짱 손 안의 무기 장착 위치를 부모로
items[minIndex].transform.parent = WeaponPosition;
// 로컬 위치, 각도를 초기화하여 손에 장착한 것으로 보이게끔
items[minIndex].transform.localPosition = Vector3.zero;
items[minIndex].transform.localEulerAngles = Vector3.right * 180f;
// 양손검 애니메이션으로 전환 !!!!! 무기 종류가 여럿 생기면 여기도 분기로 처리
ChangeToTwoHanded();
}
}
}
// 현재 애니메이션 상태에 따라 수행할 내용
void ByAnimState()
{
// 가만히 있는 상태 (idle)
if (currentBaseState.fullPathHash == idleState)
Idle();
// 휴식 모션 재생 중
else if (currentBaseState.fullPathHash == restState01 ||
currentBaseState.fullPathHash == restState02 ||
currentBaseState.fullPathHash == restState03)
Rest();
// 걷기 / 달리기 애니메이션 재생 중
else if (currentBaseState.fullPathHash == walkState ||
currentBaseState.fullPathHash == runState)
Moving();
// 점프 애니메이션 재생 중
else if (currentBaseState.fullPathHash == jumpState)
JumpingUp();
// 낙하 애니메이션 재생 중
else if (currentBaseState.fullPathHash == fallState)
Falling();
// 착지 애니메이션 재생 중
else if (currentBaseState.fullPathHash == landState)
Landing();
// 슬라이딩 애니메이션 재생 중
else if (currentBaseState.fullPathHash == slideState)
Sliding();
}
// Idle 애니메이션 재생 중
void Idle()
{
resetCollider();
// 휴식을 기존 : idle 상태에서 스페이스(점프) 키
// -> 변경 : idle 상태에서 조작 없이 일정 시간이 지난다면 랜덤 휴식 모션 재생
// 무기를 착용하지 않은 상태에서만 동작
if (anim.GetInteger("Weapon").Equals(0))
{
restCount += Time.deltaTime;
if (restCount > restDelay)
{
anim.SetBool("Rest", true);
anim.SetInteger("RestRandom", Random.Range(1, 4));
restCount = 0f;
}
// 키 입력이 있었다면 휴식 상태로 전환하는 카운터 초기화
if (Input.anyKey)
restCount = 0f;
}
}
// 휴식 애니메이션 재생 중
void Rest()
{
restCount += Time.deltaTime;
// 카운트 시간이 지났거나 아무 키나 입력되었다면, 휴식 상태 종료
if (restCount > restDelay || Input.anyKey)
{
anim.SetBool("Rest", false);
restCount = 0f;
}
}
// 걷기/달리기 애니메이션 재생 중
void Moving()
{
// 1. 이동
// 방향 벡터 (이동은 기본적으로 캐릭터 좌표계의 z축 양의 방향(앞 방향)으로만 이동하게끔 수정)
velocity = transform.forward;
// 달리기 키 입력 여부에 따라 달리기/걷기 속도
if (anim.GetBool("Running"))
velocity *= runSpeed;
else
velocity *= walkSpeed;
// 2. 슬라이딩 (이동 중에만 가능)
// 슬라이딩 키 입력 인식
slide = Input.GetKey(Slide_key);
// 슬라이딩 키를 눌렀다면, 해당 방향으로 슬라이딩
if (slide)
{
anim.SetBool("Slide", true);
// 달리기/걷기 도중에 사용 시 슬라이딩 초기 속력이 달라진다
if (anim.GetBool("Running"))
initSpeed = runSpeed * 1.5f;
else
initSpeed = walkSpeed * 1.5f;
// 현재 속도 방향을 정규화 (크기는 1, 방향에 따른 비율만)
initDirection = rb.velocity.normalized;
// 슬라이딩 속도 비율 초기화
speedRate = 0f;
}
}
//점프, 낙하 중 콜라이더 변경
void ColliderChange_Jump()
{
// animator에서 점프 높이에 따른 값(0 ~ 1 사이 값)을 받아와서 콜라이더 높이, 중심을 갱신
float jumpHeight = anim.GetFloat("JumpHeight");
col.height = orgColHight - jumpHeight;
float adjCenterY = orgVectColCenter.y + jumpHeight;
col.center = Vector3.up * adjCenterY;
}
// 점프 시작 -> 최고점 도착까지
void JumpingUp()
{
// 점프 중 상태에 따라 콜라이더 변경
ColliderChange_Jump();
// 최고점에 도달하기 전까지 중력을 꺼 주어서 제대로 점프하는 느낌이 들게
isGravityEnable = false;
// 점프 애니메이션 재생 이후에 비활성화 처리
anim.SetBool("Jump", false);
}
// 떨어지는 모든 상황에 대해
void Falling()
{
// 낙하 중 상태에 따라 콜라이더 변경
ColliderChange_Jump();
// 낙하 시작부터 중력이 다시 적용되도록
isGravityEnable = true;
// 착지 감속에 쓰기 위해 미리 초기화
speedRate = 0f;
}
// 착지
void Landing()
{
// 콜라이더 리셋
resetCollider();
// 땅에 닿으면 잠시 감속
speedRate += decreaseRate[0] * Time.deltaTime;
velocity = initDirection * Mathf.Lerp(initSpeed, 0f, speedRate);
}
// 슬라이딩 중
void Sliding()
{
// 슬라이딩 애니메이션 재생이 끝나면 자동으로 idle로 가게
anim.SetBool("Slide", false);
// 콜라이더 높이, 중심을 슬라이딩 중의 캐릭터와 맞게끔 조정
col.height = slideColHeight;
col.center = slideColCenter;
// 이동 속도는 슬라이딩 초기엔 빠르나 느려지도록
speedRate += decreaseRate[1] * Time.deltaTime;
velocity = initDirection * Mathf.Lerp(initSpeed, 0f, speedRate);
}
// 슬라이딩 애니메이션이 끝날 때 idle 애니메이션으로 돌리기 위한 이벤트
public void SlideEndEvent()
{
anim.SetTrigger("SlideEnd");
}
//애니메이터의 Attack Bool 값을 false로 바꿔주고 공격 취소 카운트 초기화
public void AttackStartEvent()
{
attackClearCount = 0f;
anim.SetBool("Attack", false);
}
// 양손 대형 무기를 집었을 때
public void ChangeToTwoHanded()
{
// 애니메이션 변경
// 무기 변경 -> 서브 스테이션 머신을 바꿔주게끔
anim.SetInteger("Weapon", 1);
// 대기
idleState = Animator.StringToHash("Base Layer.Two_Handed.Idle");
// 걷기, 달리기
walkState = Animator.StringToHash("Base Layer.Two_Handed.Walk");
runState = Animator.StringToHash("Base Layer.Two_Handed.Run");
// 점프 애니메이션을 상황에 맞게 쓰기 위해
// 점프 후 최고점까지 체공, 공중에 뜬 상태, 착지 3단계로 분할
jumpState = Animator.StringToHash("Base Layer.Two_Handed.Jump");
fallState = Animator.StringToHash("Base Layer.Two_Handed.Falling");
landState = Animator.StringToHash("Base Layer.Two_Handed.Landing");
// 휴식은 없음
// 슬라이딩
slideState = Animator.StringToHash("Base Layer.Two_Handed.Slide");
}
public void ChangeToUnarmed()
{
// 애니메이션 변경
// 무기 변경 -> 서브 스테이션 머신을 바꿔주게끔
anim.SetInteger("Weapon", 0);
// 대기
idleState = Animator.StringToHash("Base Layer.Unarmed.Idle");
// 걷기, 달리기
walkState = Animator.StringToHash("Base Layer.Unarmed.Walk");
runState = Animator.StringToHash("Base Layer.Unarmed.Run");
// 점프 애니메이션을 상황에 맞게 쓰기 위해
// 점프 후 최고점까지 체공, 공중에 뜬 상태, 착지 3단계로 분할
jumpState = Animator.StringToHash("Base Layer.Unarmed.Jump");
fallState = Animator.StringToHash("Base Layer.Unarmed.Falling");
landState = Animator.StringToHash("Base Layer.Unarmed.Landing");
// 휴식은 현재 비무장 상태에서만 존재하므로 바꿀 필요 없음
// 슬라이딩
slideState = Animator.StringToHash("Base Layer.Unarmed.Slide");
}
}
}