게임에서 주요 시스템이라고 할 수 있는 애니메이션을 만들어보자.
Epic Games가 제공하는 Infinity Blade : Warriors 라는 에셋을 다운받아 사용할 것인데
언리얼 마켓플레이스가 사라지고 Fab이 생기면서 해당 에셋이 사라졌다.
그러나 아무 에셋이나 사용해도 무방하다.
캐릭터 움직임
기본 상태 Idle과 속도에 따라서 블렌딩 작업을해 애니메이션을 나타내보도록 하자.
애니메이션은 애니메이션 블루프린트라고 하는 BP에서 관리를 한다.
C++AnimInstance를 생성한 뒤 이를 상속받는 애니메이션 블루프린트를 만들어 애님 그래프로 구체적인 모션을 지정하자.
BP를 만들때 스켈레탈 메시에 어떤걸 사용할지 선택해야한다.
애니메이션 블루프린트 구조
캐릭터 애니메이션 시스템의 생성법은?
- 스켈레탈 메시 컴포넌트의 애니메이션 블루프린트 클래스를 지정함
- 캐릭터가 초기화가 될 때 애니메이션 Instance 클래스의 인스턴스를 생성함.
캐릭터 자체가 GetAnimInstance 함수를 통해 애니메이션 인스턴스를 얻을 수 있고 애니메이션 인스턴스는 반대로 GetOwningActor를 통해 소유 액터를 알 수 있다. 서로 상호 참조가 가능한 구조로 되어있다.
캐릭터 움직임
애니메이션 블루프린트는 이벤트 그래프와 애님 그래프, 2가지 영역으로 구성되어 있다.
두 개 모다 각자의 고유한 목적을 가지고 있다.
이벤트 그래프는 발생된 이벤트로부터 상태를 파악할 수 있는 주요변수를 저장하는데 사용한다.
NativeInitialzeAnimation 과 NativeUpdateAnimation 함수 등이 있다.
이는 애니메이션 초기화, 그리고 프레임마다 수행되는 함수를 의미한다.
이제 저 프레임마다 업데이팅 되는 것은 매 프레임 마다 캐릭터에 관한 정보를 끌고 와서 상태 자체를 지정한 변수로 저장하는 구조이다.
이 상태 값에 따라 State Machine에서 노드를 바꿔주게 할 것이다.
UCLASS()
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public :
UABAnimInstance();
protected:
virtual void NativeInitializeAnimation() override;
virtual void NativeUpdateAnimation(float DeltaTime) override;
UPROPERTY(VisibieAnywhere, BlueprintReadOnly, Category = Character)
TObjectPtr<class ACharacter> Owner;
UPROPERTY(VisibieAnywhere, BlueprintReadOnly, Category = Character)
TObjectPtr<class UCharacterMovementComponent> Movement;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
FVector Velocity;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
float GroundSpeed;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
uint8 bIsIdle : 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
float MovingThreshold;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
uint8 bIsFalling : 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
uint8 bIsJumping : 1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Character)
float JumpingThreshold;
};
UABAnimInstance::UABAnimInstance()
{
MovingThreshold = 3.0f;
JumpingThreshold = 100.0f;
}
void UABAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
여기서 실제로 해당 애님인스턴스를 소유하고 있는 액터에 관한 정보를 들고오고
Owner = Cast<ACharacter>(GetOwningActor());
그에 걸맞는 캐릭터 무브먼트에 관한 정보를 들고 온다.
if (Owner) {
Movement = Owner->GetCharacterMovement();
}
}
void UABAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
Super::NativeUpdateAnimation(DeltaTime);
//이제 실제적으로 프레임마다 판단이 진행될 부분.
//속도의 경우 캐릭터의 속도를 받아와서 땅 부분의 속도만 얻으면 되니까 2D로 받고,
//점프의 경우 캐릭터 MovementComponent 자체에 IsFalling이라고 있으니까 이거를 받아오면 그만
//추가로 이제 각 멤버별로 임계값을 가지고 판단을 해준다면 그게 끝임.
if (Movement) {
Velocity = Movement->Velocity;
GroundSpeed = Velocity.Size2D();
bIsIdle = GroundSpeed < MovingThreshold;
bIsFalling = Movement->IsFalling();
bIsJumping = bIsFalling & (Velocity.Z > JumpingThreshold);
}
}
애님 그래프 만들기
Locomotion이라는 State를 Main State로 보여줄 것이다.
Locomotion은 다음과 같이 구성되어 있다.
아까 위에서 설정한 IsIdle에 따라 상태가 변하게 하면 된다.
그런데 속도에 따라 애니메이션을 출력하기 위해 BlendSpace 1D를 추가한다.
원하는 애니메이션을 넣고 속도에 따라 어떻게 블랜드 할 것인지 결정해주면 된다.
점프는 더 간단하다.
다만 Falling 보다 Jump가 우선순위기 때문에 두개가 동시에 발생한다면 Jump가 먼저 실행되게 만들어야한다.
Priority Order를 정해주면 된다.
이렇게 하면 플레이어가 잘 이동하는 모습을 볼 수 있다.
연속 공격 만들기
애니메이션 몽타주를 활용해서 캐릭터의 연속 공격을 제작해보자.
애니메이션 몽타주는 애니메이션 클립을 잘라내고 합성을 한 다음에 이를 재생하는 기법을 말한다. 애니메이션 클립을 모아두는 다수의 섹션들로 구성이 되어 있고, 섹션끼리 연동이 가능하며, 스크립트를 통해 원하는 섹션을 재생할 수 있다.
이렇게 4개의 섹션으로 나눠 애니메이션을 저때 틀어보자.
이렇게 만든 몽타주를 애님 그래프에 추가해줘야 한다.
Slot을 이용한다. 몽타주 내부에 있는 섹션을 호출한다면 몽타주의 애니메이션이 실행되고 그게 아니라면 기본적인 애니메이션이 호출될 것이다.
void AABCharacterBase::ProcessComboCommand()
{
if (CurrentCombo == 0) {
ComboActionBegin();
return;
}
if (!ComboTimerHandle.IsValid()) {
HasNextComboCommand = false;
}
else {
HasNextComboCommand = true;
}
}
void AABCharacterBase::ComboActionBegin()
{
//Combo Start
CurrentCombo = 1;
//Movement Setting
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
//Animation Setting
const float AttackSpeedRate = 1.0f;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);
ComboTimerHandle.Invalidate();
SetComboCheckTimer();
}
void AABCharacterBase::SetComboCheckTimer()
{
int32 ComboIndex = CurrentCombo - 1;
ensure(ComboActionData->EffectiveFrameCount.IsValidIndex(ComboIndex));
const float AttackSpeedRate = 1.0f;
float ComboEffectiveTime = (ComboActionData->EffectiveFrameCount[ComboIndex] / ComboActionData->FrameRate) / AttackSpeedRate;
if (ComboEffectiveTime > 0.0f) {
GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
}
}
void AABCharacterBase::ComboCheck()
{
ComboTimerHandle.Invalidate();
if (HasNextComboCommand) {
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);
FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionNamePrefix, CurrentCombo);
AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);
SetComboCheckTimer();
HasNextComboCommand = false;
}
}
이렇게 코드를 작성하면 매 어택마다 정해진 시간안에 다시 공격했을 경우 연속 공격을 할 수 있다.
'Unreal Engine 5 > 강의' 카테고리의 다른 글
[UE5] 아이템 - 여러 종류 아이템 획득하기 (0) | 2024.12.09 |
---|---|
[UE5] 애니메이션 - 캐릭터 공격 판정 (0) | 2024.12.04 |
[UE5] 캐릭터 움직임 - 캐릭터 컨트롤에 대해 (0) | 2024.11.29 |
[UE5] 언리얼 게임 제작 기초 - C++ 과 캐릭터 입력 시스템 (0) | 2024.11.28 |
[UE5] 언리얼 오브젝트 관리- 직렬화, 패키지 (0) | 2024.11.27 |