애니메이션 편집기라고 보면 된다.

 

 

메시를 더블클릭해서 들어온 후 해당 메뉴에서 애님 몽타주를 만들면 편집 창으로 들어올 수 있다.

애셋 브라우저에서 애니메이션 몇 개를 끌어오면 연속되어 재생되는것을 확인할 수 있다.

원한다면 애니메이션 세그먼트에서 재생 시간, 재생 속도, 루프 횟수를 조절할 수 있다.

 

 

점프같은 경우는 AnimInstance에서 캐릭터의 상태를 매 틱 체크하는 방식이었지만 반대로 공격은 캐릭터에서 발생 시 AnimInstance에 접근해서 Attack Montage를 실행 시킬 것이다.

 

// MyAnimInstance.h
class TESTUNREALENGINE_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

public:
	UMyAnimInstance(); // 생성자 추가

	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	void PlayAttackMontage(); // 메소드 추가

private:
	// .. 생략
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Pawn, Meta = (AllowPrivateAccess = true))
	UAnimMontage* AttackMontage; // Montage 추가
};

// ----------

// MyAnimInstance.cpp
UMyAnimInstance::UMyAnimInstance()
{
	static ConstructorHelpers::FObjectFinder<UAnimMontage> AM(TEXT("AnimMontage'/Game/Animations/Greystone_Skeleton_Montage.Greystone_Skeleton_Montage'"));

	if (AM.Succeeded())
	{
		AttackMontage = AM.Object;
	}
}

void UMyAnimInstance::PlayAttackMontage() // 실제 Montage 실행부
{
	if (!Montage_IsPlaying(AttackMontage))
	{
		Montage_Play(AttackMontage, 1.f);
	}
}

// ------------

// MyCharacter.cpp
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	// .. 생략
	PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, this, &AMyCharacter::Attack);
}

void AMyCharacter::Attack()
{
		// MyAnimInstance에 접근
	auto AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());

	if (AnimInstance)
	{
    		// 메소드 직접 실행
		AnimInstance->PlayAttackMontage();
	}
}

 

Attack을 바인딩 했으니 당연히 입력에서 Attack을 마우스 왼클릭으로 설정해주면 된다.

 

 

코드 및 AnimGraph를 완성하면 좌클릭시 설정한 애니메이션 몽타주가 재생된다.

전 챕터처럼 애니메이션의 전환을 하나의 AnimGraph에서 if-else로 관리하는 것은 명확한 한계가 있다.

 

 

스테이트 머신으로 기존의 구조를 바꿀 수 있다.

 

 

 

스테이트 머신을 더블 클릭하면 안의 구조로 들어갈 수 있고 스테이트를 추가하여 연결시킬 수 있다.

 

 

 

또 스테이트 내부로 들어가게 되면 전 챕터에서 봤던 그 구조를 볼 수 있다.

 

// MyAnimInstance.h
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Pawn, Meta = (AllowPrivateAccess = true))
	bool IsFalling;

// ----------

// MyAnimInstance.cpp
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	auto Pawn = TryGetPawnOwner();

	if (IsValid(Pawn))
	{
		Speed = Pawn->GetVelocity().Size();

		auto Character = Cast<ACharacter>(Pawn);
		if (Character)
		{
        		// 캐릭터가 추락중인가?
			IsFalling = Character->GetMovementComponent()->IsFalling();
		}
	}
}

// -------------

// MyCharacter.cpp
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

		// 별도로 Jump 메소드를 선언하지 않아도 부모 클래스에서 이미 정의되었기 때문에 사용할 수 있다
	PlayerInputComponent->BindAction(TEXT("Jump"), EInputEvent::IE_Pressed, this, &AMyCharacter::Jump);

 

Space바 키를 입력 받으면 점프모션으로 변화시켜 애니메이션을 재생하기위해 MyCharacter에는 점프키에 대한 바인딩을, MyAnimInstance에는 IsFalling을 추가하여 캐릭터가 떨어지고 있는 상태인지를 받아온다.

 

 

 

더블 클릭을 눌러서 들어가면 상태 전환간의 룰을 지정할 수 있게된다.

 

 

상태 전환간의 룰까지 지정하고 나면 의도한 대로 애니메이션이 정상적으로 작동하는 것을 볼 수 있다.

 

 

특정 상태일때의 처리, 한 상태에서 다른 상태로 넘어갈때의 처리. 이렇게 두 가지로 관리한다고 보면 된다.

