언리얼 5

230913 enum 찾기 / 카메라 진동 / 몬스터 인공지능

슬뷔 2023. 9. 13. 10:49

FName vs FString

FName 

FName 은 콘텐츠 브라우저에서 애셋 이름을 지을 때, 스켈레탈 메시에서 본에 접근할 때 등등에 사용된다.

FName 은 문자열을 하나하나 비교하지 않고, 주어진 문자열을 해싱한 테이블에 저장한 후, index 로 값을 비교한다.

이 때문에 키로 FName 에 접근하는 속도가 빠르며, 스트링에서 FName 으로의 변환도 해시 테이블을 사용하므로 상대적으로 빠르다.

FName 은 대소문자를 구분하지 않고, 변경도 불가능하다.  조작할 수 없다.

void AMonster_Base::BeginPlay()
{
	Super::BeginPlay();
	
	// 데이터 입력
	// 몬스터 데이터 테이블을 가져온다.
	UDataTable* DataTable = LoadObject<UDataTable>(nullptr, TEXT("/Script/Engine.DataTable'/Game/BlueprintClass/Monster/Table/DT_Monster.DT_Monster'"));
	if (IsValid(DataTable))
	{
		const UEnum* MonEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EMON_TYPE")); // EMON_TYPE 의 enum 정보를 가져온다.

		// FName 으로 가져오기, Enum 타입 이름으로 나옴
		FName name = MonEnum->GetNameByValue((int64)m_MonType);
	}
	GetCapsuleComponent()->OnComponentHit.AddDynamic(this, &AMonster_Base::OnHit);
	GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &AMonster_Base::BeginOverlap);
	GetCapsuleComponent()->OnComponentEndOverlap.AddDynamic(this, &AMonster_Base::EndOverlap);
}

FString 

FString 은 조작이 가능한 유일한 스트링 클래스이다.

// FString 생성
FString TestFString = FString(TEXT("TestFString"));

// 뷰포트에 출력
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TestFString);

// 출력 로그에 출력
UE_LOG(MyClass, Log, TEXT("This is %s"), *TestFString);

// Printf 함수도 있다
FString::Printf(TEXT("I am %s and I am at %.2f", *GetName(), GetActorLocation().X);

Printf 함수나 UE_LOG 에 인자로 전달할 경우, FString 을 넘길 때 %s 로 받게되면 * 를 붙여 주소를 넘겨 주어야 한다.

이는 %s 가 TCHAR* 포인터 타입을 받기 때문이다.

즉, TCHAR 타입 배열의 첫번째 원소의 주소를 받는 것인데, 이는 C 에서도 문자열을 넘길 때 char * 을 받으면서 첫번째 원소의 주소를 읽는 것과 동일하다고 생각하면 된다.

FString 은 실제로 TArray<TCHAR> 을 문자열 데이터로 저장하는 Wrapper 클래스라고 생각해도 무방하다.

void AMonster_Base::BeginPlay()
{
	Super::BeginPlay();
	
	// 데이터 입력
	// 몬스터 데이터 테이블을 가져온다.
	UDataTable* DataTable = LoadObject<UDataTable>(nullptr, TEXT("/Script/Engine.DataTable'/Game/BlueprintClass/Monster/Table/DT_Monster.DT_Monster'"));
	if (IsValid(DataTable))
	{
		const UEnum* MonEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EMON_TYPE")); // EMON_TYPE 의 enum 정보를 가져온다.

		// FString 으로 가져오기, Enum 풀네임으로 나옴
		FString fString = MonEnum->GetNameStringByValue((int64)m_MonType);
	}
	GetCapsuleComponent()->OnComponentHit.AddDynamic(this, &AMonster_Base::OnHit);
	GetCapsuleComponent()->OnComponentBeginOverlap.AddDynamic(this, &AMonster_Base::BeginOverlap);
	GetCapsuleComponent()->OnComponentEndOverlap.AddDynamic(this, &AMonster_Base::EndOverlap);
}

https://docs.unrealengine.com/5.1/ko/string-handling-in-unreal-engine/

 

스트링 처리

UE4 에서 사용가능한 스트링 클래스에 대한 개요와 FName, FText, FString 에 대한 참고 안내서입니다.

docs.unrealengine.com

 

선생님이 알려주신 방법!

lass UNREAL_3TH_API AMonster_Base : public ACharacter
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Info", meta = (RowType = "MonInfo"))
	FDataTableRowHandle m_MonTableRow;
}

meta = (RowType = "MonInfo") 의 의미 ( 사용법 : meta = (RowType=”MyRowName”) )

-> to filter DataTable Assets by their row using the metadata.

pInfo = m_MonTableRow.DataTable->FindRow<FMonInfo>(m_MonTableRow.RowName, TEXT(""));

FindRow를 통해 해당 행의 이름에 해당하는 데이터들을 가져온다.

(행 하나가 우리가 작성한 구조체 단위)

 

* FDataTableRowHandle

  1. DataTable/CurveTable - 데이터를 담는 표에 대한 콘텐츠 레퍼런스다.
  2. RowName - 데이터를 구하고자 하는 행의 첫 열 이름이다.
  3. 템플릿 함수 호출에 구조체를 지정할 수 있다.

https://docs.unrealengine.com/5.1/ko/data-driven-gameplay-elements-in-unreal-engine/

 

데이터 주도형 게임플레이 요소

외부 저장 데이터를 사용하여 게임플레이 요소를 구동시키는 법입니다.

docs.unrealengine.com

 

BeginPlay 보다 앞선 함수 OnConstruction

