본문 바로가기

언리얼C++게임개발/13.프로젝트의 설정과 무한 맵의 제작

프로젝트 설정

1. 프로젝트 설정

1.1. 소스 파일 정리

언리얼 소스 코드 구조

  • Classes : 언리얼 오브젝트 관련 헤더
  • Public : 외부에 공개하는 선언 파일
  • Private : 외부에 공개하지 않는 정의 파일

 

소스 코드 폴더에 Public과 Private 폴더를 만들어 .h 파일과 .cpp 파일을 옮겨보자. 솔루션을 재생성하면 프로젝트 구성이 바뀐 것을 확인할 수 있다. Build.cs이외에는 폴더에 다 들어간다.

  • *.Target.cs : 게임 빌드와 에디터 빌드 설정 지정

 

1.2. 모듈 추가

주 게임 모듈 외에 다른 모듈을 게임 프로젝트에 추가하고 로직을 분리해 관리할 수 있다.

단, 추가 모듈은 자동으로 생성되지 않으므로 필요한 요소를 직접 추가해야 한다.

  • 모듈 폴더와 빌드 설정 파일: 모듈 폴더와 모듈명으로 된 Build.cs 파일
  • 모듈의 정의 파일: 모듈명으로 된 .cpp 파일

솔루션파일을 재생성한 후 VS에서 추가한 모듈을 빌드할 수 있도록 ArenaBattle.Target.cs 파일과 ArenaBattleEditor.Target.cs 파일 정보를 수정해야 한다. 모듈 설정까지 추가하고 나면 빌드 명령 시 새로운 모듈을 컴파일한다.

 

모듈 추가 과정

  1. Resource>Chapter13폴더에 새로운 모듈을 생성하기 위한 ArenaBattleSetting 폴더가 준비되어 있다.
  2. 모듈을 생성하기 위한 필수 파일 생성 후 Source 폴더에 복사
  3. 솔루션 파일 재생성
  4. ArenaBattle.Target.cs와 ArenaBattleEditor.Target.cs 정보 수정 후 빌드
  5. 새 DLL 파일에 모듈 정보 기입
  6. ArenaBattle 모듈의 의존성에 ArenaBattleSetting 기입
  7. Object를 부모로 하는 ABCharacterSetting 클래스 생성
    • ArenaBattleSetting 모듈에 속한 오브젝트 필요
    • 오브젝트의 모듈을 ArenaBattleSetting으로 선택
  8. 프로젝트 재생성 후 빌드

 

ArenaBattle.Target.cs

public class ArenaBattleTarget : TargetRules
{
	public ArenaBattleTarget(TargetInfo Target) : base(Target)
	{
		...

		ExtraModuleNames.AddRange( new string[] { "ArenaBattle", "ArenaBattleSetting" } );
	}
}
ArenaBattleEditor.Target.cs
public class ArenaBattleEditorTarget : TargetRules
{
	public ArenaBattleEditorTarget(TargetInfo Target) : base(Target)
	{
		...

		ExtraModuleNames.AddRange( new string[] { "ArenaBattle", "ArenaBattleSetting" } );
	}
}
ArenaBattle.uproject
{
	"FileVersion": 3,
	"EngineAssociation": "4.27",
	"Category": "",
	"Description": "",
	"Modules": [
		{
			"Name": "ArenaBattleSetting",
			"Type": "Runtime",
			// 다른 모듈모다 먼저 로딩되도록 함
			"LoadingPhase":  "PreDefault"
		},
		{
			"Name": "ArenaBattle",
			"Type": "Runtime",
			"LoadingPhase": "Default",
			"AdditionalDependencies": [
				"Engine",
				"UMG",
				"AIModule",
				// ArenaBattleSetting에 대한 의존성 부여
				"ArenaBattleSetting"
			]
		}
	]
}

컨텐츠브라우저에서 ArenaBattleSeeting폴더가 보여야함. 이전 버전에서는 이게 잘 안보였던것 같다. 

추가 모듈 로딩 결과

 


2. INI 설정

2.1. 오브젝트 기본값 지정

지금까지 애셋 정보를 생성자 코드로 지정했지만, 애셋이 변경되면 코드를 다시 만들고 컴파일해야 한다. 애셋의 효율적인 관리를 위해 새로 추가한 ABCharacterSetting 모듈에 앞으로 사용할 애셋의 목록을 보관하자. 애셋의 경로는 FSoftObjectPath 클래스를 활용해 저장한다.

언리얼 오브젝트의 기본값을 유연하게 관리하도록 INI 파일에서 기본 속성 값을 지정하는 기능을 제공한다. 애셋 경로 정보를 참조해 프로그램을 로딩할 수 있다. 기본값을 INI 파일에서 불러들이도록 설정하면 언리얼 오브젝트를 초기화할 때 해당 속성의 값을 INI 파일에서 읽어온다.

기본값을 지정해 생성하는 객체를 클래스 기본 객체라고 한다. 언리얼 엔진 초기화 시 모든 클래스 기본 객체가 메모리에 올라간다. 메모리에 올라간 클래스 기본 객체는 엔진 종료 직전까지 상주하므로 항상 객체를 활용할 수 있다.

 

INI를 활용한 기본값 지정 과정

  1. UCLASS 매크로에 config 키워드 추가
  2. 불러들일 INI 파일명 지정
  3. UPROPERTY 속성에 config 키워드 선언
  4. ArenaBattle.Build.cs 파일에 참조할 모듈 목록 추가
    • .cpp 파일에만 모듈을 사용하므로 PrivateDependencyModule 항목에 추가
  5. ABCharacter에서 ABCharacterSetting 의 애셋 목록을 얻어오는 코드 작성
    • ArenaBattleSetting 모듈이 ArenaBattle 모듈보다 빨리 로딩되므로 GetDefault 함수를 사용해 애셋 정보 불러오기 가능
      • GetDefault : 클래스 기본 객체 가져옴

 