C++ 코드로 관리하는것은 정말 어렵다.

 

 

 

이런 식으로 점프 모션도 세분화해서 만들수 있다.

 

특정 애니메이션이 한번만 재생되길 원하는 경우 애니메이션 애셋의 디테일에서 Loop Animation 체크를 해제해주면 된다.

특정 애니메이션 재생이 끝난 직후 다음 상태로 넘어가길 원한다면 상태 전환간의 룰을 누르고 디테일 탭에서 Automatic Rule Based를 체크해주면 된다. (JumpEnd->Ground)

 

 

C++로 개발을 한다고 해도 애니메이션 상태 변환 만큼은 툴을 활용하는 것이 일반적이다.

언리얼에서의 애니메이션은 별도의 클래스에 몰빵해서 관리한다.

 

C++ 클래스로 만들 때, 부모 클래스를 AnimInstance로 고른다.

 

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Pawn, Meta=(AllowPrivateAccess=true))
float Speed;

 

UPROPERTY의 각 인자는 필요할 때마다 구글링하면 되므로 우선은 넘어간다.

 

 

AnimInstance C++ 클래스를 만들고,

 

 

 

해당 C++ 클래스를 상속받는 블루프린트 애니메이션을 만들고, (블루프린트 클래스가 아닌것에 유의)

 

 

 

해당 애니메이션을 적용시킬 BP_MyCharacter의 Animation Bluprint를 위의 ABP_MyAnim으로 설정한다.

 

실행 도중에 애니메이션 블루프린트에 들어가보면 현재 실행중인 애니메이션이 어떤 것인지 확인할 수 있다.

 

 

 

위에서 선언한 Speed는 애님 프리뷰 에디터에서 확인할 수 있다.

 

 

 

AnimGraph를 통해서 관리할 수 있다.

 

 

애니메이션에 관련된것은 AnimInstance에서 몰빵해서 관리하게 되는데, 실제로 어떤 입력값에 의해 이동이 발생해서 그 값으로 애니메이션의 상태가 결정되어야 하는 경우 AnimInstance의 값에 접근할 수 밖에 없다.

MyCharacter에서 움직임이 발생할때 마다 필요한 정보를 GetAnimInstance 등등 접근해서 수단과 방법을 가리지 않고 세팅하는 방법, 반대로 AnimInstance에서 매 틱마다 외부의 정보를 수집해서 자신에게 세팅하는 방법이 있을 것이다.

 

전자의 경우 코드가 뒤섞일 우려가 있다.

그래서 후자의 경우를 사용하는 게 일반적이다.

 

// 헤더
class TESTUNREALENGINE_API UMyAnimInstance : public UAnimInstance
{
	GENERATED_BODY()

	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

private:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Pawn, Meta=(AllowPrivateAccess=true))
	float Speed;
};

// ------

// cpp
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);
    
    // 현재 AnimInstance를 소유중인 Pawn을 가져오는 것을 시도함
	auto pawn = TryGetPawnOwner();
    
    // '시도' 이기 때문에 못 가져올 수도 있으므로 유효한 값인지 검사
	if (IsValid(pawn))
	{
		Speed = pawn->GetVelocity().Size();
	}
}

 

애니메이션 블루프린트를 만들면 테스트 하기가 편하다.

C++로 만들게 될 경우 블루프린트를 굳이 사용할 필요는 없지만 대략적으로 어떻게 돌아가는지는 알아야 하고 경우에 따라 블루프린트를 적절히 조합해서 사용하는게 더 편한 경우가 있다. 케바케이다.

 

큰 프로젝트이고 성능에 중요함이 있다고 하면 C++로 무조건 가야하고,

블루프린트는 프로토타이핑 할때는 좋으나 성능이 10배가량 느리기 때문에 남발하면 안된다.

 

 

블루프린트 클래스도 생성을 누르면 C++클래스와 마찬가지로 부모 클래스를 선택하게 된다.

C++클래스와 다르게 rename이나 삭제도 자유롭게 할 수 있다.

 

마치 비주얼 코딩을 하는 느낌이다.

블루프린트 클래스의 컴파일은 가볍지만 블루프린트 클래스 자체가 가벼운건 절대 아니다.

 

 

GameModeBase 클래스에서 블루프린트 클래스를 기본값으로 설정하려면 ConstructorHelpers로 불러와야 한다.

 

