애니메이션 블루프린트의 기반을 이루는 '애님 인스턴스'클래스를 C++로 제작하고
애님 인스턴스 클래스의 데이터를 기반으로 캐릭터가 애니메이션을 체계적으로 재생할 수 있도록
스테이트 머신에 기반한 애니메이션 시스템 제작 방법
<1>. 애니메이션 블루프린트 = '애님 그래프' + '애님 인스턴스'
- 애님 인스턴스 : 스켈레탈 메시를 소유하는 폰의 정보를 받아 애님 그래프가 참조할 데이터 제공.
- 애님 그래프 : 애님 인스턴스의 변수 값에 따라 변화하는 애니메이션 시스템 설계하는 공간.
※ C++로 애님 인스턴스를 제작 → 폰의 현재 속력을 애님 인스턴스에 저장 → 이 값에 따라 '애님 그래프'에서는 IDLE 혹은 RUN 애니메이션 재생하도록 해보자
AnimInstance를 부모로 하는 ABAnimInstance라는 c++ 클래스 생성.
//ABAnimInstance.h
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
...
public:
UABAnimInstance(); //생성자 선언. 생성자는 보통 캐릭터의 속성값을 설정해주는 역할인듯.
private:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Pawn, Meta=(AllowPrivateAccess=true))
//애님 그래프에서 사용할 수 있도록, 블루프린트에서 접근할 수 있도록
float CurrentPawnSpeed;
};
//ABAnimInstance.cpp
UABAnimInstance::UABAnimInstance()
{
CurrentPawnSpeed = 0.0f;
}
애니메이션 블루프린트 → 클래스 세팅 →부모 클래스 정보 ABAnimInstance로 변경 → CurrentPawnSpeed 변수 사용가능.
CurrentPawnSpeed, 'float > float'노드, blend(bool로 포즈를 블렌딩 하는 기능)으로 'CurrentPawnSpeed'를 바탕으로 모션을 재생하는 노드를 만들 수 있다.
<2>. 폰과 데이터 연동
실제 게임에서 폰의 속도에 따라 애니메이션을 재생하려면 '프레임마다 폰의 속력 = 애님 인스턴스의 CurrentPawnSpeed'여야 한다 → 그렇게 하려면 애님 인스턴스의 Tick에서 폰의 속도 정보를 가져온 후 CurrentPawnSpeed에 업데이트한다.
//ABAnimInstance.h
class ARENABATTLE_API UABAnimInstance : public UAnimINstance
{
...
public:
virtual void NativeUpdateAnimation(float DeltaSeconds) override;
//틱마다 호출되는 가상 함수. 애님 인스턴스 클래스 제공
...
}
//ABAnimInstance.cpp
void UABAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
auto Pawn = TryGetPawnOwner(); //폰에 접근해 폰의 속력값을 얻어온다.
if(::IsValid(Pawn)) //폰 객체가 유효한지? 앞에서 제거됐는지?
{
CurrentPawnSpeed = Pawn->GetVelocity().Size();
}
}
※스테이트 : 캐릭터가 반복해서 재생해야 할 애니메이션 동작
<3>. 점프 기능의 구현
폰이 점프를 하게 하는 것은 쉽다. 하지만, 그냥 점프를 구현하면 점프할 때마다 달리기 모션이 재생된다.
→ 폰이 점프 중인지에 대해 IsFalling() 함수를 통해 알 수 있다.
→ IsFalling()을 통해 얻은 폰의 점프 상황을 보관하기 위해 애님 인스턴스에 IsInAir라는 Boolean 속성 선언.
//ABAnimInstance.h
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
private:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Pawn, Meta = AllowPrivateAccess = true)
bool IsInAir;
};
//ABAnimInstance.cpp
UABAnimInstance::UABAnimInstance()
{
IsInAir = false;
}
void UABAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
if(::IsValid(Pawn))
{
...
auto Character = Cast<ACharacter>(Pawn); //Pawn을 Character로 변환
if (Character)
{
IsInAir = Character->GetMovementComponent()->IsFalling();
}
}
}
※스테이트 편집창에서 Ground, Jump 스테이트를 만들고 양방향 트랜지션을 추가해 준다.
Ground → Jump : IsInAir
Jump → Ground : IsInAir - Not
<4>. 애니메이션 리타겟
인간형 캐릭터의 경우, 스켈레톤의 구성이 달라도 애니메이션을 공유할 수 있도록 '애니메이션 리타겟' 기능이 있다.
<5>. 점프의 구현
점프 애니메이션을 좀 더 사실적으로 표현하기 위해 JumpStart, JumpLoop, JumpEnd로 구분 짓는다.
→2024/6/18 기준 MM_Jump, MM_Fall_Loop, MM_Land라는 이름으로 되어있다.
Ground → JumpStart : IsInAir
JumpStart →JumpLoop : Time Remaining(0.1 / <)
JumpLoop →JumpEnd : IsInAir - Not
JumpEnd →Ground : Time Remaining(0.1 / <)
!! 지금 날짜 기준으로 예제를 실행하면 Land 할 때 땅으로 꺼지는 현상이 발생한다.
이는 MM_Land가 Additive 타입으로 돼있기 때문이다. MM_Land의 에셋 디테일의 '애디티브 세팅'에서 애디티브 애님 타입을 No Additive로 바꿔주면 정상적으로 실행된다.
→Time Remaining 노드를 사용하는 과정들은 트랜지션 노드에서 제공하는 'Automatic Rule Based on Sequence Player in State' 옵션으로도 할 수 있다. (둘이 같이하면 오류가 나는 것 같다)
'게임 개발 > 이득우의 언리얼 C++' 카테고리의 다른 글
[이득우의 언리얼 C++ 게임개발의 정석] #9. 충돌 설정과 대미지 전달 (0) | 2024.06.22 |
---|---|
[이득우의 언리얼 C++ 게임개발의 정석] #8. 애니메이션 시스템 활용 (0) | 2024.06.20 |
[이득우의 언리얼 C++ 게임개발의 정석] #6. 캐릭터의 제작과 컨트롤 (0) | 2024.06.13 |
[이득우의 언리얼 C++ 게임개발의 정석] #5. 폰의 제작과 조작 (0) | 2024.06.10 |
[이득우의 언리얼 C++ 게임개발의 정석] #4. 게임플레이 프레임워크 (0) | 2024.06.10 |