멈추지 않고 끈질기게
[Unity] 애니메이션 추가하기 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.
※ 개발 중인 유니티 3D 포트폴리오의 소스 코드를 포함하고 있습니다.
※ 해당 포스팅은 하기 출처들을 참조하였습니다.
- https://docs.unity3d.com/kr/2018.4/Manual/class-Animator.html
1. 애니메이터(Animator)
유니티에서 오브젝트에 애니메이션을 추가하고 싶다면 우선 Animator 컴포넌트를 추가하고, Animator Controller를 등록해야 합니다.
Animator에 애니메이션 클립을 등록하고 변환 조건을 설정하여 상황에 맞는 애니메이션을 실행하도록 할 수 있습니다.
다음은 기타 속성들에 대한 내용입니다.
Avatar
오브젝트의 아바타 정보를 등록하는 속성입니다. 휴머노이드 애니메이션을 사용하는 3D 오브젝트의 경우 뼈대(스켈레톤) 매핑 정보를 담고 있는 아바타를 등록해주어야 제대로 동작합니다. 에셋을 사용하는 경우 보통 캐릭터의 아바타까지 포함하므로 해당 아바타를 등록해주면 됩니다.
Apply Root Motion
애니메이터가 해당 오브젝트의 트랜스폼 정보(포지션, 로테이션)를 수정할 수 있는지 여부입니다.
체크하면 애니메이션에 의해 오브젝트가 앞뒤로 이동하거나 회전하거나 할 수 있습니다.
다만 트랜스폼 정보를 제어하는 코드와 충돌할 경우 예상치 못한 이슈가 발생할 수 있으므로 주의해야 합니다.
(자세한 내용은 해당 포스팅 참조: https://sam0308.tistory.com/68)
Update Mode
애니메이터의 업데이트 주기를 정하는 속성으로, 하기 3가지 옵션 중에 선택합니다.
- Normal : Update() 주기에 맞추어 업데이트됩니다. Time.timeScale의 영향을 받습니다.
- Animate Physics : FixedUpdate() 주기에 맞추어 업데이트됩니다. Time.timeScale의 영향을 받습니다.
Rigidbody를 이용한 물리 상호작용을 하는 오브젝트의 경우 해당 옵션이 적합합니다.
- Unscaled Time : Update() 주기에 맞추어 업데이트되며, Time.timeScale을 무시합니다.
특수 효과를 위해 timeScale을 수정한 상태에서도 애니메이션이 정상 속도로 실행됩니다.
Culling Mode
애니메이션의 컬링 기준을 정하는 속성으로, 하기 3가지 옵션 중에 선택합니다.
- Always Animate : 항상 애니메이션을 실행하고 컬링 대상에서 아예 제외합니다.
- Cull Update Transforms : 오브젝트가 카메라에 보이지 않는다면 리타겟, IK 및 트랜스폼 정보를 컬링(비활성화)합니다.
- Cull Completely : 오브젝트가 카메라에 보이지 않는다면 완전히 컬링(비활성화)합니다.
2. 애니메이션 설정
Animator 윈도우와 노드
애니메이터 컨트롤러 및 애니메이션 클립은 프로젝트 창에서 우클릭 > Create > Animator Controller / Animation 을 선택하여 생성할 수 있습니다. 애니메이터에 컨트롤러까지 등록한 후, 해당 오브젝트를 선택한 상태로 애니메이터 창을 열어보면 Entry, Any State, Exit의 3가지 노드를 확인할 수 있습니다.
Entry는 기본 상태를 나타내며, 해당 오브젝트의 디폴트 애니메이션이 여기에 연결됩니다. 애니메이터에 애니메이션 클립을 처음으로 추가하면 자동으로 기본 애니메이션으로 설정되며, 해당 노드와 연결됩니다. 기본 애니메이션을 다른 클립으로 바꾸려면 우클릭 후 'Set Default Animation'을 선택하면 됩니다.
디폴트 애니메이션에서 전환되는 경우 외에는 Any State 노드에 연결하여 조건 만족 시 실행되도록 할 수 있습니다. 실행을 마친 애니메이션이 종료되도록 하려면 Exit 노드로 연결하면 됩니다. 이 경우, 실행 종료 후 디폴트 애니메이션으로 돌아갑니다.
Animation 등록하기
애니메이션은 프로젝트 창에서 우클릭 후 Animation을 선택하여 새로 생성할 수 있으며, 애니메이터 창에 끌어다 놓아 새 애니메이션 노드를 생성할 수 있습니다. 혹은 애니메이터 창에서 우클릭 -> Create State -> Empty를 선택하여 빈 노드를 생성한 후, Motion에서 애니메이션을 선택하여 등록할 수 있습니다. 에셋을 사용하는 경우 에셋에 포함된 애니메이터를 그대로 사용할 수도 있지만, 일부 애니메이션만 필요하다면 애니메이터를 새롭게 생성한 뒤 필요한 애니메이션만 등록하여 단순화할 수 있습니다.
애니메이션 편집, 혹은 사용하는 애니메이션의 동작 확인은 Animation 윈도우에서 할 수 있습니다. 하이아라키 창에서 애니메이터를 포함한 오브젝트를 선택할 경우, Property 설정 창과 타임라인을 확인할 수 있습니다.
Add Property를 선택하여 애니메이션으로 조정할 값을 선택할 수 있습니다. 기본적으로 트랜스폼 정보를 포함하며, 해당 오브젝트가 포함한 컴포넌트들 및 하위 오브젝트의 컴포넌트들도 선택 가능합니다. 프로퍼티 추가 시 기본적으로 0초 및 1초에 키프레임이 생기며, Add keyframe을 선택하여 추가할 수 있습니다. 각 키프레임마다 프로퍼티 값을 설정할 수 있으며, 키프레임 사이의 값은 자동으로 설정됩니다. 프리미어 프로같은 영상 편집 프로그램에서 애니메이션을 추가하는 방식과 비슷합니다.
또한 프로퍼티는 여러개 중첩할 수 있습니다. 3D 게임에서 플레이어블 캐릭터의 경우 팔, 다리 등 각 신체 부위를 별도의 메시로 만들어 하위 오브젝트로 포함시킨 후, 애니메이션에서 각 부위를 조정함으로써 다양한 움직임을 표현합니다. 다음 영상은 큐브의 회전 값과 메시 렌더러의 컬러 값, 하위 오브젝트 구체의 y축 값 3가지를 조정하는 애니메이션 예시입니다.
Transition과 Parameters
애니메이션의 전환은 노드를 우클릭한 후 Make Transition을 선택하여 화살표로 형태로 지정할 수 있습니다. 화살표의 방향을 따라가며 실행되므로 순서에도 유의해야 합니다. 특정 애니메이션이 조건 만족 시 1회 실행 후 종료되게 하려면 Any State에서 해당 애니메이션 방향으로, 해당 애니메이션에서 Exit 방향으로 화살표가 연결되어야 합니다.
전환 조건은 Parameters 탭을 선택한 후 + 버튼으로 추가할 수 있습니다. Int, Float, Trigger, Bool 4가지 타입이 있으며, 화살표 선택 후 Conditions 창에서 추가하여 전환 조건을 설정할 수 있습니다. Int의 경우 비교 값과 일치(Equals), 불일치(NotEqual), 더 큼(Greater), 작음(Less) 4가지 조건 중 하나를 선택할 수 있으며, Float는 부동소수점 특성상 Greater과 Less 2가지 옵션만 존재합니다. Bool은 True와 False 두가지 옵션이 있으며, Trigger는 별도의 하위 옵션이 없습니다(1회성 애니메이션의 전환 조건으로 적합).
물론 두가지 이상의 파라미터를 이용하여 복합적인 조건을 거는 것도 가능하며, 이 경우 해당 파라미터들이 모두 조건을 만족해야 전환이 이루어집니다.
또한 한 애니메이션의 전환에 Transition을 2개 이상 걸 수도 있습니다. 위 예시의 경우 Idle에서 다시 한번 Make Transition 선택 후 Walk로 이으면 Transition이 중복으로 걸리며, 화살표의 모양도 중첩된 모양으로 변합니다.
중복된 Transition을 선택하면 인스펙터 창에서 Transitions가 2개 나오는 것을 확인할 수 있으며, 각 트랜지션마다 Parameters를 따로 설정할 수 있습니다. 예를 들어 Idle 애니메이션에서 이동속도가 0보다 크거나 특정 버튼 입력 중일 경우 걷기 애니메이션으로 전환하고 싶다면, Float 파라미터를 갖는 트랜지션과 Bool 파라미터를 갖는 트랜지션을 중첩하여 구현할 수 있습니다.
3. 스크립트
애니메이터의 제어는 스크립트에서 Animator 컴포넌트에 접근하여 실행할 수 있습니다. 다음은 파라미터 설정 함수 및 기타 유용한 속성 및 함수들입니다.
Animator animator;
void Awake()
{
animator = GetComponent<Animator>();
}
void Test()
{
// 애니메이터 관련 함수
// Float형 파라미터 설정
animator.SetFloat("moveSpeed", 5.0f);
// Int형 파라미터 설정
animator.SetInteger("count", 3);
// Bool형 파라미터 설정
animator.SetBool("isWalking", true);
// Trigger 파라미터 설정
animator.SetTrigger("doShoot");
// 애니메이션 속도 조절
animator.speed = 0.5f;
// 애니메이터의 파라미터들
AnimatorControllerParameter[] parameters = animator.parameters;
// 현재 실행중인 애니메이션의 정보
AnimatorStateInfo asInfo = animator.GetCurrentAnimatorStateInfo(0);
bool isNameIdle = asInfo.IsName("Idle");
bool isLooping = asInfo.loop;
}
SetInteger(string name, int value) / SetFloat(string name, float value) / SetBool(string name, bool value)
name 이름의 파라미터의 값을 value로 설정합니다. 파라미터 이름과 각 파라미터 타입에 맞는 값을 매개변수로 받으며, 파라미터의 이름과 타입이 함수와 일치해야만 제대로 동작합니다.
SetTrigger(string name)
name 이름의 Trigger 파라미터를 작동시킵니다. 트리거 파라미터는 호출하는 순간 바로 1회 true로 취급하므로 1회성 애니메이션의 전환에 적합합니다.
speed
애니메이터의 애니메이션 재생 속도값입니다. float 타입으로, 1.0이 통상 재생속도이며 직접 수정할 수 있습니다.
parameters
애니메이터가 가지고 있는 모든 파라미터들의 집합입니다. AnimatorControllerParameter 타입의 배열이며, 각 원소(파라미터)에 접근하여 이름(name), 타입(type)등의 정보를 확인할 수 있습니다.
GetCurrentAnimatorStateInfo(int layerIndex)
현재 애니메이터의 상태 정보를 AnimatorStateInfo 타입으로 반환합니다. 레이어 인덱스를 매개변수로 받으며, 따로 레이어를 나누지 않았다면 0이 현재 레이어입니다. AnimatorStateInfo는 현재 실행중인 애니메이션의 이름을 비교할 수 있는 함수(IsName())와 현재 실행중인 애니메이션의 반복 여부를 확인할 수 있는 속성(loop)을 포함하고 있습니다.
다음은 개발중인 3D 포트폴리오 프로젝트에서 가져온 애니메이션 제어 관련 코드입니다.
// 애니메이터 변수 관리용
enum AnimatorVar
{
isMoving,
isJumping,
doSlide,
isGrabbing,
onKnockBack
}
void Awake()
{
rigid = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
}
void PlayerMove()
{
// 일시정지 상태나 넉백중이라면 패스
if (GameManager.Instance.IsPaused || inKnockBack)
return;
// 걷기 애니메이션 설정
animator.SetBool(AnimatorVar.isMoving.ToString(), inputVec.magnitude > 0);
// 이동 방향으로 바라보기
transform.LookAt(transform.position + moveVec);
// 일반 이동
rigid.MovePosition(rigid.position + moveVec);
}
방향 입력 시 값을 저장하는 inputVec의 크기가 0보다 클 경우 isMoving 파라미터를 true로, 아닐 경우 false로 전환하게 했습니다. 파라미터 이름을 직접 입력하는 대신, 파라미터 이름들을 열거형으로 선언해두고 ToString()으로 전달하는 방식을 사용했습니다. 다음 영상은 상기 코드의 실제 적용 모습입니다.
public class LobbyModel : MonoBehaviour
{
Animator animator;
[SerializeField] float animInterval;
string[] animTriggers;
string idle = "Idle";
void Awake()
{
animator = GetComponent<Animator>();
}
void Start()
{
// 애니메이터의 파라미터들 이름 저장해두기
AnimatorControllerParameter[] parameters = animator.parameters;
animTriggers = new string[parameters.Length];
for (int i = 0; i < parameters.Length; ++i)
{
animTriggers[i] = parameters[i].name;
}
}
public void RandomAnim()
{
// 현재 애니메이션이 Idle일때만 실행
if (animator.GetCurrentAnimatorStateInfo(0).IsName(idle))
{
int idx = Random.Range(0, animTriggers.Length);
animator.SetTrigger(animTriggers[idx]);
}
}
}
해당 클래스는 로비 씬에 배치한 캐릭터 모델에 적용한 스크립트로, 해당 캐릭터 클릭 시 RandomAnim() 함수를 호출하여 랜덤 애니메이션을 재생하도록 했습니다. animator.parameters로 파라미터 배열을 가져온 뒤 동일한 크기의 string 타입 배열에 이름을 저장해두고, RandomAnim() 실행 시 랜덤 인덱스를 뽑아 SetTrigger를 실행하도록 했습니다. 랜덤 애니메이션 실행 함수의 경우 animator.GetCurrentAnimatorStateInfo(0).IsName()을 통해 현재 상태가 Idle인지 체크하고, 다른 애니메이션 실행중이 아닐 때만 실행되도록 작성했습니다. 다음 영상은 상기 코드의 실제 적용 모습입니다.
'Unity' 카테고리의 다른 글
[Unity] Addressable 기능 (0) | 2023.07.27 |
---|---|
[Unity][포트폴리오] Rigidbody.AddForce()가 제대로 동작하지 않는 이슈 (0) | 2023.07.03 |
[Unity][UI] Content Size Fitter (0) | 2023.04.13 |
[Unity][UI] 레이아웃 그룹(Layout Group) (0) | 2023.03.31 |
[Unity][번외] Transform.SetPositionAndRotation (0) | 2023.02.15 |