2022. 8. 18. 01:36ㆍProjects/Charon
1. 애니메이션 가져오기
캐릭터 모델이 휴머노이드가 아니라서 리타겟팅을 못 사용하는 경우
저는 에셋 스토어에서 캐릭터 모델을 샀고, Humonoid Animation Retargeting을 이용하여 Mixamo 사이트에 있는 애니메이션들을 재사용하려고 했습니다. 그런데, 구매한 에셋이 휴머노이드 모델이 아니더라구요. 그래서 Mixamo 사이트 애니메이션들을 적용하질 못 했습니다. 기존 휴머노이드 뼈대 구조와 구매한 에셋의 뼈대 구조가 달랐기에, 매칭이 안 되는 문제가 발생하더라구요.
그래서 그냥 3D 툴(Blender, Maya 같은)에 가져와서 뼈대를 모두 제거 후, Mixamo 사이트에 오토 리깅(Auto Rigging)이라는 기능을 이용하여 자동으로 휴머노이드 뼈대 구조를 등록해 주었습니다. 이전에 비공개 처리한 게시글에서 자세히 다루었던 내용들이지만, 이 글의 주요 내용은 아니니 생략할게요.
Idle, Move 애니메이션 옵션 설정
Idle 애니메이션과 Move 애니메이션 설정은 다음과 같이 해주었습니다. Root Transform Rotation, Root Transform Position(Y), Root Transform Position(XZ)을 설정하여 애니메이션에 의해 캐릭터의 트랜스폼이 영향을 받아 비정상적인 움직임을 유발하는 것을 방지하였습니다.
2. 블렌드 트리 만들기
애니메이션들을 관리하고 제어하기 위해 애니메이터(Animator)를 생성할 차례입니다.
기본적으로 캐릭터는 이동속도에 따라 이동 애니메이션 재생 속도가 유동적으로 달라져야 합니다. 롤에 비유해 보자면 슬로우 CC기에 걸리면 챔피언이 느리게 걷고, 이동속도가 빨라지게 하는 스킬이나 아이템을 사용하면 챔피언이 그만큼 빠르게 걷죠.
그렇기에 이동속도에 따라 애니메이션 재생 속도를 제어해주기 위해 블렌드 트리로 손쉽게 구현하고자 합니다. 어떤 기능을 하는지 구별하기 쉽게 저는 "Move"이름을 지어주었습니다.
그리고 이동속도에 따라 애니메이션 재생 속도에 곱해줄 Float 매개변수도 생성해 주었습니다.
이제 생성한 Move 블렌드 트리에서 다음과 같이 설정해주면, Velocity 매개변수 값에 따라 애니메이션 재생 속도가 달라지게 될 겁니다. 물론 블렌드 트리 내에서 애니메이션 각각 배속을 다르게 설정할 수도 있는데, 스크립트 상에서 이걸 제어가 가능한 지 모르고, 이렇게 진행해도 별 문제가 없기에 진행했습니다.
그리고 Move 블렌드 트리 내부로 들어와서, 다음과 같이 세팅해주었습니다.
Velocity 매개변수가 0이면 Idle 애니메이션을 재생하고, 1 이상이면 Move 애니메이션을 재생하도록 한 것이죠.
즉, Velocity 변수는 애니메이션 재생 속도와 애니메이션 간 전환, 이렇게 2가지 용도로 사용되고 있는 것입니다.
이렇게 하면, 블렌드 트리 구성은 완료된 것입니다. 캐릭터 오브젝트에 애니메이터(Animator) 컴포넌트를 추가하고, 위에서 만들었던 애니메이터 컨트롤러를 넣어줍니다.
3. 기능 구현하기
문제점이 있지만 일단 구현한 버전
이제 스크립트에서 애니메이터 컴포넌트 참조를 얻어, Velocity 애니메이터 매개변수를 변경하도록 하여 재생하도록 해봅시다. 일단은 발과 애니메이션 싱크를 생각하지 말고, 다음과 같이 필요한 부분만 작성했습니다.
public class PlayerController : MonoBehaviour
{
protected Animator animator;
protected const float DEFAULT_CONVERT_MOVESPEED = 3f;
protected const float DEFAULT_ANIMATION_PLAYSPEED = 0.9f;
...
protected void Move()
{
float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
float animationPlaySpeed = (direction == Vector3.zero) ? 0f : 1f:
LookAt();
rigidBody.velocity = direction * currentMoveSpeed + Vector3.up * rigidBody.velocity.y;
animator.SetFloat("Velocity", animationPlaySpeed);
}
그러면 다음과 같이 올바르게 작동할 겁니다.
하지만 위와 같이 한다면 여러 문제가 있다는 것을 눈으로 단번에 알아차리실 수 있을 겁니다. 하나씩 해결해보죠.
바닥에 그리드(Grid) 텍스쳐 입히기
눈치채신 분들도 계시겠지만, 바닥 텍스쳐가 그리드(Grid)로 되어 있습니다. 단순 무지색 텍스쳐로는 캐릭터가 발이 미끄러지듯 이동하는지 알아보기가 어렵기 때문이죠. 이 아이디어는 🔗이 영상을 보고 얻을 수 있었습니다. 단순히 애니메이션 싱크를 맞출 때만 잠깐 사용할 용도이기에 구글에서 찾은 🔗그리드 텍스쳐를 Unity로 가져왔습니다.
Unity로 가져온 후, 새 Material을 생성하여 Albedo 프로퍼티에 가져온 텍스쳐를 드래그 앤 드롭해주었습니다. 그렇게 하면 텍스쳐가 입혀진 Material이 됩니다.
그리고 바닥 오브젝트 Mesh Renderer의 Materials 프로퍼티에 위에서 만든 Material을 넣어주면 됩니다.
그리고 캐릭터가 그리드 무늬 한 칸에 딱 들어가도록 Material의 Tililing 프로퍼티 X와 Y값을 수정해주면 됩니다.
저 같은 경우에는 X = 2.5, Y = 3으로 하니 딱 캐릭터가 한 칸 안에 들어가더라구요.
이제 캐릭터를 움직여보며, 각 이동속도 때마다 적절한 재생 배율을 구하면 됩니다. 캐릭터가 미끄러지듯 움직이면 애니메이션 재생 속도를 좀 더 높여주면 되겠죠. 저 같은 경우에는 테스트 해보니, 다음과 같은 수치들을 얻을 수 있었네요.
이동속도에 따른 애니메이션 재생 속도 싱크 맞추기
사실 이 부분이 가장 고난이었습니다. 단순히 일정 구간마다 값을 나누어 분기 처리를 하자니 너무 하드 코딩인 것 같아 그러기는 싫었죠. 어떻게 하면 효율적으로, 유동적으로 처리가 가능할 지 고민을 했던 것 같네요.
이것 저것 테스트를 해보며 데이터를 얻어보니, 다음과 같은 식을 만들 수 있었습니다. 다음 공식처럼 적용하여 확인해보니 자연스럽게 잘 싱크가 맞더라구요.
이동속도에 따라 알맞은 애니메이션 재생 배율 값을 리턴해주는 함수를 작성했습니다.
protected const float DEFAULT_CONVERT_MOVESPEED = 3f;
protected const float DEFAULT_ANIMATION_PLAYSPEED = 0.9f;
protected float GetAnimationSyncWithMovement(float changedMoveSpeed)
{
if (direction == Vector3.zero)
{
return -DEFAULT_ANIMATION_PLAYSPEED;
}
// (바뀐 이동 속도 - 기본 이동속도) * 0.1f
return (changedMoveSpeed - DEFAULT_CONVERT_MOVESPEED) * 0.1f;
}
그리고 Move()에서 위 함수의 리턴값을 받아주기만 하면 됩니다.
protected void Move()
{
float currentMoveSpeed = player.MoveSpeed * CONVERT_UNIT_VALUE;
float animationPlaySpeed = DEFAULT_ANIMATION_PLAYSPEED +
GetAnimationSyncWithMovement(currentMoveSpeed);
LookAt();
rigidBody.velocity = direction * currentMoveSpeed + Vector3.up * rigidBody.velocity.y;
animator.SetFloat("Velocity", animationPlaySpeed);
}
이렇게 해주면 끝입니다. 코드가 간결하고 이쁘게 딱 떨어지니, 정말 기분이 좋았습니다.
4. 마무리 테스트
테스트 해보니 정말 기분이 좋네요. 별 다른 기능들 건드릴 필요없이, 이동속도만 바꿔주면 알아서 자동으로 애니메이션 재생 배율 싱크 조절까지 되니까요.
다음 글에서는 오늘 하루종일 해결한 버그들을 다뤄보려고 합니다. 경사로(Slope)에서 캐릭터가 이동할 때 문제가 있다는 것을 알게 되었고, 하루종일 수정했거든요... 이전 글에서 제가 문제가 있다고 한 코드가 있는데, 이 부분도 다음 글에서 수정하여 해결할 계획입니다.
rigidBody.velocity = direction * currentMoveSpeed + Vector3.up * rigidBody.velocity.y;
오늘도 글이 길어졌네요. 읽어주셔서 감사합니다!
'Projects > Charon' 카테고리의 다른 글
[Charon] #6. 휴머노이드 애니메이션 수정과 무기 탈장착 시스템 기반 만들기 (1) | 2022.10.05 |
---|---|
[Charon] #5. 상태 패턴(State Pattern) 도입하기 (4) | 2022.10.04 |
[Charon] #4. RigidBody를 이용한 3D 캐릭터 N단 대시(Dash) 구현하기 (4) | 2022.09.21 |
[Charon] #3. RigidBody를 사용한 3D 캐릭터의 경사로(Slope) 지형 이동 구현하기 (9) | 2022.08.30 |
[Charon] #1. New Input System을 적용하여 플레이어 이동 구현하기 (8) | 2022.08.17 |