PART 3 - 강의 4/4
낮/밤 시스템 구현
시간대에 따른 월드 콘텐츠 전환
01
낮/밤 콘텐츠 분리
시간대별 다른 오브젝트 표시
낮에만 활동하는 NPC, 밤에만 출현하는 몬스터 등 시간대별로 다른 콘텐츠를 Data Layer로 관리할 수 있습니다.
Data Layer 구성: DL_Environment_Day ├── 낮 활동 NPC ├── 상점 (열림) ├── 낮 전용 이펙트 └── 그림자 방향 오브젝트 DL_Environment_Night ├── 밤 활동 NPC (경비병) ├── 가로등 (켜짐) ├── 야행성 몬스터 └── 밤 전용 이펙트
02
DayNightWorldManager 구현
시간 기반 자동 전환 시스템
C++ - DayNightWorldManager.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "WorldPartition/DataLayer/DataLayerAsset.h"
#include "DayNightWorldManager.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTimeOfDayChanged, bool, bIsNight);
UCLASS()
class MYGAME_API ADayNightWorldManager : public AActor
{
GENERATED_BODY()
public:
ADayNightWorldManager();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintCallable, Category = "Day Night")
void SetTimeOfDay(float NormalizedTime); // 0.0 = 자정, 0.5 = 정오
UFUNCTION(BlueprintCallable, Category = "Day Night")
void TransitionToNight();
UFUNCTION(BlueprintCallable, Category = "Day Night")
void TransitionToDay();
UFUNCTION(BlueprintPure, Category = "Day Night")
bool IsNight() const { return bIsNight; }
UFUNCTION(BlueprintPure, Category = "Day Night")
float GetCurrentTime() const { return CurrentTimeOfDay; }
UPROPERTY(BlueprintAssignable)
FOnTimeOfDayChanged OnTimeOfDayChanged;
protected:
// 낮 전용 콘텐츠
UPROPERTY(EditAnywhere, Category = "Data Layers")
TObjectPtr<UDataLayerAsset> DayDataLayer;
// 밤 전용 콘텐츠
UPROPERTY(EditAnywhere, Category = "Data Layers")
TObjectPtr<UDataLayerAsset> NightDataLayer;
// 전환 설정
UPROPERTY(EditAnywhere, Category = "Transition")
float DawnTime = 0.25f; // 06:00
UPROPERTY(EditAnywhere, Category = "Transition")
float DuskTime = 0.75f; // 18:00
// 시간 진행 속도 (1.0 = 실시간)
UPROPERTY(EditAnywhere, Category = "Time")
float TimeScale = 60.0f; // 1분 = 1시간
UPROPERTY(EditAnywhere, Category = "Time")
bool bAutoAdvanceTime = true;
private:
float CurrentTimeOfDay = 0.5f;
bool bIsNight = false;
void UpdateWorldState();
};
C++ - DayNightWorldManager.cpp
#include "DayNightWorldManager.h"
#include "WorldPartition/DataLayer/DataLayerManager.h"
ADayNightWorldManager::ADayNightWorldManager()
{
PrimaryActorTick.bCanEverTick = true;
}
void ADayNightWorldManager::BeginPlay()
{
Super::BeginPlay();
UpdateWorldState();
}
void ADayNightWorldManager::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bAutoAdvanceTime)
{
// 시간 진행 (TimeScale에 따라)
CurrentTimeOfDay += (DeltaTime * TimeScale) / (24.0f * 3600.0f);
if (CurrentTimeOfDay >= 1.0f)
{
CurrentTimeOfDay -= 1.0f;
}
UpdateWorldState();
}
}
void ADayNightWorldManager::SetTimeOfDay(float NormalizedTime)
{
CurrentTimeOfDay = FMath::Clamp(NormalizedTime, 0.0f, 1.0f);
UpdateWorldState();
}
void ADayNightWorldManager::UpdateWorldState()
{
bool bShouldBeNight = (CurrentTimeOfDay < DawnTime || CurrentTimeOfDay > DuskTime);
if (bShouldBeNight != bIsNight)
{
bIsNight = bShouldBeNight;
if (bIsNight)
{
TransitionToNight();
}
else
{
TransitionToDay();
}
OnTimeOfDayChanged.Broadcast(bIsNight);
}
}
void ADayNightWorldManager::TransitionToNight()
{
UWorld* World = GetWorld();
if (!World) return;
UDataLayerManager* DataLayerManager =
UDataLayerManager::GetDataLayerManager(World);
if (!DataLayerManager) return;
// 낮 콘텐츠 언로드
if (DayDataLayer)
{
DataLayerManager->SetDataLayerRuntimeState(
DayDataLayer, EDataLayerRuntimeState::Unloaded, true);
}
// 밤 콘텐츠 활성화
if (NightDataLayer)
{
DataLayerManager->SetDataLayerRuntimeState(
NightDataLayer, EDataLayerRuntimeState::Activated, true);
}
UE_LOG(LogTemp, Log, TEXT("World transitioned to NIGHT"));
}
void ADayNightWorldManager::TransitionToDay()
{
UWorld* World = GetWorld();
if (!World) return;
UDataLayerManager* DataLayerManager =
UDataLayerManager::GetDataLayerManager(World);
if (!DataLayerManager) return;
// 밤 콘텐츠 언로드
if (NightDataLayer)
{
DataLayerManager->SetDataLayerRuntimeState(
NightDataLayer, EDataLayerRuntimeState::Unloaded, true);
}
// 낮 콘텐츠 활성화
if (DayDataLayer)
{
DataLayerManager->SetDataLayerRuntimeState(
DayDataLayer, EDataLayerRuntimeState::Activated, true);
}
UE_LOG(LogTemp, Log, TEXT("World transitioned to DAY"));
}
03
부드러운 전환
Loaded 상태를 활용한 즉각적 전환
C++ - 프리로드 전략
// 전환 전에 반대편 레이어를 Loaded 상태로 유지
void ADayNightWorldManager::PreloadOppositeTimeOfDay()
{
UWorld* World = GetWorld();
if (!World) return;
UDataLayerManager* Manager =
UDataLayerManager::GetDataLayerManager(World);
if (!Manager) return;
if (bIsNight)
{
// 밤일 때 낮 콘텐츠를 Loaded로 프리로드
if (DayDataLayer)
{
Manager->SetDataLayerRuntimeState(
DayDataLayer, EDataLayerRuntimeState::Loaded, true);
}
}
else
{
// 낮일 때 밤 콘텐츠를 Loaded로 프리로드
if (NightDataLayer)
{
Manager->SetDataLayerRuntimeState(
NightDataLayer, EDataLayerRuntimeState::Loaded, true);
}
}
}
// 새벽/해질녘 근처에서 프리로드 시작
void ADayNightWorldManager::UpdateWorldState()
{
// 새벽 30분 전 또는 해질녘 30분 전이면 프리로드
float PreloadOffset = 0.02f; // 약 30분
bool bNearDawn = (CurrentTimeOfDay > DawnTime - PreloadOffset &&
CurrentTimeOfDay < DawnTime);
bool bNearDusk = (CurrentTimeOfDay > DuskTime - PreloadOffset &&
CurrentTimeOfDay < DuskTime);
if (bNearDawn || bNearDusk)
{
PreloadOppositeTimeOfDay();
}
// ... 나머지 상태 업데이트 로직
}
메모리 트레이드오프
Loaded 상태로 프리로드하면 전환이 즉각적이지만 메모리 사용량이 증가합니다. 모바일 등 메모리가 제한된 환경에서는 Unloaded 상태 유지가 필요할 수 있습니다.
04
라이팅 연동
Data Layer 전환과 함께 라이팅 조정
C++
// 낮/밤 전환 시 라이팅도 함께 조정
void ADayNightWorldManager::TransitionToNight()
{
// Data Layer 전환
// ... (기존 코드)
// 라이팅 전환 (예: 타임라인 또는 머티리얼 파라미터)
if (ADirectionalLight* Sun = GetSunLight())
{
// Sun Light 비활성화 또는 강도 조절
Sun->SetActorHiddenInGame(true);
}
if (ASkyLight* SkyLight = GetSkyLight())
{
// Sky Light 강도 조절
SkyLight->GetLightComponent()->SetIntensity(0.1f);
}
// Moon Light 활성화
if (ADirectionalLight* Moon = GetMoonLight())
{
Moon->SetActorHiddenInGame(false);
}
}
// 혹은 타임라인으로 부드러운 전환
void ADayNightWorldManager::StartSmoothTransition(bool bToNight)
{
if (TransitionTimeline)
{
if (bToNight)
{
TransitionTimeline->Play();
}
else
{
TransitionTimeline->Reverse();
}
}
}
SUMMARY
핵심 요약
- DayDataLayer / NightDataLayer로 시간대별 콘텐츠 분리
- DawnTime / DuskTime으로 전환 시점 정의
- 전환 전 Loaded 상태로 프리로드하면 부드러운 전환
- OnTimeOfDayChanged 이벤트로 다른 시스템과 연동
- 라이팅, 포스트 프로세스 등과 함께 조정하여 일관된 경험
PRACTICE
도전 과제
배운 내용을 직접 실습해보세요
실습 1: 기본 낮/밤 전환 시스템
DayTime과 NightTime 두 개의 Runtime Data Layer를 생성하고, 게임 내 시간에 따라 전환하세요. 낮에는 상점 NPC와 행인, 밤에는 몬스터와 가로등이 활성화되는 시스템을 구현합니다.
실습 2: 시간대별 세분화 레이어
새벽/아침/낮/저녁/밤 5개 시간대 레이어를 구성하고, 시간 경과에 따라 순차적으로 활성/비활성화하세요. 각 시간대의 조명, NPC, 환경 효과를 다르게 설정합니다.
심화 과제
실시간 시간 흐름에 맞춰 Data Layer를 자동 전환하고, Directional Light 회전/색상, Sky Atmosphere, 볼류메트릭 포그를 연동하는 완전한 낮/밤 사이클을 구현하세요. 멀티플레이어 동기화를 고려합니다.