UCLASS()
class UNREAL_3TH_API AMonster_Base : public ACharacter
{
	GENERATED_BODY()
protected:
	// 에디터 상에서 속성, 위치값이 변경될 때 호출되는 함수
	// begin play 보다 먼저 호출돼서 게임 시작하지 않아도 값 변경이 가능하다
	virtual void OnConstruction(const FTransform& transform) override;
}

위의 BeginPlay 에서 진행한 몬스터 enum 찾기를 OnConstruction 에서 진행

void AMonster_Base::OnConstruction(const FTransform& transform)
{
	// 데이터 테이블 안에서 몬스터 타입에 맞는 행 정보를 가져와서
	FMonInfo* pInfo = nullptr;

	if (IsValid(m_MonTableRow.DataTable) && !m_MonTableRow.RowName.IsNone())
	{
		pInfo = m_MonTableRow.DataTable->FindRow<FMonInfo>(m_MonTableRow.RowName, TEXT(""));
	
		// 몬스터의 m_Info 값을 넣어준다.
		if (nullptr != pInfo)
			m_Info = *pInfo;
	}
}

기능 추가

- 수류탄 던졌을 때 카메라 진동

CameraShakeBase 상속받아서 BPC_ShakeDefault 생성

void AGranade::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	m_AccTime += DeltaTime;

	if (m_LifeTime < m_AccTime)
	{
		// 폭발할 때, 카메라 쉐이크 호출
		// 게임플레이 도중 클래스 찾아오는것
		TSubclassOf<UCameraShakeBase> pShakeClass = LoadClass<UCameraShakeBase>(nullptr, TEXT("/Script/Engine.Blueprint'/Game/BlueprintClass/Effect/BPC_ShakeDefault.BPC_ShakeDefault_C'"));
		
		if (IsValid(pShakeClass))
		{
			// 주체 : 컨트롤러
			GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(pShakeClass);
		}
	}
}

몬스터 인공지능

행동트리 이용한 AIController 제작 

 

레벨에 배치된 애들은 다 Actor 에서 파생

Actor 에서 파생된 애들 중에 Pawn을 상속 받은 애들이 있다 -> Character

Character를 상속받은 BPC_Character 

 

Pawn 의 주요기능 -> Controller에 빙의할 수 있다.

 

캐릭터는 Controller 를 상속받은 PlayerController 빙의하고 있다

몬스터는 Controller 를 상속받은 AIController 가 빙의할 것이다.

 

PlayerController  vs  AIController 

PlayerController  는 Input Component 를 갖고 있어서 특정 키가 발생했을 때 일어날 동작을 바인딩해서 걸어놓을 수 있다.

ex)

void ACharacter_Base::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* InputCom = Cast<UEnhancedInputComponent>(PlayerInputComponent);

	if (nullptr == InputCom)
		return;

	if (!InputActionSetting.IsNull())
	{
		UIADataAsset* pDA = InputActionSetting.LoadSynchronous();

		for (int32 i = 0; i < pDA->IADataArr.Num(); ++i)
		{
			if (pDA->IADataArr[i].Action.IsNull())
				continue;

			switch (pDA->IADataArr[i].Type)
			{
			case EInputActionType::MOVE:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Move);
				break;
			case EInputActionType::ROTATION:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Rotation);
				break;
			case EInputActionType::JUMP:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Jump);
				break;
			case EInputActionType::SPRINT_TOGGLE:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::SprintToggle);
				break;
			case EInputActionType::ATTACK:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Attack);
				break;

			case EInputActionType::FIRE:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Fire);
				break;

			case EInputActionType::RELOAD:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Reload);
				break;			
			}			
		}
	}
}

 

AIController 는 자동으로 스스로 판단해서 움직일 수 있어야 한다.

Input Component 가 아닌 Behavior Tree Component 행동트리컴포넌트 를 갖고 있다.

state machine 을 좀 더 개선한 버전이다.

Behavior Tree 를 따라하다보면 저장해야하는데 그 때 사용하는 메모리 공간이 블랙 보드 이다.

 

Behavior Tree 와 Black Board 를 사용하려면 모듈 추가를 해야 한다.

프로젝트명.build.cs

추가한 것 :

"AIModule"   "GamePlayTasks"   "NavigationSystem"

* 추가를 하고 비쥬얼스튜디오를 끈 후, 다시 generate visual studio 를 해주어야 추가한 모듈이 적용이 된다.

public class Unreal_3th : ModuleRules
{
	public Unreal_3th(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { 
			"Core"
			, "CoreUObject"
			, "Engine"
			, "InputCore"
			, "EnhancedInput" 
			, "Niagara"
            		, "AIModule"
            		, "GamePlayTasks"
            		, "NavigationSystem"
        });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
}

1. AIController 를 상속받은 AIController_Default.h / AIController_Default.cpp 생성

#include "CoreMinimal.h"
#include "AIController.h"
#include "AIController_Default.generated.h"

UCLASS()
class UNREAL_3TH_API AAIController_Default : public AAIController
{
	GENERATED_BODY()
public:
	virtual void OnPossess(APawn* _Owner) override;
	virtual void OnUnPossess() override;
};
#include "AIController_Default.h"

void AAIController_Default::OnPossess(APawn* _Owner)
{
	Super::OnPossess(_Owner);

	// 빙의 대상(몬스터) 로 부터, 사용할 행동트리를 가져온다.
}

void AAIController_Default::OnUnPossess()
{
	Super::OnUnPossess();

}

2. 블루프린트 Behavior Tree / BlackBoard 생성

인공지능 -> 비헤이비어트리

인공지능 -> 블랙보드

Behavior Tree

Selector

- 데코레이터

노드를 

- 서비스

일정 시간마다 수행 (주기적으로 해야하는 작업)

 

Sequence