언리얼 엔진/25.01.23 2차프로젝트 기술

원거리 에너미 애니메이션 적용하기

odkokdh 2025. 2. 16. 17:59

이번에는 원거리 에너미에게 애니메이션을 주기로 하였다.

먼저 적용할 에셋을 찾아보았다.

 

https://www.fab.com/listings/7397dd8d-2e47-4e68-9e9d-a10c6fa0a9c7

 

에셋 스토어에서 에셋을 찾아보던 중 해당 자료를 발견하였다. 외형도 고를 수 있고 애니메이션도 있고 원하던 외형이기에 바로 에셋을 받고 적용하였다.

 


1. 원거리 에너미에게 에셋 적용

 

RangedEnemy.cpp

ARangedEnemy::ARangedEnemy()
{
	ConstructorHelpers::FObjectFinder<USkeletalMesh> tempMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/ParagonHowitzer/Characters/Heroes/Howitzer/Skins/Tier_2/Domed/Meshes/Howitzer_Domed.Howitzer_Domed'"));

	if (tempMesh.Succeeded())
	{
		GetMesh()->SetSkeletalMesh(tempMesh.Object);
	}
	GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -75.0f), FRotator(0.0f, -90.0f, 0.0f));
}

 

블루프린트에서 저절로 적용되도록 생성자에서 위의 코드를 작성하였다. 

ConstructorHelpers::FObjectFinder<USkeletalMesh>로 미리 에셋을 지정할 수 있게 만들었다. 그리고 성공했을 경우 GetMesh를 통해 스켈레탈메쉬를 설정해 준다.SetRelativeLocationAndRotation()은 위치와 회전 값을 조절해 준다. FVector()를 사용해 X,Y,Z의 위치를 조절, FRotator()를 사용해 회전이 가능하다.

  • SetRelativeLocation(FVector()) : 위치를 조절해준다.
  • SetRelativeRotation(FRotator) : 회전 값을 조절해준다.
  • SetRelativeLocationAndRotation(FVector(), FRotator) : 위치와 회전을 동시에 조절할 수 있다.

 

ConstructorHelpers::FObjectFinder<USkeletalMesh> tempMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/ParagonHowitzer/Characters/Heroes/Howitzer/Skins/Tier_2/Domed/Meshes/Howitzer_Domed.Howitzer_Domed'"));

위의 코드는 폴더의 주소를 사용해서 미리 지정하는 것이다. 이것에 대해서는 아래에서 쓰는 방법을 작성하겠다.

먼저 블루프린트에서 스켈레탈 메쉬를 설정하여 본다.

 

 

그리고 빨갛게 표시한 아이콘을 누르면 콘텐츠 브라우저에서 해당 에셋의 위치로 이동된다.

 

 

해당되는 것을 클릭한 다음 Ctrl+C를 해준다. 그 다음 Ctlr+V를 바로 코드에 작성하던가, 메모장에 작성해 둔다. 그러면 주소가 작성된다.

주의할 점은 메모장에 붙여놓기를 하면 크게 문제가 안되는데 노션에 붙여놓기를 하면 무언가가 더 붙어서 오류가 일어날 수 있다.

 


2. 원거리 애니메이션 블루프린트 제작하기

먼저 코드에서 애니메이션 전환과 같은 동작을 제어할 수 있도록 C++ 코드를 하나 만들어 준다.

 

먼저 부모 클래스로 AnimInstance를 눌러서 생성을 해줘야 한다.

 

REAnimInstance.h

class DESTINY2_API UREAnimInstance : public UAnimInstance
{
	GENERATED_BODY()
	
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite,Category = REAnimInst)
	float Speed = 0.0f;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite,Category = REAnimInst)
	float Direction = 0.0f;
};

 

먼저 헤더에서 변수들을 설정해주었다. 플레이어를 추적할 때 움직이는 상황일 것이라 생각하여 Speed와 Direction 두개를 만들었다. 움직이는 것만 설정하면 Speed 두개만 있어도 될 것이라 생각할 수 있는데 두개를 만든 이유는 뒤에서 같이 설명하겠다.

 

void UREAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeUpdateAnimation(DeltaSeconds);

	//이 클래스를 갖고 있는 pawn의 정보를 가져옴
	AEnemyBase* Enemy = Cast<AEnemyBase>(TryGetPawnOwner());
	if (!Enemy) return; //없을 경우 아래는 실행 안함

	//캐릭터의 속도를 저장해둠
	FVector velocity = Enemy->GetVelocity();
	//그리고 해당 캐릭터의 앞방향 벡터를 받아옴
	FVector forwardVector = Enemy->GetActorForwardVector();

	// 앞/뒤 이동속도 (내적을 이용)
	Speed = FVector::DotProduct(velocity, forwardVector);

	FVector RightVector = Enemy->GetActorRightVector();

	Direction = FVector::DotProduct(velocity, RightVector);
}

 

먼저 해당 클래스를 갖고있는 pawn의 정보를 가져온다. 그리고 캐릭터의 속도와 해당 액터의 fowardVector를 저장한다. 이후 DotProduct를 사용하여 Speed에 저장한다.

DotProduct가 내적을 구하는 것이라고 한다.

forwardVector를 기준으로 내적하면 같은 앞방향은 값이 1로,

좌, 우는, 즉 수직은 값이 0으로,

앞의 반대인 뒷 방향은 값이 -1로 나온다고 한다.

 

솔직히 자세히 이해하지는 못하였다. 내적은 타겟의 각도, 위치를 알기위해, 외적은 방향을 알기 위해 사용된다고 한다.

콘텐츠 브라우저에서 우클릭 후 Animation 탭에서 Animation Blueprint를 클릭해서 제작한다.

 

그럼 위와 같이 창이 나오는데 자신이 사용하려는 메쉬의 맞는 스켈레톤을 선택하고 아랫부분에서 방금 만든 AnimInstance를 부모 클래스로 설정한 다음 만들어준다.

 

 

빈공간에 우클릭을 해서 State Machine이라 검색하면 정확히 일치하는 것이 있다. 이를 생성하고 Output Pose에 연결해준다.

 

먼저 기본 상태는 Idle로 이름을 바꿔 설정해줬다. 가만히 있을 때만 해당 애니메이션이 재생된다. 그런데 애니메이션을 미리보면 애니메이션이 반복 재생이 되지않아 한 사이클이 끝나면 멈춘다. 루프를 걸어주지 않았기에 이렇게 되는 것이다.

먼저 Idle을 더블클릭하여 들어가자.

그럼 사진과 같이 나오게 되는데 애니메이션을 클릭해서 Loop Animation을 체크해준다. 그럼 한 사이클이 끝나도 다시 애니메이션이 재생된다.

 

그리고 이제 앞 뒤 좌 우 움직일때 뛰는 모션이 나오도록 할 예정이다. 방향에 따라 뛰는 모션이 달라지도록 하는 간단한 방법이 있다.

먼저 Animation탭에서 Blend Space를 클릭한다.

그러면 다시 스켈레톤을 선택하는 창이 나오는데 맞는 스켈레톤을 선택해 주면 된다.

해당 사진은 이미 설정이 끝난 상태이다. 간단하게 설명을 적자면

  • 우측 파랑색 영역: 어떤 애니메이션들이 있는지 알 수 있는 창이다. 여기에서 원하는 애니메이션을 빨강색 영역에 넣는다.
  • 중앙 빨강색 영역: 각 값들에 따라 애니메이션이 재생될 지 설정하는 영역. 자세한 설명은 아래에서 후술
  • 좌측 초록색 영역: 빨강색 영역의 설정을 변경할 수 있는 부분이다. 자세한 설명은 아래에서 후술

아래의 네모난 창은 상하의 값과 좌우의 값에 따라 어떤 애니메이션이 재생될지 설정하는 것이다.

먼저 Speed 값이 300, 즉 위로 가까워지면 1번의 애니메이션이, -300에 가까워지면 2의 애니메이션이 재생된다.

Direction의 값이 -300에 가까워 지면 3번의 애니메이션이, 300에 가까워지면 4의 애니메이션이 재생된다.

그리고 두개의 값이 0일 경우 5번의 애니메이션이 재생된다.

만약 1번과 3번의 중간 값이 나올 경우 저절로 1과 3의 중간 애니메이션이 보정되어서 재생된다. 참 편리하다.

작업을 하는데 쓰기 편한 사용법이 두개 있다. 

  • 애니메이션 미리 보기: Ctrl을 누른 상태에서 해당 창에 마우스 포인터 올리기
  • 애니메이션 격자에 맞춰 놓기: Shift를 누른 상태에서 흰색 점을 드래그하기.

