[World] 레벨 스트리밍 공부

suyoung·2026년 1월 11일

UE5

목록 보기
12/12

Level Streaming 예제 및 정리

  1. 빈 캔버스가 될 persistent level 하나 생성한다.
  2. ThirdPersonMap를 persistent level에 추가한다.
    1. 레벨을 Main Level 밑으로 드래그엔 드롭하면 추가된다.
    2. 파란색 → persistent level
  3. position 조정
  4. 들어갈 수 있게 문 만들기
  5. level streaming 을 위한 로직 작성 → 간단한 예제는 블루프린트로 작성한다.
    1. level streaming volume 설치




    ⇒ Level Streaming Volume 은 스트리밍될 레벨의 Details 창에서 볼륨을 연결해주어야 한다.
  • streaming volume 안에 들어와야지 load되어지고, volume을 나가게 된면, unload 한다.
// 레벨 스트리밍 볼륨의 다양한 사용 목적을 정의하는 열거형
UENUM()
enum EStreamingVolumeUsage : int
{
	SVB_Loading,
	SVB_LoadingAndVisibility,
	SVB_VisibilityBlockingOnLoad,
	SVB_BlockingOnLoad,
	SVB_LoadingNotVisible,
	SVB_MAX,
};

UCLASS(hidecategories=(Advanced, Attachment, Collision, Volume), MinimalAPI)
class ALevelStreamingVolume : public AVolume
{
	GENERATED_UCLASS_BODY()

	/** 이 레벨 스트리밍 볼륨의 영향을 받는 레벨 이름들 */
	UPROPERTY(Category=LevelStreamingVolume, VisibleAnywhere, BlueprintReadOnly, meta=(DisplayName = "Streaming Levels"))
	TArray<FName> StreamingLevelNames;

	// true라면,
	// 에디터에서의 레벨 스트리밍 미리보기 용도로만 사용된다.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelStreamingVolume)
	uint32 bEditorPreVisOnly:1;

