현재 프로젝트의 소스를 효과적으로 관리할 수 있도록 프로젝트 구조를 변경하고
게임 설정에 관련된 데이터를 별도의 모듈로 분리하는 한편,
언리얼 엔진의 설정 시스템을 이용해 게임의 기본 데이터를 INI 파일로 관리하는 방법을 학습한다.
또한, 레벨을 섹션이라는 단위로 개편하고 무한으로 증가하는 레벨을 설계한다.
<1>. 프로젝트의 정리와 모듈의 추가
- 프로젝트 정리 : 확장자가 '.h'인 헤더 파일들은 Public 폴더로, '.cpp' 파일들은 Private 폴더로 옮긴다.
- 모듈의 추가 : 지금까지 작업한 코드는 'ArenaBattle'이라는 '주 게임 모듈'에서 관리한다. 게임 세팅을 위한 별도의 게임 모듈(ArenaBattleSetting)을 생성해서 두 개의 모듈로 게임 프로젝트를 구성해 보자.
※ 추가 모듈 제작을 위해 필요한 요소는 다음과 같다.
- 모듈 폴더와 빌드 설정 파일 : 모듈 폴더와 모듈명으로 된 Build.cs 파일
- 모듈의 정의 파일 : 모듈명으로 된 .h, .cpp 파일
- 추가한 모듈을 빌드하도록 ArenaBattle.Target.cs 파일 / ArenaBattleEditor.Target.cs 파일 정보 수정. (ExtraModuleNames에 "ArenaBattleSetting" 추가하기)
빌드가 완료되면 Binaries 폴더에 다음과 같이 파일이 생성된다.
새로운 DLL 파일을 로딩하도록 명령해야 한다. 이를 위해 uproject 파일에 새로운 모듈에 대한 정보를 기입해야 한다.
{
"FileVersion": 3,
"EngineAssociation": "5.3",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "ArenaBattleSetting",
"Type": "Runtime",
"LoadingPhase": "PreDefault"
},
{
"Name": "ArenaBattle",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine",
"UMG",
"AIModule",
"ArenaBattleSetting"
]
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
}
]
}
→ 이제 ArenaBattleSetting 모듈은 항상 ArenaBattle 모듈보다 먼저 언리얼 에디터 프로세스에 올라간다.
<2>. INI 설정과 애셋의 지연 로딩
※ 새로운 모듈에 추가한 ABCharacterSetting은 앞으로 사용할 캐릭터 애셋의 목록을 보관한다.
※ INI 파일 : 응용 프로그램 초기 설정에 필요한 정보가 들어 있는 텍스트 파일.
이번엔 INI 파일을 사용해 ABCharacterSetting의 기본값을 설정하는 방법을 학습해 본다.
→ 에셋 경로 정보를 보관하기 위해 'FSoftObjectPath'라는 클래스 제공됨.
→ 언리얼 오브젝트가 기본값을 INI 파일에서 불러들이려면 UCLASS 매크로에 config 키워드를 추가해 불러들일 INI 파일의 이름을 지정하고, PROPERTY 속성에는 config 키워드를 선언해야 한다.
//ABCharacterSetting.h
UCLASS(config=ArenaBattle) //초기화 단계에서 Config 폴더에 위치한 DefaultArenaBattle.ini 파일을 읽어들임.
class ARENABATTLE_API UABCharacterSetting : public UObject
{
GENERATED_BODY()
public:
UABCharacterSetting();
UPROPERTY(config)
TArray<FSoftObjectPath> CharacterAssets; // INI파일에서 설정한 값 할당→기본 객체가 메모리에 올라감
//FSoftObjectPath : 애셋 경로 정보 보관
};
UCLASS 매크로 내 config 키워드에 있는 ArenaBattle 설정으로 인해, 초기화 단계에서 Config 폴더에 위치한 DefaultArenaBattle.ini 파일을 읽어 들여 ABCharacterSetting의 CharacterAssets 값을 설정한다.
ABCharacterSetting 클래스 기본 객체는 엔진 초기화 단계에서 생성자를 거쳐 INI에서 설정한 값이 할당된다.
GetDefault 함수를 통해 이 객체들을 가져올 수 있다.
※ ArenaBattle 모듈의 ABCharacter에서, ArenaBattleSetting 모듈의 ABCharacterSetting에서 캐릭터 에셋 목록 얻어오기.
→ ArenaBattle.Build.cs 파일에서 ArenaBattleSetting 모듈을 사용하도록 참조할 모듈 목록 추가.
//ArenaBattle.Build.cs
public class ArenaBattle:ModuleRules
{
public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
{
PrivateDependencyModuleNames.AddRange(new string[] {"ArenaBattleSetting"});
}
};
※ NPC가 생성될 때 랜덤하게 목록 중 하나를 골라 캐릭터 에셋을 로딩하는 기능을 구현해 보자.
게임 진행 중에도 비동기방식으로 에셋을 로딩하도록 'FStreamableManager'라는 클래스가 제공된다. 이 매니저 클래스는 프로젝트에서 하나만 활성화하는 것이 좋기 때문에 유일한 인스턴스로 동작하는 'ABGameInstance'에서 멤버 변수로 선언한다.
→ 비동기 방식으로 에셋을 로딩하는 명령은 'AsyncLoad'이다. 이 함수에 FStreamableDelegate 형식의 델리게이트를 넘겨줄 경우, 에셋 로딩이 완료되면 델리게이트에 연결된 함수를 호출해 준다.
※ CreateUObject : 즉석에서 델리게이트를 생성함.
//ABGameInstance.h
#include "Engine/StreamableManager.h"
class ARENABATTLE_API UABGameInstance:public UGameInstance
{
public:
FStreamableManager StreamableManager;
};
//ABCharacter.h
private:
void OnAssetLoadCompleted();
FSoftObjectPath CharacterAssetToLoad = FSoftObjectPath(nullptr);
TSharedPtr<struct FStreamable> AssetStreamingHandle;
//ABCharacter.cpp
#include "ABCharacterSetting.h"
#include "ABGameInstance.h"
void AABCharacter::BeginPlay()
{
Super::BeginPlay();
auto CharacterWidget = Cast<UABCharacterWidget>(HPBarWidget->GetUserWidgetObject());
if (nullptr != CharacterWidget)
{
CharacterWidget->BindCharacterStat(CharacterStat);
}
if(!IsPlayerControlled())
{
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);
}
}
!!! 여기서 언리얼 엔진 버전이 4.52 이상이신 분들은 HP 바 위젯이 오류가 났을 것이다!!!
책의 ABCharacter.cpp 코드에서 Super::BeginPlay(); 코드 밑에 "..."이 생략돼 있다. 즉, 기존에 있던 코드들을 지우면 안 된다. 심지어 공식 사이트 정오표에도 안 고쳐져 있다...
<3>. 무한 맵의 생성
레벨을 섹션이라는 단위로 나누고, 하나의 섹션을 클리어하면 새로운 섹션이 등장하는 무한 맵 스테이지를 제작해 보자.
※ 섹션 액터가 해야 할 일
- 섹션의 배경과 네 방향으로 캐릭터 입장을 통제하는 문 제공
- 플레이어가 섹션에 진입하면 모든 문을 닫는다
- 문이 닫히고 일정 시간 후에 중앙에서 NPC 생성
- 문이 닫히고 일정 시간 후에 아이템 상자 생성
- NPC 모두 죽으면 모든 문 개방
- 통과한 문으로 섹션이 이어짐
※ Actor를 부모클래스로 하는 'ABSection'이라는 클래스를 ArenaBattle 모듈에 생성한다.
→ 철문마다 스태틱메시 컴포넌트를 제작하고 이를 소켓에 부착한다.
→ 소켓 목록(TArray)을 제작하고 이를 사용해 철문을 각각 부착한다.
※ ABCharacter만을 감지하는 'ABTrigger' 콜리전 프리셋(Box)을 섹션의 중앙, 각 철문 영역에 부착한다.
(Trigger : 섹션을 감싸는 BoxCollision / NewGateTrigger : 철문을 감싸는 BoxCollision)
※ 섹션 액터의 로직을 스테이트들로 설계한다.
- READY 스테이트 : 시작 스테이트. 문을 열어놓고 중앙의 박스 트리거가 플레이어를 감지하면 전투 스테이트로 이동.
- BATTLE 스테이트 : 문을 닫고 NPC, 아이템 상자 소환. NPC가 죽으면 완료 스테이트로 이동.
- COMPLETE 스테이트 : 닫힌 문을 연다. 문쪽의 트리거 게이트가 플레이어 감지하면 그 방향으로 새로운 섹션 소환.
플레이하기 전에도, 문이 열려있는 상태로 보고 싶다.
→ 에디터와 연동되는 액터의 'OnConstruction' 함수를 사용한다. 에디터 작업에서 액터의 속성을 설정할 수 있다.
//ABSection.h
class ARENABATTLE_API AABSection : public AActor
{
...
public:
virtual void OnConstruction(const FTransform& Transform) override;
}
//ABSection.cpp
void AABSection::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
SetState(bNoBattle ? ESectionState::COMPLETE : ESectionState::READY);
}
※ 트리거의 로직
- 중앙 트리거 : 플레이어를 감지하면 READY에서 BATTLE로 전환.
- 철문 트리거 : 이미 섹션이 생성돼 있다면 생성하지 않음.
<4>. 내비게이션 메시 시스템 설정
섹션에서 NPC와 아이템 상자를 생성하는 기능을 추가한다. 생성될 시간을 지정할 속성을 추가하고 타이머 기능을 사용.
※ 새로운 섹션에는 내비게이션 메시가 설정되지 않기 때문에 '프로젝트 세팅→내비게이션 메시→Runtime Generation' 속성 값을 Dynamic으로 변경하면 내비게이션 메시가 실시간으로 만들어져 적용된다.
'게임 개발 > 이득우의 언리얼 C++' 카테고리의 다른 글
[이득우의 언리얼 C++ 게임개발의 정석] #15. 게임의 완성 (0) | 2024.07.05 |
---|---|
[이득우의 언리얼 C++ 게임개발의 정석] #14. 게임플레이의 제작 (0) | 2024.07.03 |
[이득우의 언리얼 C++ 게임개발의 정석] #12. AI 컨트롤러와 비헤이비어 트리 (0) | 2024.06.28 |
[이득우의 언리얼 C++ 게임개발의 정석] #11. 게임 데이터와 UI 위젯 (0) | 2024.06.26 |
[이득우의 언리얼 C++ 게임개발의 정석] #10. 아이템 상자와 무기 제작 (0) | 2024.06.25 |