





// 레벨 스트리밍 볼륨의 다양한 사용 목적을 정의하는 열거형
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());
}
}
}
}
}
}
}
// 아래 방법처럼 로드시키기 위해서는 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);
Level Steaming 에서 사전에 정의된 함수 호출
// 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;
}
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);
}