	/**
	 * true로 설정하면, 이 스트리밍 볼륨은 레벨 스트리밍 코드에서 무시된다.
	 * 이 옵션은 레벨과의 연결을 해제하지 않고 스트리밍 볼륨을 비활성화하거나,
	 * 레벨 스트리밍 제어를 Kismet(블루프린트) 방식과 볼륨 기반 방식 사이에서
	 * 전환하기 위해 사용된다.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelStreamingVolume)
	uint32 bDisabled:1;

	// 이 볼륨이 어떤 용도로 사용되는지를 결정한다.
	// 예를 들어, 레벨 로딩만 제어할지
	// 로딩과 가시성을 함께 제어할지
	// 도는 가시성만 제어할지(로딩 시 대기 처리 포함)등을 설정한다
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelStreamingVolume)
	TEnumAsByte<enum EStreamingVolumeUsage> StreamingUsage;

	//~ Begin UObject Interface.
	virtual ENGINE_API void PostLoad() override;
	//~ End UObject Interface.

};
  • 실제 레벨 스트리밍이 동작하는 곳
/**
 * 플레이어가 레벨 스트리밍 볼륨 안에 있는지 또는 밖에 있는지에 따라
 * 레벨 스트리밍 로드/언로드 요청을 발생시킨다
 */
void UWorld::ProcessLevelStreamingVolumes(FVector* OverrideViewLocation)
{
	if (GetWorldSettings()->bUseClientSideLevelStreamingVolumes != (GetNetMode() == NM_Client))
	{
		return;
	}

	// 스트리밍 볼륨 사용을 지연 중이라면, 지금 처리하지 않고 반환
	if( StreamingVolumeUpdateDelay > 0 )
	{
	 // 스트리밍 볼륨 업데이트를 지연시킬 프레임 수
	 // 여러 레벨을 미리 프리로드했지만, 카메라가 아직 그 위치로 이동하지 않았을 때 유용
	 // INDEX_NONE 으로 설정하면 무한히 지연된다.
		StreamingVolumeUpdateDelay--;
		return;
	}
	// Option to skip indefinitely.
	else if( StreamingVolumeUpdateDelay == INDEX_NONE )
	{
		return;
	}

	SCOPE_CYCLE_COUNTER( STAT_VolumeStreamingTickTime );

	bool bStreamingVolumesAreRelevant = false;
	// 현재 레벨의 컨트롤러를 반복문으로 가져오며,
	for (FConstPlayerControllerIterator Iterator = GetPlayerControllerIterator(); Iterator; ++Iterator)
	{
		APlayerController* PlayerActor = Iterator->Get();
		if (PlayerActor && PlayerActor->bIsUsingStreamingVolumes)
		{
			// 현재 그 플레이어가 레벨 스트리밍을 사용하는지를 확인
			bStreamingVolumesAreRelevant = true; //레벨 스트리밍을 사용한다면, bStreamingVolumesAreRelevant = true
			break;
		}
	}

	if (bStreamingVolumesAreRelevant)
	{
		// EditPreVisiOnly가 아닌 스트리밍 볼륨이 연결된 Kismet 기반 레벨 스트리밍 오브젝트 목록을
		// 먼저 구성한다.
		TArray<ULevelStreaming*> LevelStreamingObjectsWithVolumes;
		TSet<ULevelStreaming*> LevelStreamingObjectsWithVolumesOtherThanBlockingLoad;
		// StreamingLevels : 현재 스트리밍 대상으로 고려되고 있는 레벨들을
		// 우선순위에 따라 정렬한 배열
		for (ULevelStreaming* LevelStreamingObject : StreamingLevels)
		{
			if( LevelStreamingObject )
			{
				// 현재 레벨에 바인딩된 레벨 스트리밍 볼륨 -> 레벨 스트리밍을 가지고 있다면,
				for (ALevelStreamingVolume* StreamingVolume : LevelStreamingObject->EditorStreamingVolumes)
				{
					if( StreamingVolume 
					&& !StreamingVolume->bEditorPreVisOnly 
					&& !StreamingVolume->bDisabled )
					{
						LevelStreamingObjectsWithVolumes.Add(LevelStreamingObject);
						if( StreamingVolume->StreamingUsage != SVB_BlockingOnLoad )
						{
							// TSet이라 중복 아님.
							LevelStreamingObjectsWithVolumesOtherThanBlockingLoad.Add(LevelStreamingObject);
						}
						break;
					}
				}
			}
		}
		
		// 현재 플레이어의 시점을 포함하고 있는
		// 레벨 스트리밍 볼륨을 가진 레벨들의 집합
		TMap<ULevelStreaming*,FVisibleLevelStreamingSettings> VisibleLevelStreamingObjects;
		
		// 모든 플레이어를 순회하면서,
		// 플레이어의 시점을 포함하고 있는
		// 레벨 스트리밍 볼륨을 가진 레벨 스트리밍 목록을 구성한다.
		for( FConstPlayerControllerIterator Iterator = GetPlayerControllerIterator(); Iterator; ++Iterator )
		{
			APlayerController* PlayerActor = Iterator->Get();
			// 죽었을 땐 사용할 수 없고, 플레이어 컨트롤러가 생성되었을 땐 항상 true로 동작함.
			if (PlayerActor && PlayerActor->bIsUsingStreamingVolumes)
			{
				FVector ViewLocation(0,0,0);
				// 볼륨 판정에 사용할 위치를
				// 호출자가 직접 지정해서 덮어 쓸 수 있도록 한다.
				if (OverrideViewLocation)
				{
					// ProcessLevelStreamVolumes 호출하는 부분 -> Kismet 에서 함수 호출시 
					// ViewLocation 전달하는 것 같다.
					ViewLocation = *OverrideViewLocation;
				}
				else
				{
					FRotator ViewRotation(0,0,0);
					// 플레이어가 바라보고 있는 위치를 가져오는 형태구나.
					PlayerActor->GetPlayerViewPoint( ViewLocation, ViewRotation );
				}

				TMap<AVolume*,bool> VolumeMap;
				
				// 스트리밍 볼륨이 연결된 레벨들을 순회하면서,
				// 플레이어의 시점 위치(ViewLocation)가
				// 해당 레벨의 어떤 스트리밍 볼륨 안에 포함되는지를 계산한다.
				for( int32 LevelIndex = 0 ; LevelIndex < LevelStreamingObjectsWithVolumes.Num() ; ++LevelIndex )
				{
					ULevelStreaming* LevelStreamingObject = LevelStreamingObjectsWithVolumes[ LevelIndex ];
					
					// StreamingSettings는 플레이어의 시점을 포함하고 있는
					// 모든 스트리밍 볼륨들의 레벨 스트리밍 설정을 OR 연산으로 합친 결과
					FVisibleLevelStreamingSettings StreamingSettings;
					
					// 다른 플레이어에 대해 이미 계산된 레벨 스트리밍 설정이 있는지를 확인
					FVisibleLevelStreamingSettings* ExistingStreamingSettings = VisibleLevelStreamingObjects.Find( LevelStreamingObject );
					if ( ExistingStreamingSettings )
					{
						// 해당 레벨에 대해 모든 스트리밍 설정이 이미 활성화되어있다면,
						// 더이상 시점을 포함하는 볼륨을 찾지 않고 탐색을 중단
						if ( ExistingStreamingSettings->AllSettingsEnabled() )
						{
							continue;
						}
						
						// 다른 플레이어에 대해 이미 계산된 스트리밍 설정을 사용하여
						// 해당 레벨의 스트리밍 설정을 초기화한다.
						StreamingSettings = *ExistingStreamingSettings;
					}
					
					// 이 레벨에 연결된 각 레벨 스트리밍 볼륨에 대해 반복 
					for ( int32 i = 0 ; i < LevelStreamingObject->EditorStreamingVolumes.Num() ; ++i )
					{
						// 스트리밍 볼륨을 가지고 옴.
						ALevelStreamingVolume* StreamingVolume = LevelStreamingObject->EditorStreamingVolumes[i];
						if ( StreamingVolume && !StreamingVolume->bEditorPreVisOnly && !StreamingVolume->bDisabled )
						{
							bool bViewpointInVolume; //볼륨내에 viewpoint가 있는가 확인
							bool* bResult = VolumeMap.Find(StreamingVolume); //StreamingVolume이 있어?
							if ( bResult )
							{
								// 이 볼륨은 이미 다른 레벨에 대해 검사된 적이 있다.
								bViewpointInVolume = *bResult;
							}
							else
							{
								// 시점이 이 볼륨 내부에 있는지를 계산하고, 그 결과를 캐시에 저장한다.
								bViewpointInVolume = StreamingVolume->EncompassesPoint( ViewLocation );								
						
								VolumeMap.Add( StreamingVolume, bViewpointInVolume );
								INC_DWORD_STAT( STAT_VolumeStreamingChecks );
							}
							
							// 볼륨 내 시점이 들어있으면
							if ( bViewpointInVolume )
							{
								// 이 볼륨에 설정된 스트리밍 설정을 복사해 온다.
								StreamingSettings |= FVisibleLevelStreamingSettings( (EStreamingVolumeUsage) StreamingVolume->StreamingUsage );
								
								// 해당 레벨의 스트리밍 설정을 갱신한다.
								// 이 과정에서 레벨은 로드되어야 함 상태로 표시된다.
								VisibleLevelStreamingObjects.Add( LevelStreamingObject, StreamingSettings );
								
								// 모든 스트리밍 설정이 활성화되면, 뷰포인트를 포함하는 볼륨을 더이상 탐색하지 않음.
								if ( StreamingSettings.AllSettingsEnabled() )
								{
									break;
								}
							}
						}
					}
				} // for each streaming level 
			} // bIsUsingStreamingVolumes
		} // for each PlayerController
		
		// 레벨 스트리밍 볼륨의 가시성 판정 결과를 기반으로
		// 모든 스트리밍 레벨의 로딩 상태를 갱신한다.
		for( int32 LevelIndex = 0 ; LevelIndex < LevelStreamingObjectsWithVolumes.Num() ; ++LevelIndex )
		{
			ULevelStreaming* LevelStreamingObject = LevelStreamingObjectsWithVolumes[LevelIndex];
			
			// 레벨 로드 여부를 판단하고, 상태 변경알림을 위해 기존 상태를 저장한다.
			FVisibleLevelStreamingSettings* NewStreamingSettings= VisibleLevelStreamingObjects.Find( LevelStreamingObject );
			bool bShouldAffectLoading							= LevelStreamingObjectsWithVolumesOtherThanBlockingLoad.Find( LevelStreamingObject ) != nullptr;
			bool bShouldBeLoaded								= (NewStreamingSettings != nullptr);
			bool bOriginalShouldBeLoaded						= LevelStreamingObject->ShouldBeLoaded();
			bool bOriginalShouldBeVisible						= LevelStreamingObject->ShouldBeVisible();
			bool bOriginalShouldBlockOnLoad						= LevelStreamingObject->bShouldBlockOnLoad;
			int32 OriginalLODIndex								= LevelStreamingObject->GetLevelLODIndex();

			if( bShouldBeLoaded || bShouldAffectLoading )
			{
				if( bShouldBeLoaded )
				{
					// 로드시킨다.
					// 만약 bIsShouldBeLoaded 와 다를 경우 (딱 1번만 실행을 위해서 이렇게 진행하는 듯싶다.)
					// World로부터 UpdateStremingLevelShouldBeConsidered 함수를 호출한다.
					// 즉, 스트리밍 레벨에 대한 고려대상으로 레벨 스트리밍을 추가
					LevelStreamingObject->SetShouldBeLoaded(true);
					LevelStreamingObject->SetShouldBeVisible(NewStreamingSettings->ShouldBeVisible(bOriginalShouldBeVisible));
					LevelStreamingObject->bShouldBlockOnLoad	= NewStreamingSettings->ShouldBlockOnLoad();
				}
				else if (LevelStreamingObject->ShouldBeLoaded())
				{
					// 이미 로드된 상태일 때, bShouldBeLoaded = false -> 즉 언로드를 의미함.
					// 언로드 요청이 과도하게 발생하는 것을 방지한다.
					// 단, 플레이 시작 후 UnLoadCooldownTime 초 이내에는 언로드 요청을 허용한다.
					if (TimeSeconds - LevelStreamingObject->LastVolumeUnloadRequestTime > LevelStreamingObject->MinTimeBetweenVolumeUnloadRequests ||  LevelStreamingObject->LastVolumeUnloadRequestTime < 0.1f)
					{
						if (GetPlayerControllerIterator())
						{
							LevelStreamingObject->LastVolumeUnloadRequestTime = TimeSeconds;
							LevelStreamingObject->SetShouldBeLoaded(false); // 언로드 시킴
							LevelStreamingObject->SetShouldBeVisible(false);
						}
					}
				}
				
				// 로드된 결과를 bNewShouldBeLoaded/bNewShouldBeVisible 에 저장
				const bool bNewShouldBeLoaded = LevelStreamingObject->ShouldBeLoaded();
				const bool bNewShouldBeVisible = LevelStreamingObject->ShouldBeVisible();

				// 플레이어에게 변화 결과를 알려준다.
				if( bOriginalShouldBeLoaded		!= bNewShouldBeLoaded
				||	bOriginalShouldBeVisible	!= bNewShouldBeVisible
				||	bOriginalShouldBlockOnLoad	!= LevelStreamingObject->bShouldBlockOnLoad
				||  OriginalLODIndex			!= LevelStreamingObject->GetLevelLODIndex())
				{
					// 모든 플레이어들에게 돌면서,
					for( FConstPlayerControllerIterator Iterator = GetPlayerControllerIterator(); Iterator; ++Iterator )
					{
						// 각 플레이어 컨트롤러에게 
						if (APlayerController* PlayerController = Iterator->Get())
						{
							// 레벨 스트리밍 상태가 변경되었음을 전달함.
							// PlayerController -> ClientUpdateLevelStreamingStatus 호출로 
							// 클라이언트들에게 변경되었음을 전달함.
							// 내부에서 PackageName으로 스트리밍 레벨을 찾음.
							PlayerController->LevelStreamingStatusChanged( 
									LevelStreamingObject, 
									bNewShouldBeLoaded, 
									bNewShouldBeVisible,
									LevelStreamingObject->bShouldBlockOnLoad,
									LevelStreamingObject->bShouldBlockOnUnload,
									LevelStreamingObject->GetLevelLODIndex());
						}
					}
				}
			}
		}
	}
}

레벨 스트리밍 하는 방법.

  1. Streaming Volume 사용
    1. 위 함수처럼, Streaming Voulme 이 시각 내에 들어오면, 로드 언로드 하는 로직 존재
  2. 아래와 같은 코드 작성
// 아래 방법처럼 로드시키기 위해서는 LevelStreamingObject가 필수적으로 필요하다.
// LevelStreaming은 UWorld 내부에 존재하며, 아래처럼 얻어올 수 있다. 
 	TArray<ULevelStreaming*> StreamingLevel= World->GetStreamingLevels();

	for (int StreamingIdx = 0; StreamingIdx < StreamingLevel.Num(); StreamingIdx++)
	{
		if (IsValid(StreamingLevel[StreamingIdx]))
		{
		}
	}
	
// 또한, Level을 로드할 때는 true/ 언로드시 false 처리로 진행
LevelStreamingObject->SetShouldBeLoaded(false); //이 두 함수 를 변경하면?
LevelStreamingObject->SetShouldBeVisible(false);
  1. Level Steaming 에서 사전에 정의된 함수 호출

    • BP : Load Level Instance (by Name/by Object Reference)
    // SoftObjectPtr(즉, 경로만 가지고 있는 오브젝트 클래스가 전달되어진다면, 원하는 파라미터 설정)
    ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(UObject* WorldContextObject, const TSoftObjectPtr<UWorld> Level, const FTransform LevelTransform, bool& bOutSuccess, const FString& OptionalLevelNameOverride, TSubclassOf<ULevelStreamingDynamic> OptionalLevelStreamingClass, bool bLoadAsTempPackage)
    {
    	bOutSuccess = false;
    	if (Level.IsNull())
    	{
    		return nullptr;
    	}
    
    	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
    
    	FLoadLevelInstanceParams Params(World, Level.GetLongPackageName(), LevelTransform);
    	Params.OptionalLevelNameOverride = OptionalLevelNameOverride.IsEmpty() ? nullptr : &OptionalLevelNameOverride;
    	Params.OptionalLevelStreamingClass = OptionalLevelStreamingClass;
    	Params.bLoadAsTempPackage = bLoadAsTempPackage;
    	return LoadLevelInstance(Params, bOutSuccess);
    }
    
    ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstance(const FLoadLevelInstanceParams& Params, bool& bOutSuccess)
    {
    	bOutSuccess = false;
    	if (!Params.World)
    	{
    		return nullptr;
    	}
    
    	if (Params.LongPackageName.IsEmpty())
    	{
    		return nullptr;
    	}
    	// 반드시 필요한 항목 검사 후 레벨을 로드하는 방식
    	return LoadLevelInstance_Internal(Params, bOutSuccess);
    }
    