AMyGameModeBase::AMyGameModeBase()
{
	// 기존 문법
    //DefaultPawnClass = AMyCharacter::StaticClass();

	static ConstructorHelpers::FClassFinder<ACharacter>
    BP_Char(TEXT("Blueprint'/Game/Blueprints/BP_MyCharacter.BP_MyCharacter_C'"));

	if (BP_Char.Succeeded())
	{
		DefaultPawnClass = BP_Char.Class;
	}

 

경로 뒤에 _C를 붙이게 되는데 규칙이다.

 

 

  

void AMyCharacter::UpDown(float Value)
{
	AddMovementInput(GetActorForwardVector(), Value);
}

void AMyCharacter::LeftRight(float Value)
{
	AddMovementInput(GetActorRightVector(), Value);
}

void AMyCharacter::Yaw(float Value)
{
	AddControllerYawInput(Value);
}

 

둘의 결과는 동일하다.

물론 블루프린트 클래스와 아래의 코드는 서로 다른 클래스이긴 하지만 똑같이 작동하게 구현할 수 있다는 것을 확인할 수 있다.

 

블루프린트 클래스는 C++ 클래스를 상속 받아서 사용한다. 당연히 내가 만들어둔 클래스를 상속받아 사용하는 것도 가능하다.

하지만 반대로 블루프린트 클래스를 상속받는 C++ 클래스를 만드는 것은 불가능하다.

 

 

꼭 캐릭터만 블루프린트로 만들라는 법은 없다. 게임 모드도 블루프린트 클래스로 만들어서 편하게 관리할 수도 있다.

 

만들어진 블루프린트 클래스는 유니티의 prefab과 같은 것이라고 보면 될 것 같다.

Pawn에서 Character로 넘어갈 때 생각보다 기능이 많이 추가된다.

Character는 기본적으로 SkeletalMesh가 붙어있다.

CharacterMoveComponent가 기본적으로 상속되어있다.

 

액터나 폰은 생성자에서 메시나 루트 컴포넌트를 직접 설정해줬어야 하는데 캐릭터는 부모 클래스가 기본값으로 다 설정을 해둔다.

 

 

// MyCharacter.h
	void UpDown(float Value); // 이전시간 Pawn에 있던 것과 같음
	void LeftRight(float Value); // cpp에서도 똑같이 복붙 해주면 된다. 클래스명만 바꿔서.

private:
	UPROPERTY(VisibleAnywhere)
	class USpringArmComponent* SpringArm; // CoreMinimal에 없으므로 전방선언

	UPROPERTY(VisibleAnywhere)
	class UCameraComponent* Camera; // 마찬가지로 전방선언
};


// MyCharacter.cpp
#include "MyCharacter.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"

// Sets default values
AMyCharacter::AMyCharacter()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// 보이지 않는 일종의 셀카봉
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
    // 카메라
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));

	// 셀카봉을 캐릭터의 하위 구조에 넣음
	SpringArm->SetupAttachment(GetCapsuleComponent());
    // 카메라를 셀카봉의 하위 구조에 넣음
	Camera->SetupAttachment(SpringArm);

	// 셀카봉을 늘린다
	SpringArm->TargetArmLength = 500.f;
    // 캐릭터의 뒤쪽으로 뻗어나가게 회전 시킨다
	SpringArm->SetRelativeRotation(FRotator(-35.f, 0.f, 0.f));

	// 캐릭터 메시 위치를 조정한다 (위치 및 회전 동시 조정)
	GetMesh()->SetRelativeLocationAndRotation(
		FVector(0.f, 0.f, -88.f), FRotator(0.f, -90.f, 0.f));

	static ConstructorHelpers::FObjectFinder<USkeletalMesh> SM(TEXT("SkeletalMesh'/Game/ParagonGreystone/Characters/Heroes/Greystone/Meshes/Greystone.Greystone'"));
	
	if (SM.Succeeded())
	{
    	// StaticMesh가 아닌 SkeletalMesh를 가지고 있으므로 맞춰서 변경해줌
		GetMesh()->SetSkeletalMesh(SM.Object);
	}
}

 

 

상속 구조도 그대로 표현되는 것을 확인할 수 있다.

 

마우스 좌우 움직임으로 캐릭터 회전을 추가하는 경우, 기존 키 입력처럼 프로젝트 세팅에서 마우스 축을 추가하고,

SetupPlayerInputComponent에서 BindAxis를 해준 후 메소드를 구현해주면 된다.

+ Recent posts