Animation(애니메이션)
State 디자인 패턴
bool isFalling;
bool isJumping;
bool isSkillCasting;
bool isSkillChanneling;
if(isJumping)
{
}
else
{
if(isSkillChanneling && isFalling)
{
}
else
{
if(isSkillCasting)
{
}
}
}
이런식으로 bool타입의 상태 flag 변수를 여러개 사용하면 너무 복잡하다. if - else문이 너무 많아진다. 상태 flag 변수가 적은 갯수라면 괜찮지만 규모가 큰 게임일 수록 많이 필요하기 때문에 적합하지 않다.
해결 방법 👉 State 패턴 사용하기!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
float wait_run_ratio = 0;
[SerializeField]
float _speed = 10.0f;
Vector3 _destPos; // 목적지
void Start()
{
Managers.Input.MouseAction -= OnMouseClicked;
Managers.Input.MouseAction += OnMouseClicked;
}
public enum PlayerState
{
Die,
Moving,
Idle,
}
PlayerState _state = PlayerState.Idle;
void Update()
{
switch (_state)
{
case PlayerState.Die:
UpdateDie();
break;
case PlayerState.Moving:
UpdateMoving();
break;
case PlayerState.Idle:
UpdateIdle();
break;
}
}
void UpdateDie()
{
// 아무것도 못함
}
void UpdateMoving()
{
Vector3 dir = _destPos - transform.position;
if (dir.magnitude < 0.0001f) // 도착함
{
_state = PlayerState.Idle;
}
else // 도착 X
{
float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
transform.position = transform.position + dir.normalized * moveDist;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
transform.LookAt(_destPos);
}
// 애니메이션
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
Animator anim = GetComponent<Animator>();
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
void UpdateIdle()
{
// 애니메이션
wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
Animator anim = GetComponent<Animator>();
anim.SetFloat("wait_run_ratio", wait_run_ratio);
anim.Play("WAIT_RUN");
}
void OnMouseClicked(Define.MouseEvent evt)
{
if (_state == PlayerState.Die)
return;
if (evt != Define.MouseEvent.Click)
return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
{
_destPos = hit.point;
_state = PlayerState.Moving;
}
}
}
if (_state == PlayerState.Die)
//...
switch (_state)
{
case PlayerState.Die:
UpdateDie();
break;
case PlayerState.Moving:
UpdateMoving();
break;
case PlayerState.Idle:
UpdateIdle();
break;
}
enum으로 상태들을 정의하고, if-else문 혹은 switch문 사용하기!
대신 bool 상태 플래그 변수와는 다르게 동시에 두가지 상태를 가질 순 없음. 👉 이와 같은 특성 때문에 여러 동작이 꼬일 일이 없음. 동작 별로 분리해서 만들 수 있다는 장점.
isJumping && isAttacking // 👉 가능
_state == PlayerState.Jump && _state == PlayerState.Attack // 👉 불가능
State Machine
State 패턴과 관련이 깊다. 어떤 상태이냐에 따라 재생시킬 애니메이션이 다르다. State Machine 에서 하나의 State 는 하나의 Animation 클립에 대응한다.
다른 상태로 바뀌는 전이를 화살표로 이어주기만 하면 된다.
화살표에서 Condition 에서 그 전이 조건을 설정할 수 있다.
Parameter 값을 조건식에 이용한다.
Transition
✔️ Has Exit Time
그래프를 보면 RUN 상태(애니메이션)에서 WAIT 상태로 블렌딩 되듯이 전이가 일어나는 것을 볼 수 있다. 위 파란색 범위를 조절하여 설정할 수 있다. 이 범위가 좁을 수록 블렌딩이 적고 RUN 끝나자마자 바로 WAIT 이 재생되서 부자연스러울 수 있음
Has Exit Time 애니메이션 재생 종료 후 다음 전이로 이동한다. (Condition 조건에 따라 전이 되는게 아님)
체크 👉 종료 시점이 존재하게 되는 것.
- Settings 의 Fixed Duration이 체크되어 있다면 Exit Time 값이 “시간”으로 해석된다. Exit Time 값이 0.7 이고 Has Exit TIme이 체크되어 있다면 절대적인 “0.7초”의 시간 후에 다음 전이가 일어난다.
- Settings 의 Fixed Duration이 체크되어 있지 않다면 Exit Time 값이 “% 비율”로 해석된다. Exit Time 값이 0.7 이고 Has Exit TIme이 체크되어 있다면 “해당 애니메이션의 전체 재생 길이의 70 %”까지만 재생한 후에 다음 전이가 일어난다.
체크 해제 👉 애니메이션이 다 재생 되더라도 전이가 일어나지 않는다.
- 전이가 일어나려면 Contidion 조건을 만족해야 한다. 따라서 파라미터 값으로 Condition 조건을 통해 전이가 즉각 일어나게 하려면 Has Exit Time 을 체크 해제해주어야 한다.
블렌딩 Has Exit Time 이걸 체크하면 Exit Time 비율만큼 애니메이션 재생 후 다음 상태로 전이된다. 체크 해제 하면 전이 조건을 만족해야만 즉시 전이가 이루어진다. 전이 조건이 만족될 때 즉각 재생되게 하고 싶으면 이게 체크 해제되어야 한다.
“RUN 👉 JUMP”, “RUN 👉 WAIT” 두 전이 모두 다 Has Exit Time이 체크되어 있다는 가정하에, 현재 전이 순서(Transitions)이 위와 같기 때문에 “RUN 👉 JUMP”이 먼저 실행되게 된다. 만약 “RUN 👉 JUMP” 전이는 Has Exit Time이 체크되어 있지 않다면 “RUN 👉 WAIT”가 실행될 것이다.
여담으로 애니메이션 자체가 계속 반복 실행되는 경우는 해당 애니메이션 상태가 loop Time 속성이 체크되어 있어서 그렇다.
Transition Duration이 바로 이 블렌딩 되는 파란색 범위 구간이다. 이 지연시간이 길면 길수록 자연스럽고 짧으면 짧을수록 애니메이션이 즉각즉각 바뀐다.
파라미터 사용
Animator anim = GetComponent<Animator>();
anim.SetFloat("speed", _speed);
이렇게 스크립트에서 움직일 때에 애니메이션 파라미터 값을 설정할 수가 있다. 이 파라미터 값의 변화에 따라 전이 Condition 조건시기 만족하여 전이가 일어나게 된다.
여담으로 실무에선 기본적인 움직임 동작은 State Machine 에서, 스킬 애니메이션은 어마어마하게 다양할 수 있기 때문에 따로 블렌딩 트리로 묶어 작성하는 식으로 작업 한다고 한다.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 3-7-1. UI : 기초, Rect Transform (앵커) (0) | 2023.08.23 |
---|---|
Part 3-6-3. 애니메이션 : KeyFrame, Event (0) | 2023.08.22 |
Part 3-6-1. 애니메이션 : 기초, 블렌딩 (0) | 2023.08.22 |
Part 3-5-3. 카메라 : 카메라가 벽에 막힐시, 벽 앞으로 카메라 이동시키기 (1) | 2023.08.21 |
Part 3-5-2. 카메라 : 클릭한 곳으로 플레이어 이동하기 (0) | 2023.08.21 |