Infinite Fighter 개발일지 (8)

유영준·2023년 5월 7일
0

UE5 UNSEEN

목록 보기
8/18
post-thumbnail

2023.05.01 ~ 2023.05.07 개발일지

추가한 부분

폴더 정리

Build 파일에 PublicIncludePaths.AddRange 를 통해 경로를 추가하고, C++클래스들을 폴더 별로 나누어 정리하였다

InfiniteFighter.Build.cs

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class InfiniteFighter : ModuleRules
{
	public InfiniteFighter(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicIncludePaths.AddRange(new string[] { "InfiniteFighter" });

		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", 
        "HeadMountedDisplay", "EnhancedInput", "AnimGraphRuntime", "CommonInput", "UMG" });
	}
}

비주얼 스튜디오와 엔진 두 곳에서 모두 잘 적용되는 것을 확인하였다


게임모드 캐릭터 불러오기

이전까지는 게임모드에서 캐릭터를 불러올때 헤더파일을 참조하여 불러오는 방식을 사용했는데,

캐릭터 클래스가 커짐에 따라 컴파일 시간을 줄이기 위해 헤더 파일을 참조하지 않고 ClassFinder를 통해 불러오게 되었다

IFGameMode.cpp

#include "IFGameMode.h"

AIFGameMode::AIFGameMode()
{
	static ConstructorHelpers::FClassFinder<APawn> ThirdPersonClassRef
	(TEXT("/Script/InfiniteFighter.IFCharacter"));
	if(ThirdPersonClassRef.Succeeded())
		DefaultPawnClass = ThirdPersonClassRef.Class;
}

싱크 모션 연출

특정 상황이 되었을때, 캐릭터가 적을 한번에 처형 시키는 기술을 구현하고자 하였다

먼저 애니메이션을 준비했다

밟는 대상이 캐릭터, 밟히는 대상을 적으로 설정하여 몽타주를 실행시켜줄 예정이다

이때 둘 사이의 거리는 75만큼의 차이가 나고 캐릭터와 적이 같은 시간에 지정된 위치에 애니메이션을 실행하여야 했다

이를 위해 언리얼 엔진의 모션 워핑을 사용하였다

모션 워핑

모션 워핑을 적용하기 위해서는 먼저 플러그인과 모듈을 추가해주어야 한다

에디터에서 모션 워핑을 추가해주고, Build 파일에서 MotionWarping을 추가해준다

다음은 적 캐릭터를 구축하였다

이때 처형 모션을 할 수 있는 영역인 WarpCollision을 설정하고,

그 영역 안에서 처형 모현을 실행할 때 이동할 지점인 WarpPoint를 설정했다

또 캐릭터에는 같이 모션을 플레이할 Target이라는 변수를 두고, WarpCollision에 들어가면 캐릭터의 Target이 본인이 되도록 지정했다

마지막으로 애니메이션을 실행할때는 실행중 매쉬나 콜리전이 겹쳐지는 것을 방지하기 위해 적의 Collision 세팅을 바꿔주었다

AIFEnemy.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "AI/IFEnemy.h"
#include "Components/BoxComponent.h"
#include "Components/SceneComponent.h"
#include "Components/CapsuleComponent.h"
#include "Character/IFCharacter.h"
#include "IFEnemyAnimInstance.h"

// Sets default values
AIFEnemy::AIFEnemy()
{
 	// 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;

	GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -90.0f), FRotator(0.0f, -90.0f, 0.0f));

	// setting the mesh and animation
	static ConstructorHelpers::FObjectFinder<USkeletalMesh>SKM_MESH
	(TEXT("/Game/InFiniteFighter/Characters/Mannequin_UE4/Meshes/SK_Mannequin.SK_Mannequin"));
	if (SKM_MESH.Succeeded())
		GetMesh()->SetSkeletalMesh(SKM_MESH.Object);

	GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);

	static ConstructorHelpers::FClassFinder<UAnimInstance>ENEMY_ANIM
	(TEXT("/Game/InFiniteFighter/AI/EnemyAnimBlueprint.EnemyAnimBlueprint_C"));
	if (ENEMY_ANIM.Succeeded())
		GetMesh()->SetAnimInstanceClass(ENEMY_ANIM.Class);

	// setting the point and box for motion warping
	WarpPoint = CreateDefaultSubobject<USceneComponent>(TEXT("WARP_POINT"));
	WarpPoint->SetRelativeLocation(FVector(75.0f, 0.0f, 0.0f));
	WarpPoint->SetupAttachment(RootComponent);

	WarpCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("WARP_COLLISION"));
	WarpCollision->SetRelativeLocation(FVector(125.0f, 0.0f, 0.0f));
	WarpCollision->SetBoxExtent(FVector(75.0f, 75.0f, 32.0f));
	WarpCollision->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AIFEnemy::BeginPlay()
{
	Super::BeginPlay();
	
	WarpCollision->OnComponentBeginOverlap.AddDynamic(this, &AIFEnemy::OverlapBegin);
	WarpCollision->OnComponentEndOverlap.  AddDynamic(this, &AIFEnemy::OverlapEnd);
}

