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, 볼류메트릭 포그를 연동하는 완전한 낮/밤 사이클을 구현하세요. 멀티플레이어 동기화를 고려합니다.