|
|
5 ay önce | |
|---|---|---|
| .. | ||
| Image | 5 ay önce | |
| README.md | 5 ay önce | |
UE 中 Level 是 Actor 的一种组织形式
一个 World 可以包含多个 Level,一个 Level 可以包含多个 Actor
为了创造更大的世界,资源加载和占用是巨大的,如果一开始就直接加载全部的场景,卡顿和资源消耗是不可接受的
LevelStreaming 就是用于动态加载和卸载游戏世界中的不同部分,可以保证在玩家移动时能动态加载和卸载 Level
| 参数 | 作用 |
|---|---|
| Level | 期望加载的关卡 |
| Location | 关卡原点所在的世界坐标写 |
| Rotation | 关卡旋转 |
| OptionalLevelNameOverride | 如果存在同名关卡,则本次不加载 |
| OptionalLevelStreamingClass | 继承自 ULevelStreamingDynamic 类,如果配置加载地图时 UClass 选择配置的类,默认使用 ULevelStreamingDynamic |
最后会执行 ULevelStreamingDynamic::LoadLevelInstance_Internal
// 收集信息
// 处理数据
// 存在名称 OptionalLevelNameOverride 同名 ULevelStreaming 则不加载
if (Params.World->GetStreamingLevels().ContainsByPredicate([&ModifiedLevelPackageName](ULevelStreaming* LS) { return LS && LS->GetWorldAssetPackageFName() == ModifiedLevelPackageName; }))
{
return nullptr;
}
// 创建 ULevelStreamingDynamic
UClass* LevelStreamingClass = Params.OptionalLevelStreamingClass != nullptr ? Params.OptionalLevelStreamingClass.Get() : ULevelStreamingDynamic::StaticClass();
ULevelStreamingDynamic* StreamingLevel = NewObject<ULevelStreamingDynamic>(Params.World, LevelStreamingClass, NAME_None, RF_Transient, NULL);
// 设置 StreamingLevel 参数
Params.World->AddStreamingLevel(StreamingLevel);
在 UWorld::AddStreamingLevel 中对应操作就是添加到 StreamingLevels 和 StreamingLevelsToConsider 数组中
void UWorld::AddStreamingLevel(ULevelStreaming* StreamingLevelToAdd)
{
if (StreamingLevelToAdd)
{
// 一些条件判断
StreamingLevels.Add(StreamingLevelToAdd);
FStreamingLevelPrivateAccessor::OnLevelAdded(StreamingLevelToAdd);
if (FStreamingLevelPrivateAccessor::UpdateTargetState(StreamingLevelToAdd))
{
StreamingLevelsToConsider.Add(StreamingLevelToAdd);
}
}
}
前面一顿操作也只是将 FStreamingLevelDynamic 添加到 UWorld 的容器中,并没有真正的加载到场景中
为了防止 Level 内容过多影响游戏运行帧率,UE 的解决方案还是经典 分帧
每帧 UWorld::UpdateLevelStreaming 都会遍历 StreamingLevelsToConsider 数组,从中取出待加载的 LevelStreaming,执行加载逻辑
bShouldContinueToConsider 值为 false,该 LevelStreaming 会从 StreamingLevelsToConsider 数组中删除bUpdateAgain 值判断,是否需要跳出 while 循环,执行下一个 LevelStreaming 的加载操作因为涉及删除操作,所以遍历需要后序遍历
一个 LevelStreaming 加载涉及多个阶段,每个阶段结束都会去判断一下所用时间是否超过限制时间,如果超过那么 bUpdateAgain 为 false,等待下一帧再执行后续加载阶段
for (int32 Index = StreamingLevelsToConsider.GetStreamingLevels().Num() - 1; Index >= 0; --Index)
{
// do something ...
if (ULevelStreaming* StreamingLevel = StreamingLevelsToConsider.GetStreamingLevels()[Index])
{
bool bUpdateAgain = true;
bool bShouldContinueToConsider = true;
while (bUpdateAgain && bShouldContinueToConsider)
{
bool bRedetermineTarget = false;
FStreamingLevelPrivateAccessor::UpdateStreamingState(StreamingLevel, bUpdateAgain, bRedetermineTarget);
if (bRedetermineTarget)
{
bShouldContinueToConsider = FStreamingLevelPrivateAccessor::UpdateTargetState(StreamingLevel);
}
}
if (!bShouldContinueToConsider)
{
StreamingLevelsToConsider.RemoveAt(Index);
}
}
前面 UWorld::UpdateLevelStreaming 在处理 StreamingLevelsToConsider 调用的是 FStreamingLevelPrivateAccessor::UpdateStreamingState 来更新 LevelStreaming 的状态
LevelStreaming 总共包含下面八个状态
enum class ELevelStreamingState : uint8
{
Removed,
Unloaded,
FailedToLoad,
Loading,
LoadedNotVisible,
MakingVisible,
LoadedVisible,
MakingInvisible
};
| 状态 | 作用 |
|---|---|
| Removed | 已经被删除 |
| Unloaded | 未加载 |
| FailedToLoad | 加载失败 |
| Loading | 正在被加载 |
| LoadedNotVisible | 加载完毕,但是还没显示 |
| MakingVisible | 标记想要显示 |
| LoadedVisible | 加载完毕,并且显示 |
| MakingInvisible | 标记不显示 |
从 ULevelStreaming::UpdateStreamingState 可见一斑
从 Unloaded -> Loading -> LoadedNotVisible -> MakingVisible -> LoadedVisible -> MakingInvisible -> LoadedNotVisible -> Unloaded
除了上述这些当前状态之外,还需要一个目标状态 TargetState
LoadedNotVisible
TargetState 应该是 LoadedVisibleTargetState 应该是 Unloadedenum class ELevelStreamingTargetState : uint8
{
Unloaded,
UnloadedAndRemoved,
LoadedNotVisible,
LoadedVisible,
};
代码其实写的很简单粗暴,两个 switch 通过 CurrentState 和 TargetState 决定执行哪些函数
case ELevelStreamingState::LoadedNotVisible:
switch (TargetState)
{
case ELevelStreamingTargetState::LoadedVisible:
// Do Something ...
break;
case ELevelStreamingTargetState::Unloaded:
// Do Something ...
break;
case ELevelStreamingTargetState::LoadedNotVisible:
// Do Something ...
break;
default:
ensure(false);
}
| 当前 | 目标 | 操作 | 作用 | 下一个状态 |
|---|---|---|---|---|
| Unloaded | LoadedNotVisible | UpdateStreamingState_RequestLevel | 异步加载资源 | Loading |
| Loading | 无操作 | 无操作 | 等待资源加载完毕 | LoadedNotVisible 或者 LoadedVisible |
| LoadedNotVisible | LoadedVisible | BeginClientNetVisibilityRequest | MakingVisible | |
| MakingVisible | LoadedVisible | World->AddToWorld | 加载场景对象 | LoadedVisible |
当 LevelStreaming 时 LoadedVisible 的时候,就表示加载完毕并且显示内容
当 Level 资产加载完毕之后,需要将其显示到场景中
先记录两个数据:TimeLimit 时间限制 和 StartTime 开始时间
const double StartTime = FPlatformTime::Seconds();
double TimeLimit = 0.0;
总共有六步操作
每次操作完之后都通过 IsTimeLimitExceeded 根据 StartTime 和 TimeLimit 来判断操作时间超过时间限制
Actor 的坐标,毕竟 加载时有设置 LevelStreaming 的 LocationFLevelUtils::FApplyLevelTransformParams TransformParams(Level, LevelTransform);
TransformParams.bSetRelativeTransformDirectly = true;
FLevelUtils::ApplyLevelTransform(TransformParams);
Actor 中的位置,确保新 Level 在世界原点(World Origin)下的正确位置if (WorldComposition)
{
WorldComposition->OnLevelAddedToWorld(Level);
}
do
{
Level->IncrementalUpdateComponents( NumComponentsToUpdate, bRerunConstructionScript, ContextPtr);
// Process AddPrimitives if threshold is reached
if (Context.Count() > GLevelStreamingAddPrimitiveGranularity)
{
Context.Process();
}
}
while (!Level->bAreComponentsCurrentlyRegistered && !IsTimeLimitExceeded(TEXT("updating components"), StartTime, Level, TimeLimit));
if (!Level->bAlreadyInitializedNetworkActors)
{
Level->InitializeNetworkActors();
}
else
{
Level->ClearActorsSeamlessTraveledFlag();
}
do
{
Level->RouteActorInitialize(NumActorsToProcess);
} while (!Level->IsFinishedRouteActorInitialization() && !IsTimeLimitExceeded(TEXT("routing Initialize on actors"), StartTime, Level, TimeLimit));
Actor 排序,将 WorldSettings 放在第一个,将需要联网的 Actor 放在前面,不联网的 Actor 放在后面Level->SortActorList();
Level->bAlreadySortedActorList = true;
Level->bIsVisible = true;
IStreamingManager::Get().AddLevel(Level);
FWorldDelegates::LevelAddedToWorld.Broadcast(Level, this);
BroadcastLevelsChanged();
ULevelStreaming::BroadcastLevelVisibleStatus(this, Level->GetOutermost()->GetFName(), true);