PART 3 - 강의 3/4
퀘스트 기반 월드 변형
퀘스트 진행에 따른 동적 월드 상태 관리
01
퀘스트-월드 연동 개념
스토리 진행에 따른 월드 변화
RPG에서 퀘스트 완료 시 마을이 파괴되거나 복구되는 등 월드가 변하는 경우가 많습니다. Data Layer를 활용하면 이를 효율적으로 구현할 수 있습니다.
예시: 마을 방어 퀘스트 퀘스트 시작 전: [DL_Village_Normal] = Activated [DL_Village_Attacked] = Unloaded [DL_Village_Destroyed] = Unloaded 퀘스트 진행 중: [DL_Village_Normal] = Unloaded [DL_Village_Attacked] = Activated <-- 전투 상태 [DL_Village_Destroyed] = Unloaded 퀘스트 성공: [DL_Village_Normal] = Activated <-- 복구됨 [DL_Village_Attacked] = Unloaded [DL_Village_Destroyed] = Unloaded 퀘스트 실패: [DL_Village_Normal] = Unloaded [DL_Village_Attacked] = Unloaded [DL_Village_Destroyed] = Activated <-- 파괴됨
02
QuestDataLayerManager 구현
퀘스트와 Data Layer 연결 시스템
C++ - QuestDataLayerManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "QuestDataLayerManager.generated.h"
USTRUCT(BlueprintType)
struct FQuestWorldState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName QuestID;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<TObjectPtr<UDataLayerAsset>> ActivateDataLayers;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<TObjectPtr<UDataLayerAsset>> DeactivateDataLayers;
};
UCLASS()
class MYGAME_API UQuestDataLayerManager : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// 퀘스트 진행에 따른 월드 상태 변경
UFUNCTION(BlueprintCallable, Category = "Quest|World")
void ApplyQuestWorldState(const FQuestWorldState& WorldState);
// 퀘스트 완료 시 월드 변형
UFUNCTION(BlueprintCallable, Category = "Quest|World")
void OnQuestCompleted(FName QuestID);
// 월드 상태 복원 (세이브 로드 시)
UFUNCTION(BlueprintCallable, Category = "Quest|World")
void RestoreWorldState(const TArray<FName>& CompletedQuests);
protected:
// 퀘스트 ID별 월드 상태 매핑
UPROPERTY(EditAnywhere, Category = "Quest")
TMap<FName, FQuestWorldState> QuestWorldStates;
private:
void SetDataLayerStates(
const TArray<TObjectPtr<UDataLayerAsset>>& DataLayers,
EDataLayerRuntimeState State);
};
C++ - QuestDataLayerManager.cpp
#include "QuestDataLayerManager.h"
#include "WorldPartition/DataLayer/DataLayerManager.h"
void UQuestDataLayerManager::ApplyQuestWorldState(const FQuestWorldState& WorldState)
{
UWorld* World = GetGameInstance()->GetWorld();
if (!World) return;
// 비활성화할 레이어 처리
SetDataLayerStates(WorldState.DeactivateDataLayers,
EDataLayerRuntimeState::Unloaded);
// 활성화할 레이어 처리
SetDataLayerStates(WorldState.ActivateDataLayers,
EDataLayerRuntimeState::Activated);
UE_LOG(LogTemp, Log, TEXT("Quest '%s' world state applied"),
*WorldState.QuestID.ToString());
}
void UQuestDataLayerManager::OnQuestCompleted(FName QuestID)
{
if (FQuestWorldState* WorldState = QuestWorldStates.Find(QuestID))
{
ApplyQuestWorldState(*WorldState);
}
}
void UQuestDataLayerManager::RestoreWorldState(
const TArray<FName>& CompletedQuests)
{
// 완료된 퀘스트 순서대로 월드 상태 적용
for (const FName& QuestID : CompletedQuests)
{
if (FQuestWorldState* WorldState = QuestWorldStates.Find(QuestID))
{
ApplyQuestWorldState(*WorldState);
}
}
}
void UQuestDataLayerManager::SetDataLayerStates(
const TArray<TObjectPtr<UDataLayerAsset>>& DataLayers,
EDataLayerRuntimeState State)
{
UWorld* World = GetGameInstance()->GetWorld();
if (!World) return;
UDataLayerManager* DataLayerManager =
UDataLayerManager::GetDataLayerManager(World);
if (!DataLayerManager) return;
for (const TObjectPtr<UDataLayerAsset>& DataLayer : DataLayers)
{
if (DataLayer)
{
DataLayerManager->SetDataLayerRuntimeState(DataLayer, State, true);
}
}
}
03
세이브/로드 연동
게임 저장 시 월드 상태 보존
C++
// 세이브 데이터 구조체
USTRUCT(BlueprintType)
struct FGameSaveData
{
GENERATED_BODY()
// 완료된 퀘스트 목록 (순서 보존)
UPROPERTY(SaveGame)
TArray<FName> CompletedQuests;
// 현재 활성화된 Data Layer 상태
UPROPERTY(SaveGame)
TMap<FName, EDataLayerRuntimeState> DataLayerStates;
};
// 게임 로드 시
void AMyGameMode::LoadGame(const FGameSaveData& SaveData)
{
// 퀘스트 기반 월드 상태 복원
if (UQuestDataLayerManager* QDLManager =
GetGameInstance()->GetSubsystem<UQuestDataLayerManager>())
{
QDLManager->RestoreWorldState(SaveData.CompletedQuests);
}
// 또는 직접 Data Layer 상태 복원
if (UDataLayerManager* Manager =
UDataLayerManager::GetDataLayerManager(GetWorld()))
{
for (const auto& Pair : SaveData.DataLayerStates)
{
// Data Layer Asset을 이름으로 찾아서 상태 설정
// (실제로는 Asset 참조를 저장하는 것이 더 안전)
}
}
}
설계 팁
완료된 퀘스트 목록만 저장하고, 로드 시 순서대로 월드 상태를 적용하면 데이터 무결성을 유지하기 쉽습니다. 각 Data Layer 상태를 개별 저장하면 버그 발생 가능성이 높아집니다.
04
Data Asset 기반 구성
에디터에서 퀘스트-월드 매핑 설정
C++ - 퀘스트 월드 상태 Data Asset
// 퀘스트별 월드 상태를 Data Asset으로 정의
UCLASS(BlueprintType)
class MYGAME_API UQuestWorldStateAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Quest")
FName QuestID;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World State")
TArray<TObjectPtr<UDataLayerAsset>> LayersToActivate;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World State")
TArray<TObjectPtr<UDataLayerAsset>> LayersToDeactivate;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "World State")
TArray<TObjectPtr<UDataLayerAsset>> LayersToLoad;
// 전환 설정
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Transition")
bool bShowLoadingScreen = false;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Transition")
float MinLoadingTime = 0.0f;
};
Content Browser에서 Quest World State Asset을 생성하고, 에디터에서 각 퀘스트에 대해 활성화/비활성화할 Data Layer를 설정합니다.
SUMMARY
핵심 요약
- 퀘스트 완료 시 여러 Data Layer를 동시에 전환하여 월드 변형
- FQuestWorldState로 퀘스트별 활성화/비활성화 레이어 정의
- 완료 퀘스트 목록을 저장하고 로드 시 순서대로 적용
- Data Asset으로 에디터에서 설정 가능하게 구성
- 전환 시 로딩 화면으로 UX 개선 가능
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: 퀘스트 단계별 월드 변형
퀘스트 3단계(수락 전/진행 중/완료 후)에 대응하는 3개의 Runtime Data Layer를 만들고, 각 단계에서 다른 NPC 배치와 환경을 표시하세요. 퀘스트 상태 변경 시 Data Layer를 자동 전환합니다.
실습 2: Data Layer 전환 시 시각적 효과
Data Layer 전환 시 액터가 갑자기 나타나거나 사라지는 것을 방지하기 위해, 페이드 인/아웃 효과를 구현하세요. 시퀀서 또는 코드에서 액터 Visibility를 점진적으로 변경합니다.
심화 과제
플레이어의 선택에 따라 마을이 다르게 변하는 분기형 시스템을 구현하세요. 5개 이상의 Data Layer 조합으로 다양한 월드 상태를 표현하고, 선택 이력을 저장하여 일관된 월드 상태를 유지합니다.