지금 값들이 -300과 300으로 되어있는데 이는 자신이 원하는 값으로 최소 최대를 설정할 수 있다.

 

Axis Settings에서 좌우, 상하 값에 대한 정보를 변경할 수 있다.

 

  • Name: 각 값의 이름을 변경할 수 있다.
  • Minimun Axis Value: 최소값을 몇으로 할 수 있는지 설정이 가능하다.
  • Maximum Axis Value: 최대값을 몇으로 할 수 있는지 설정이 가능하다
  • Girid Division: 총 몇 개의 칸으로 설정할 것인지 설정할 수 있다. 4이면 4칸, 2이면 2칸으로 된다.
  • Snap to Grid: 애니메이션의 위치를 설정할 때 체크되어 있으면 자동으로 칸에 맞춰 설정해준다..

그 이후 다시 애니메이션 블루프린트로 돌아가 방금 만든 블랜드 스페이스를 세팅한다. 이것만 하면 애니메이션 전환이 이루어지지 않는다. 빨강색 부분을 더블클릭하면 어떤 조건으로 애니메이션이 전환될 수 있는지 조건을 지정해줄 수 있다.

 

사진과 같이 설정을 했다. 움직이는 경우에만 재생될 수 있게 Speed의 값이 0.1보다 크면 전환되도록 조건을 만들었다.

Speed는 C++ 코드로 만든 변수이다.

 

더보기

 

 

애니메이션 블루프린트에서 빨강색으로 표시한 톱니바퀴 아이콘을 누르고 Show Inherited Variables를 체크한다. 그러면 작성한 변수가 보일 것이다.

 

이렇게 해서 테스트를 해보니 에너미가 플레이어를 추적할 때는 정상적으로 전진 애니메이션이 나온다. 하지만 에너미의 좌우 공격이 실행될 때에는 좌우 이동 애니메이션이 재생되지 않는다.

원인과 해결법을 계속해서 찾으려 했지만 도저히 모르겠다. 그리고 꼭 블랜드 스페이스를 이용해서 좌우 애니메이션이 나와야 할까 생각을 하였다. 그래서 일단 임시방편으로 bool을 사용해서 좌우 움직임이 나오도록 하였다.

 

UREAnimInstance.h

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = REAnimInst)
bool bRightMove = false;

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = REAnimInst)
bool bLeftMove = false;

 

먼저 해더에 좌우 이동을 체크하는 불을 추가로 작성한다.

사진과 같이 좌우 이동 애니메이션으로 전환되도록 만든 다음, 조건을 각각 설정해준다.

 

순서대로 우측 좌측 이동 애니메이션이 전환되는 조건을 설정하였다. 반대의 경우에는 Not Bool을 이용하여 체크되도록 만든다.

 

ARangedEnemyAI.h

UPROPERTY()
class UREAnimInstance* Anim;
ARangedEnemyAI.cpp

//Tick 작성 부분
if (!Anim)
{
	//애니메이션 인스턴스를 사용하기 위해 가져옴
	Anim = Cast<UREAnimInstance>(EnemyCharacter->GetMesh()->GetAnimInstance());
}

void ARangedEnemyAI::ChangeMovingBool()
{
	moving = !moving;

	Anim->bRightMove = false;
	Anim->bLeftMove = false;
	
	EnemyCharacter->ActionNumReset();
}

 

내가 작성한 코드를 수정하였다. 좌,우 움직이는 것을 실행한 다음 일정 시간이 지나면 ChangeMovingBool()함수를 불러와 좌우 움직임을 멈추고 좌우 이동 애니메이션을 멈추게 수정하였다.

AnimInstance 정보를 저장하기 위해 Tick부분에서 현재 갖고 있지 않을 경우 받게 만들었다.

Begin이 아닌 Tick에서 실행한 이유는 Begin에서는 스폰시켰을 때 값이 null로 받아 실행되어서 크래시가 발생하였다. 오류를 방지하기 위해 Tick에서 실행하도록 만들었다.

 

이렇게하여 테스트를 해보니 정상적으로 원거리 에너미가 앞, 좌우로 움직이는데 애니메이션이 정상적으로 실행되었다.