ABCharacterSetting.h

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ABCharacterSetting.generated.h"

UCLASS(config=ArenaBattle)
class ARENABATTLESETTING_API UABCharacterSetting : public UObject
{
	GENERATED_BODY()
public:
	UABCharacterSetting();

	UPROPERTY(config)
	TArray<FSoftObjectPath> CharacterAssets;
};
 

 

ABCharacterSetting.cpp

UABCharacterSetting::UABCharacterSetting()
{

}
 

 

ArenaBattle.Build.cs

public class ArenaBattle : ModuleRules
{
	public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
	{
		...
		
		PrivateDependencyModuleNames.AddRange(new string[] { "ArenaBattleSetting" });
	}
}
 

 

ABCharacter.cpp

#include "ABCharacterSetting.h"

AABCharacter::AABCharacter()
{
    ...
        
	auto DefaultSetting = GetDefault<UABCharacterSetting>();
	if (DefaultSetting->CharacterAssets.Num() > 0)
	{
		for (auto CharacterAsset : DefaultSetting->CharacterAssets)
		{
			ABLOG(Warning, TEXT("Character Asset : %s"), *CharacterAsset.ToString());
		}
	}
}

로그파일 - INI파일에 저장된 설정 값을 로딩한 결과 화

ArenaBattle: Warning: AABPlayerController::PostInitializeComponents(14)
ArenaBattle: Warning: AABGameMode::PostLogin(24) PostLogin Begin
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Barbarous.SK_CharM_Barbarous
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/sk_CharM_Base.sk_CharM_Base
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Bladed.SK_CharM_Bladed
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Forge.SK_CharM_Forge
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_FrostGiant.SK_CharM_FrostGiant
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Golden.SK_CharM_Golden
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Natural.SK_CharM_Natural
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Pit.SK_CharM_Pit
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Ragged0.SK_CharM_Ragged0
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_RaggedElite.SK_CharM_RaggedElite
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Ram.SK_CharM_Ram
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Robo.SK_CharM_Robo
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Shell.SK_CharM_Shell
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_solid.SK_CharM_solid
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Standard.SK_CharM_Standard
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Tusk.SK_CharM_Tusk
ArenaBattle: Warning: AABCharacter::AABCharacter(132) Character Asset : /Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Warrior.SK_CharM_Warrior

 

INI파일 설정값 로딩 결과

 

2.2. 캐릭터 애셋 로딩Permalink

애셋 경로를 통해 NPC가 생성될 때 랜덤하게 목록 중 하나를 골라 캐릭터 애셋을 로딩하는 기능을 만들 수 있다. ABGameInstance 클래스에 FStreamableManager 클래스를 멤버 변수로 선언해 애셋 로딩 명령을 내린다.

  • FStreamableManager : 게임 진행 도중 비동기 방식으로 애셋 로딩
    • 프로젝트에서 하나만 활성화하는 것을 권장
    • AsyncLoad : 비동기 방식 에셋 로딩 명령
👀싱글톤 설정
게임 인스턴스는 싱글톤처럼 동작해 FStreamableManager 클래스를 멤버 변수로 선언했지만 별도로 싱글톤으로 지정할 수도 있다.
프로젝트 설정 -> Default Classes 섹션의 고급 섹션에서 설정 가능하다!

 

ABGameInstance.h

#include "Engine/StreamableManager.h"

class ARENABATTLE_API UABGameInstance : public UGameInstance
{
	GENERATED_BODY()

public:
	...

	FStreamableManager StreamableManager;
}
 

 

ABCharacter.h

class ARENABATTLE_API AABCharacter : public ACharacter
{
private:
	...

	void OnAssetLoadComplete();
private:
	FSoftObjectPath CharacterAssetToLoad = FSoftObjectPath(nullptr);
	TSharedPtr<struct FStreamableHandle> AssetStreamingHandle;
}
 

ABCharacter.cpp

#include "ABGameInstance.h"

void AABCharacter::BeginPlay()
{
	Super::BeginPlay();

	/*auto CharacterWidget = Cast<UABCharacterWidget>(HPBarWidget->GetUserWidgetObject());
	if (nullptr != CharacterWidget)
	{
		CharacterWidget->BindCharacterStat(CharacterStat);
	}*/
	if (!IsPawnControlled())
	{
		auto DefaultSetting = GetDefault<UABCharacterSetting>();
		int32 RandIndex = FMath::RandRange(0, DefaultSetting->CharacterAssets.Num() - 1);
		CharacterAssetToLoad = DefaultSetting->CharacterAssets[RandIndex];

		auto ABGameInstance = Cast<UABGameInstance>(GetGameInstance());
		if (nullptr != ABGameInstance)
		{
			AssetStreamingHandle = ABGameInstance->StreamableManager.RequestAsyncLoad(CharacterAssetToLoad, FStreamableDelegate::CreateUObject(this, &AABCharacter::OnAssetLoadCompleted));
		}
	}
}

void AABCharacter::OnAssetLoadCompleted()
{
	USkeletalMesh* AssetLoaded = Cast<USkeletalMesh>(AssetStreamingHandle->GetLoadedAsset());
	AssetStreamingHandle.Reset();
	if (nullptr != AssetLoaded)
	{
		GetMesh()->SetSkeletalMesh(AssetLoaded);
	}
}
 

결과 화면