void AIFEnemy::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	AnimInstance = Cast<UIFEnemyAnimInstance>(GetMesh()->GetAnimInstance());
}

void AIFEnemy::OverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, 
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	AIFCharacter* PlayerCharacter = Cast<AIFCharacter>(OtherActor);

	// If the cast is successful and the player's Target variable is not already set
	if (PlayerCharacter != nullptr && PlayerCharacter->Target == nullptr)
	{
		// Set the player's Target variable to be this enemy
		PlayerCharacter->Target = this;
	}
}

void AIFEnemy::OverlapEnd(UPrimitiveComponent* OverlappedComponent, 
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	AIFCharacter* PlayerCharacter = Cast<AIFCharacter>(OtherActor);

	// If the cast is successful and the player's Target variable is set to this
	if (PlayerCharacter != nullptr && PlayerCharacter->Target == this)
	{
		// Set the player's Target variable to be nullptr
		PlayerCharacter->Target = nullptr;
	}
}

void AIFEnemy::PlayExecuteVictim()
{
	// set capsule to ignore pawn so it doesn't go throw floor
	GetCapsuleComponent()->SetCollisionProfileName("IgnoreOnlyPawn");
	GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	// set mesh to overlap all so it doesn't block anything
	GetMesh()->SetCollisionProfileName("OverlapAll");
	GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	AnimInstance->PlayExecuteVictimMontage();
}

캐릭터에는 모션 워핑이 가능하게 하는 모션 워핑 컴포넌트와 그 헤더를 추가시켜주었다

애니메이션 몽타주에서는 모션워핑이 일어날 구간을 설정해주어야 한다

초반부에 잡는 지점까지를 모션 워핑으로 두고, 타겟을 지정해주었다

타겟이 없으면 모션 워핑이 제대로 되지 않는다

Execute 함수를 실행 시킬때는 AddOrUpadteWarpTargetFromTransform을 통해 우리가 지정한 WarpPoint의 값을 받아와 이동하게 구현하였다

AIFCharacter.cpp

void AIFCharacter::Execute()
{
	if (Target != nullptr)
	{
		Sheathe();
		AnimInstance->SetAxeHolding(false);
		AnimInstance->SetDrawState(false);
		AnimInstance->PlayExecuteMontage();
		Target->PlayExecuteVictim();
		MotionWarpingComponent->AddOrUpdateWarpTargetFromTransform(TEXT("Target"), Target->WarpPoint->GetComponentTransform());
		LevelSequencePlayer->Play();
	}
}

시퀀서 추가

여기까지 실행했을 때, 두 캐릭터가 합을 맞추며 애니메이션이 잘 나갔지만 카메라가 캐릭터의 뒤를 비추고 있고,

애니메이션이 실행되는 도중에 캐릭터가 움직일 수 있었기에 의도한 대로의 모습이 나오지 않았다

그래서 애니메이션이 실행되는 과정을 컷씬으로 만들며 자연스럽게 게임플레이와 컷씬을 연결시키기로 했다

이를 위해 시퀀서를 사용하였다

레벨 시퀀스를 생성하고, 카메라 트랙을 추가해주었다

카메라를 우클릭 후 Spawnable에서 Possessable로 바꾸고,

캐릭터의 자식으로 배치함과 동시에 위치를 현재 카메라의 위치로 두었다

Camera Cut 옵션에서 Can Blend 옵션을 활성화해 이전 카메라에서 시네카메라로 자연스럽게 블랜딩 되도록 설정해주었다

상단에 작은 삼각형을 눌러 블랜딩을 조절할 수 있다

이후에는 카메라의 키 프레임을 추가하며 박력있는 카메라 무브먼트를 만들었다

이렇게 처음 시퀀서를 만들게 되면, 카메라 액터 말고도 시퀀서를 실행해주는 액터가 생성되는데,

우리는 코드에서 실행할 것이기 때문에 그 액터는 삭제해 주었다


코드를 통해 시퀀서를 호출하려면 먼저 두가지 모듈 LevelSequenceMovieScene을 추가해주어야 한다

시퀀서는 실질적으로 플레이되는 트랙인 LevelSequence와 시퀀스를 실행시키는 LevelSequencePlayer가 필요하다

두가지를 모두 캐릭터 헤더에 추가시켜주고, LevelSequenceObjectFinder를 통해 만들어둔 시퀀스를 할당하고,

LevelSequencePlayer는 ULevelSequencePlayer의 함수인 CreateLevelSequencePlayer 를 통해 생성해주었다

AIFCharacter.cpp

void AIFCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	...
    
	FMovieSceneSequencePlaybackSettings Settings;
	Settings.bDisableLookAtInput   = true;
	Settings.bDisableMovementInput = true;
	Settings.bHideHud			   = true;

	ALevelSequenceActor* SequenceActor;

	LevelSequencePlayer = ULevelSequencePlayer::CreateLevelSequencePlayer(GetWorld(), LevelSequence, Settings, SequenceActor);
}

시퀀서를 설정할 때 Setting 항목에서 인풋과 UI를 감추는 설정을 추가해주었다

마지막으로 완성된 모습을 보자

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글