Skip to content

Instantly share code, notes, and snippets.

@hach-que
Created September 23, 2017 19:43
Show Gist options
  • Save hach-que/11f2cf003ba61027f2d84fac1f3c8e2b to your computer and use it in GitHub Desktop.
Save hach-que/11f2cf003ba61027f2d84fac1f3c8e2b to your computer and use it in GitHub Desktop.
// 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;
}
}
}
}
// 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