    ULevelStreamingDynamic* ULevelStreamingDynamic::LoadLevelInstance_Internal(const FLoadLevelInstanceParams& Params, bool& bOutSuccess)
    {
    	const FString PackagePath = FPackageName::GetLongPackagePath(Params.LongPackageName);
    	FString ShortPackageName = FPackageName::GetShortName(Params.LongPackageName);
    	UClass* LevelStreamingClass = Params.OptionalLevelStreamingClass != nullptr ? Params.OptionalLevelStreamingClass.Get() : ULevelStreamingDynamic::StaticClass();
    
    	if (ShortPackageName.StartsWith(Params.World->StreamingLevelsPrefix))
    	{
    		ShortPackageName.RightChopInline(Params.World->StreamingLevelsPrefix.Len(), EAllowShrinking::No);
    	}
    	
    	// PIE 전용으로 붙은 접두사를 제거해서 실제 패키지 이름으로 되돌림
    	const FString OnDiskPackageName = PackagePath + TEXT("/") + ShortPackageName;
    
    	// 로드될 패키지 이름을 결정
    	const FString LevelPackageNameStr(GetLevelInstancePackageName(Params));
    
    	const bool bNeedsUniqueTest = Params.OptionalLevelNameOverride != nullptr;
    
    	const FName UnmodifiedLevelPackageName = FName(*LevelPackageNameStr);
    #if WITH_EDITOR
    	const bool bIsPlayInEditor = Params.World->IsPlayInEditor();
    	int32 PIEInstance = INDEX_NONE;
    	if (bIsPlayInEditor)
    	{
    		const FWorldContext& WorldContext = GEngine->GetWorldContextFromWorldChecked(Params.World);
    		PIEInstance = WorldContext.PIEInstance;
    	}
    #endif
    	if (bNeedsUniqueTest)
    	{
    		FName ModifiedLevelPackageName = UnmodifiedLevelPackageName;
    #if WITH_EDITOR
    		if (bIsPlayInEditor)
    		{
    			ModifiedLevelPackageName = FName(*UWorld::ConvertToPIEPackageName(LevelPackageNameStr, PIEInstance));
    		}
    #endif
    		// 중복 스트리밍 레벨 생성 방지를 위함
    		// 해당 스트리밍 레벨이 이미 존재하는 지 여부를 확인
    		if (ULevelStreaming* const* ExistingLevelStreaming = Params.World->GetStreamingLevels().FindByPredicate([&ModifiedLevelPackageName](ULevelStreaming* LS) { return LS && LS->GetWorldAssetPackageFName() == ModifiedLevelPackageName; }))
    		{
    			// 스트리밍 레벨 재사용은 다음 조건을 모두 만족할 때만 허용한다.
    			// Params.bAllowReuseExitingLevelStreaming이 true일 때
    			// Params.World 가 게임 월드일 때
    			// 기존 LevelStreaming이 같은 클래스일때
    			// 기존 LevelStreaming이 같은 레벨의 트랜스폼을 가질때
    			// UStreamingLevel은 캐싱되어지지만, 실제 레벨에서 사용되어지는 액터는 GC에 의해 처리
    			// 그 코드는 Process... 위 코드 존재
    			ULevelStreamingDynamic* StreamingLevel = Params.bAllowReuseExitingLevelStreaming && Params.World->IsGameWorld() ? Cast<ULevelStreamingDynamic>(*ExistingLevelStreaming) : nullptr;
    			if (StreamingLevel && 
    				StreamingLevel->GetClass() == LevelStreamingClass &&
    				StreamingLevel->LevelTransform.Equals(Params.LevelTransform))
    			{
    				bOutSuccess = true; // 성공했음을 판단
    				StreamingLevel->SetShouldBeLoaded(true); //로드시킴
    				StreamingLevel->SetShouldBeVisible(Params.bInitiallyVisible);
    				StreamingLevel->SetIsRequestingUnloadAndRemoval(false);
    				UE_LOG(LogLevelStreaming, Verbose, TEXT("LoadLevelInstance found existing StreamingLevel for LevelPackageName:%s"), *ModifiedLevelPackageName.ToString());
    				return StreamingLevel;
    			}
    			else
    			{
    				UE_LOG(LogLevelStreaming, Error, TEXT("LoadLevelInstance called with a name that already exists, returning nullptr. LevelPackageName:%s"), *ModifiedLevelPackageName.ToString());
    				return nullptr;
    			}
    		}
    	}
       
      // 스트리밍 레벨 객체를 생성/초기화해서 특정 맵을 로드할 준비를 한다.
    	ULevelStreamingDynamic* StreamingLevel = NewObject<ULevelStreamingDynamic>(Params.World, LevelStreamingClass, NAME_None, RF_Transient, NULL);
    	if (Params.LevelStreamingCreatedCallback)
    	{
    		Params.LevelStreamingCreatedCallback(StreamingLevel);
    	}
    
    	FSoftObjectPath WorldAssetPath(*WriteToString<512>(UnmodifiedLevelPackageName, TEXT("."), ShortPackageName));
        StreamingLevel->SetWorldAsset(TSoftObjectPtr<UWorld>(WorldAssetPath));
    #if WITH_EDITOR
    	if (bIsPlayInEditor)
    	{
    		// Necessary for networking in PIE
    		StreamingLevel->RenameForPIE(PIEInstance);
    	}
    #endif // WITH_EDITOR
        StreamingLevel->LevelColor = FColor::MakeRandomColor();
        StreamingLevel->SetShouldBeLoaded(true);
        StreamingLevel->SetShouldBeVisible(Params.bInitiallyVisible);
        StreamingLevel->bShouldBlockOnLoad = false;
        StreamingLevel->bInitiallyLoaded = true;
        StreamingLevel->bInitiallyVisible = Params.bInitiallyVisible;
    	// Transform
        StreamingLevel->LevelTransform = Params.LevelTransform;
    	// Map to Load
        StreamingLevel->PackageNameToLoad = FName(*OnDiskPackageName);
    #if WITH_EDITOR
    	StreamingLevel->EditorPathOwner = Params.EditorPathOwner;
    #endif
        // Add the new level to world.
        Params.World->AddStreamingLevel(StreamingLevel);
          
    	bOutSuccess = true;
        return StreamingLevel;
    }	
  • C++ 전용
    • UGameplayStatics::LoadStreamLevel
void UGameplayStatics::LoadStreamLevel(const UObject* WorldContextObject, FName LevelName, bool bMakeVisibleAfterLoad, bool bShouldBlockOnLoad, FLatentActionInfo LatentInfo)
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
	{
		FLatentActionManager& LatentManager = World->GetLatentActionManager();
		if (LatentManager.FindExistingAction<FStreamLevelAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr)
		{
			FStreamLevelAction* NewAction = new FStreamLevelAction(true, LevelName, bMakeVisibleAfterLoad, bShouldBlockOnLoad, LatentInfo, World);
			LatentManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, NewAction);
		}
	}
}

void FLatentActionManager::AddNewAction(UObject* InActionObject, int32 UUID, FPendingLatentAction* NewAction)
{
	TSharedPtr<FObjectActions>& ObjectActions = ObjectToActionListMap.FindOrAdd(InActionObject);
	if (!ObjectActions.Get())
	{
		ObjectActions = MakeShareable(new FObjectActions());
	}
	ObjectActions->ActionList.Add(UUID, NewAction);

	LatentActionsChangedDelegate.Broadcast(InActionObject, ELatentActionChangeType::ActionsAdded);
}
profile
게임 클라이언트 프로그래머

0개의 댓글