Created
September 23, 2017 19:43
-
-
Save hach-que/11f2cf003ba61027f2d84fac1f3c8e2b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright Redpoint Games Pty Ltd 2017 All Rights Reserved. Some portions of this code are from Rama's Victory BP Library, which is licensed separately. | |
#include "SublevelComponent.h" | |
#include "Misc/PackageName.h" | |
#include "Engine/World.h" | |
#include "UnrealNetwork.h" | |
USublevelComponent::USublevelComponent() | |
{ | |
PrimaryComponentTick.bCanEverTick = true; | |
} | |
void USublevelComponent::EndPlay(const EEndPlayReason::Type type) | |
{ | |
if (!CurrentLevelStreamInfo.PackageNameToLoad.IsNone()) | |
{ | |
RemoveFromStreamingLevels(CurrentLevelStreamInfo); | |
CurrentLevelStreamInfo.PackageNameToLoad = TEXT(""); | |
} | |
Super::EndPlay(type); | |
} | |
void USublevelComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) | |
{ | |
Super::TickComponent(DeltaTime, TickType, ThisTickFunction); | |
UWorld* const World = GetWorld(); | |
if (World->IsServer()) | |
{ | |
if (!LastSetLevelPath.Equals(LevelPath)) | |
{ | |
FString LongPackageName; | |
bool bFoundPackage = FPackageName::SearchForPackageOnDisk(LevelPath, &LongPackageName); | |
if (bFoundPackage) | |
{ | |
TargetLevelStreamInfo.PackageNameToLoad = FName(*LongPackageName); | |
LastSetLevelPath = LevelPath; | |
} | |
else | |
{ | |
TargetLevelStreamInfo.PackageNameToLoad = NAME_None; | |
} | |
} | |
TargetLevelStreamInfo.Location = GetComponentToWorld().GetLocation(); | |
TargetLevelStreamInfo.Rotation = GetComponentToWorld().GetRotation().Rotator(); | |
TargetLevelStreamInfo.bShouldBeLoaded = LevelActive; | |
TargetLevelStreamInfo.bShouldBeVisible = LevelActive; | |
TargetLevelStreamInfo.bShouldBlockOnLoad = false; | |
TargetLevelStreamInfo.LODIndex = 1; | |
if (TargetLevelStreamInfo.PackageNameToLoad != CurrentLevelStreamInfo.PackageNameToLoad || | |
TargetLevelStreamInfo.Location != CurrentLevelStreamInfo.Location || | |
TargetLevelStreamInfo.Rotation != CurrentLevelStreamInfo.Rotation || | |
TargetLevelStreamInfo.bShouldBeLoaded != CurrentLevelStreamInfo.bShouldBeLoaded || | |
TargetLevelStreamInfo.bShouldBeVisible != CurrentLevelStreamInfo.bShouldBeVisible || | |
TargetLevelStreamInfo.bShouldBlockOnLoad != CurrentLevelStreamInfo.bShouldBlockOnLoad || | |
TargetLevelStreamInfo.LODIndex != CurrentLevelStreamInfo.LODIndex) | |
{ | |
// Things are out of sync on the server! | |
if (!CurrentLevelStreamInfo.PackageNameToLoad.IsNone()) | |
{ | |
RemoveFromStreamingLevels(CurrentLevelStreamInfo); | |
CurrentLevelStreamInfo.PackageNameToLoad = NAME_None; | |
} | |
CurrentLevelStreamInfo = TargetLevelStreamInfo; | |
// Generate a new unique package name. | |
const FString ShortPackageName = FPackageName::GetShortName(CurrentLevelStreamInfo.PackageNameToLoad.ToString()); | |
const FString PackagePath = FPackageName::GetLongPackagePath(CurrentLevelStreamInfo.PackageNameToLoad.ToString()); | |
FString UniqueLevelPackageName = PackagePath + TEXT("/") + World->StreamingLevelsPrefix + ShortPackageName; | |
UniqueLevelPackageName += TEXT("_LevelInstance_") + FString::FromInt(this->GetUniqueID()); | |
CurrentLevelStreamInfo.PackageName = FName(*UniqueLevelPackageName); | |
TargetLevelStreamInfo.PackageName = CurrentLevelStreamInfo.PackageName; // Replicate to clients. | |
if (!CurrentLevelStreamInfo.PackageNameToLoad.IsNone()) | |
{ | |
AddToStreamingLevels(CurrentLevelStreamInfo); | |
} | |
} | |
} | |
else | |
{ | |
if (TargetLevelStreamInfo.PackageName != CurrentLevelStreamInfo.PackageName || | |
TargetLevelStreamInfo.PackageNameToLoad != CurrentLevelStreamInfo.PackageNameToLoad || | |
TargetLevelStreamInfo.Location != CurrentLevelStreamInfo.Location || | |
TargetLevelStreamInfo.Rotation != CurrentLevelStreamInfo.Rotation || | |
TargetLevelStreamInfo.bShouldBeLoaded != CurrentLevelStreamInfo.bShouldBeLoaded || | |
TargetLevelStreamInfo.bShouldBeVisible != CurrentLevelStreamInfo.bShouldBeVisible || | |
TargetLevelStreamInfo.bShouldBlockOnLoad != CurrentLevelStreamInfo.bShouldBlockOnLoad || | |
TargetLevelStreamInfo.LODIndex != CurrentLevelStreamInfo.LODIndex) | |
{ | |
// Things are out of sync on the client! | |
if (!CurrentLevelStreamInfo.PackageNameToLoad.IsNone() && | |
!CurrentLevelStreamInfo.PackageName.IsNone()) | |
{ | |
RemoveFromStreamingLevels(CurrentLevelStreamInfo); | |
CurrentLevelStreamInfo.PackageNameToLoad = NAME_None; | |
} | |
CurrentLevelStreamInfo = TargetLevelStreamInfo; | |
if (!CurrentLevelStreamInfo.PackageNameToLoad.IsNone() && | |
!CurrentLevelStreamInfo.PackageName.IsNone()) | |
{ | |
AddToStreamingLevels(CurrentLevelStreamInfo); | |
} | |
} | |
} | |
} | |
void USublevelComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const | |
{ | |
Super::GetLifetimeReplicatedProps(OutLifetimeProps); | |
DOREPLIFETIME(USublevelComponent, LevelPath); | |
DOREPLIFETIME(USublevelComponent, LevelActive); | |
DOREPLIFETIME(USublevelComponent, TargetLevelStreamInfo); | |
} | |
void USublevelComponent::AddToStreamingLevels(const FLevelStreamInstanceInfo& LevelInstanceInfo) | |
{ | |
bool bResult = true; | |
UWorld* const World = GetWorld(); | |
if (World != nullptr) | |
{ | |
bool bAlreadyExists = false; | |
for (auto StreamingLevel : World->StreamingLevels) | |
{ | |
if (StreamingLevel->GetWorldAssetPackageFName() == LevelInstanceInfo.PackageName) | |
{ | |
bAlreadyExists = true; | |
break; | |
} | |
} | |
if (!bAlreadyExists) | |
{ | |
FName PackageName = LevelInstanceInfo.PackageName; | |
// For PIE Networking: remap the packagename to our local PIE packagename | |
FString PackageNameStr = PackageName.ToString(); | |
if (GEngine->NetworkRemapPath(World->GetNetDriver(), PackageNameStr, true)) | |
{ | |
PackageName = FName(*PackageNameStr); | |
} | |
World->DelayGarbageCollection(); | |
// Setup streaming level object that will load specified map | |
ULevelStreamingKismet* StreamingLevel = NewObject<ULevelStreamingKismet>(World, ULevelStreamingKismet::StaticClass(), NAME_None, RF_Transient, nullptr); | |
StreamingLevel->SetWorldAssetByPackageName(PackageName); | |
StreamingLevel->LevelColor = FColor::MakeRandomColor(); | |
StreamingLevel->bShouldBeLoaded = LevelInstanceInfo.bShouldBeLoaded; | |
StreamingLevel->bShouldBeVisible = LevelInstanceInfo.bShouldBeVisible; | |
StreamingLevel->bShouldBlockOnLoad = LevelInstanceInfo.bShouldBlockOnLoad; | |
StreamingLevel->bInitiallyLoaded = true; | |
StreamingLevel->bInitiallyVisible = true; | |
// Transform | |
StreamingLevel->LevelTransform = FTransform(LevelInstanceInfo.Rotation, LevelInstanceInfo.Location); | |
// Map to Load | |
StreamingLevel->PackageNameToLoad = LevelInstanceInfo.PackageNameToLoad; | |
// Add the new level to world. | |
World->StreamingLevels.Add(StreamingLevel); | |
World->FlushLevelStreaming(EFlushLevelStreamingType::Full); | |
} | |
} | |
} | |
void USublevelComponent::RemoveFromStreamingLevels(const FLevelStreamInstanceInfo& LevelInstanceInfo) | |
{ | |
UWorld* const World = GetWorld(); | |
// Check if the world exists and we have a level to unload | |
if (World != nullptr && !LevelInstanceInfo.PackageName.IsNone()) | |
{ | |
#if WITH_EDITOR | |
// If we are using the editor we will use this lambda to remove the play in editor string | |
auto GetCorrectPackageName = [&](FName PackageName) { | |
FString PackageNameStr = PackageName.ToString(); | |
if (GEngine->NetworkRemapPath(World->GetNetDriver(), PackageNameStr, true)) | |
{ | |
PackageName = FName(*PackageNameStr); | |
} | |
return PackageName; | |
}; | |
#endif | |
// Get the package name that we want to check | |
FName PackageNameToCheck = LevelInstanceInfo.PackageName; | |
#if WITH_EDITOR | |
// Remove the play in editor string and client id to be able to use it with replication | |
PackageNameToCheck = GetCorrectPackageName(PackageNameToCheck); | |
#endif | |
// Find the level to unload | |
for (auto StreamingLevel : World->StreamingLevels) | |
{ | |
FName LoadedPackageName = StreamingLevel->GetWorldAssetPackageFName(); | |
#if WITH_EDITOR | |
// Remove the play in editor string and client id to be able to use it with replication | |
LoadedPackageName = GetCorrectPackageName(LoadedPackageName); | |
#endif | |
// If we find the level unload it and break | |
if (PackageNameToCheck == LoadedPackageName) | |
{ | |
// This unload the level | |
StreamingLevel->bShouldBeLoaded = false; | |
StreamingLevel->bShouldBeVisible = false; | |
// This removes the level from the streaming level list | |
StreamingLevel->bIsRequestingUnloadAndRemoval = true; | |
// Force a refresh of the world | |
World->FlushLevelStreaming(EFlushLevelStreamingType::Full); | |
break; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright Redpoint Games Pty Ltd 2017 All Rights Reserved. Some portions of this code are from Rama's Victory BP Library, which is licensed separately. | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "Components/SceneComponent.h" | |
#include "Engine/LevelStreamingKismet.h" | |
#include "Engine.h" | |
#include "SublevelComponent.generated.h" | |
USTRUCT(BlueprintType) | |
struct FLevelStreamInstanceInfo | |
{ | |
GENERATED_USTRUCT_BODY() | |
UPROPERTY() | |
FName PackageName; | |
UPROPERTY() | |
FName PackageNameToLoad; | |
UPROPERTY() | |
FVector Location; | |
UPROPERTY() | |
FRotator Rotation; | |
UPROPERTY() | |
uint8 bShouldBeLoaded : 1; | |
UPROPERTY() | |
uint8 bShouldBeVisible : 1; | |
UPROPERTY() | |
uint8 bShouldBlockOnLoad : 1; | |
UPROPERTY() | |
int32 LODIndex; | |
FLevelStreamInstanceInfo() {} | |
FLevelStreamInstanceInfo(ULevelStreamingKismet* LevelInstance); | |
FString ToString() const | |
{ | |
return FString::Printf(TEXT("PackageName: %s\nPackageNameToLoad:%s\nLocation:%s\nRotation:%s\nbShouldBeLoaded:%s\nbShouldBeVisible:%s\nbShouldBlockOnLoad:%s\nLODIndex:%i") | |
, *PackageName.ToString() | |
, *PackageNameToLoad.ToString() | |
, *Location.ToString() | |
, *Rotation.ToString() | |
, (bShouldBeLoaded) ? TEXT("True") : TEXT("False") | |
, (bShouldBeVisible) ? TEXT("True") : TEXT("False") | |
, (bShouldBlockOnLoad) ? TEXT("True") : TEXT("False") | |
, LODIndex); | |
} | |
}; | |
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) | |
class MINUTEOFMAYHEM_API USublevelComponent : public USceneComponent | |
{ | |
GENERATED_BODY() | |
public: | |
// Sets default values for this component's properties | |
USublevelComponent(); | |
/** The path to the level asset to manage as a sublevel instance */ | |
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Level") | |
FString LevelPath; | |
UPROPERTY(Replicated, BlueprintReadWrite, EditAnywhere, Category = "Level") | |
bool LevelActive; | |
protected: | |
virtual void EndPlay(const EEndPlayReason::Type type) override; | |
public: | |
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; | |
private: | |
UPROPERTY(Replicated) | |
FLevelStreamInstanceInfo TargetLevelStreamInfo; | |
UPROPERTY() | |
FLevelStreamInstanceInfo CurrentLevelStreamInfo; | |
FString LastSetLevelPath; | |
void AddToStreamingLevels(const FLevelStreamInstanceInfo& LevelInstanceInfo); | |
void RemoveFromStreamingLevels(const FLevelStreamInstanceInfo& LevelInstanceInfo); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment