核心概念
复习ECS
ECS三剑客
- Component:数据组件,存储游戏状态
- Entity:实体,数据组件的载体
- System:系统,实现游戏规则、行为
完美诠释了:组合优于继承
Mass中的ECS
FMassEntityHandle
ECS中的E。用来唯一标识一个Entity实体的句柄。
// A handle to a lightweight entity. An entity is used in conjunction with the FMassEntityManager
// for the current world and can contain lightweight fragments.
USTRUCT()
struct FMassEntityHandle
{
GENERATED_BODY()
FMassEntityHandle() = default;
FMassEntityHandle(const int32 InIndex, const int32 InSerialNumber)
: Index(InIndex), SerialNumber(InSerialNumber)
{
}
UPROPERTY(VisibleAnywhere, Category = "Mass|Debug", Transient)
int32 Index = 0;
UPROPERTY(VisibleAnywhere, Category = "Mass|Debug", Transient)
int32 SerialNumber = 0;
// ...
}
FMassFragment
ECS中的C。一个数据片段,代表了一个最小数据单元。Fragment分三大类:FMassFragment、FMassSharedFragment、FMassChunkFragment。FMassSharedFragment通常用于配制常量参数上。
// This is the base class for all lightweight fragments
USTRUCT()
struct FMassFragment
{
GENERATED_BODY()
FMassFragment() {}
};
USTRUCT()
struct FMassChunkFragment
{
GENERATED_BODY()
FMassChunkFragment() {}
};
USTRUCT()
struct FMassSharedFragment
{
GENERATED_BODY()
FMassSharedFragment() {}
};
FMassTag
可理解为ECS中特殊的C。用作:标签、状态标记、分类。
// This is the base class for types that will only be tested for presence/absence, i.e. Tags.
// Subclasses should never contain any member properties.
USTRUCT()
struct FMassTag
{
GENERATED_BODY()
FMassTag() {}
};
UMassProcessor
ECS中的S。游戏逻辑写在这里。下面对一些关键成员变量进行解释:
- bAutoRegisterWithProcessingPhases表示是否自动注册到对应执行阶段。一般在父类Processor中会关掉自动注册,在子类中才开启自动注册。
- bRequiresGameThreadExecution表示Processor是否要求在游戏主线程中执行,默认值是false,即允许在其他线程执行。Processor中涉及到调用Actor的一些线程敏感函数的时候可以开启这个选项。
- ExecutionFlags是用来控制Processor是否在服务器、客户端、单机上执行。
- ProcessingPhase控制执行所处的阶段(PrePhysics、StartPhysics、DuringPhysics、EndPhysics、PostPhysics、FrameEnd)。
- ExecutionOrder是用来控制Processor之间执行的先后顺序的。比如A Processor要在B Processor后面执行,但是又要在C Processor前面执行。
UMassProcessor源码
UCLASS(abstract, EditInlineNew, CollapseCategories, config = Mass, defaultconfig, ConfigDoNotCheckDefaults)
class MASSENTITY_API UMassProcessor : public UObject
{
GENERATED_BODY()
public:
UMassProcessor();
UMassProcessor(const FObjectInitializer& ObjectInitializer);
/** Whether this processor should be executed on StandAlone or Server or Client */
UPROPERTY(EditAnywhere, Category = "Pipeline", meta = (Bitmask, BitmaskEnum = "/Script/MassEntity.EProcessorExecutionFlags"), config)
int32 ExecutionFlags;
/** Processing phase this processor will be automatically run as part of. */
UPROPERTY(EditDefaultsOnly, Category = Processor, config)
EMassProcessingPhase ProcessingPhase = EMassProcessingPhase::PrePhysics;
/** Configures when this given processor can be executed in relation to other processors and processing groups, within its processing phase. */
UPROPERTY(EditDefaultsOnly, Category = Processor, config)
FMassProcessorExecutionOrder ExecutionOrder;
/** Configures whether this processor should be automatically included in the global list of processors executed every tick (see ProcessingPhase and ExecutionOrder). */
UPROPERTY(EditDefaultsOnly, Category = Processor, config)
bool bAutoRegisterWithProcessingPhases = true;
UPROPERTY(EditDefaultsOnly, Category = Processor, config)
bool bRequiresGameThreadExecution = false;
};
FMassEntityQuery
FMassEntityQuery主要是用来指明Processor的执行条件,可理解为过滤器。例如,要求执行的实体必须有某一些数据片段但不能有某一个标记。
示例代码:
void UMassDebugCrowdVisualizationProcessor::ConfigureQueries()
{
EntityQuery.AddTagRequirement<FMassCrowdTag>(EMassFragmentPresence::All);
EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
EntityQuery.AddRequirement<FMassRepresentationFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FMassActorFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.RequireMutatingWorldAccess(); // due to UWorld mutable access
}
ObserverProcessor
一类特殊的Processor。主要是用来监听Entity上Fragment的变化。例如,某一个实体上动态添加了一个数据片段,会把所有监听此种变化ObserverProcessor都执行一遍。需要注意的是前面提到的控制参数ExecutionOrder、ProcessingPhase对ObserverProcessor无效。
/**
* Processor to stop and uninitialize StateTrees on entities.
*/
UCLASS()
class MASSAIBEHAVIOR_API UMassStateTreeFragmentDestructor : public UMassObserverProcessor
{
GENERATED_BODY()
public:
UMassStateTreeFragmentDestructor();
protected:
virtual void Initialize(UObject& Owner) override;
virtual void ConfigureQueries() override;
virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
FMassEntityQuery EntityQuery;
UPROPERTY(Transient)
TObjectPtr<UMassSignalSubsystem> SignalSubsystem = nullptr;
};
UMassStateTreeFragmentDestructor::UMassStateTreeFragmentDestructor()
: EntityQuery(*this)
{
ExecutionFlags = (int32)(EProcessorExecutionFlags::Standalone | EProcessorExecutionFlags::Server);
ObservedType = FMassStateTreeInstanceFragment::StaticStruct();
Operation = EMassObservedOperation::Remove;
bRequiresGameThreadExecution = true;
}
UMassSignalSubsystem
Mass中的信号系统,可理解为不带参数的事件通知。
SignalSubsystem->SignalEntity(UE::Mass::Signals::HitReceived, Entity);
UMassSignalSubsystem* SignalSubsystem = UWorld::GetSubsystem<UMassSignalSubsystem>(Owner.GetWorld());
SubscribeToSignal(*SignalSubsystem, UE::Mass::Signals::StateTreeActivate);
AMassSpawner
类似于SpawnActor用来生成Actor,AMassSpawner根据配置来生成Entity。Count表示要生成Entity的数量。EntityTypes控制每一类型Entity所用的配置以及对应权重,会根据统计的总权重来计算每一种类型Entity的具体数量。SpawnDataGenerators控制Entity的出生位置。
/** A spawner you can put on a map and configure it to spawn different things */
UCLASS(hidecategories = (Object, Actor, Input, Rendering, LOD, Cooking, Collision, HLOD, Partition))
class MASSSPAWNER_API AMassSpawner : public AActor
{
public:
/**
* Starts the spawning of all the agent types of this spawner
*/
UFUNCTION(BlueprintCallable, Category = "Spawning")
void DoSpawning();
/**
* Despawn all mass agent that was spawned by this spawner
*/
UFUNCTION(BlueprintCallable, Category = "Spawning")
void DoDespawning();
UPROPERTY(EditAnywhere, Category = "Mass|Spawn")
int32 Count;
/** Array of entity types to spawn. These define which entities to spawn. */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mass|Spawn")
TArray<FMassSpawnedEntityType> EntityTypes;
/** Array of entity spawn generators. These define where to spawn entities. */
UPROPERTY(EditAnywhere, Category = "Mass|Spawn")
TArray<FMassSpawnDataGenerator> SpawnDataGenerators;
UPROPERTY(Category = "Mass|Spawn", EditAnywhere)
uint32 bAutoSpawnOnBeginPlay : 1;
TArray<FSpawnedEntities> AllSpawnedEntities;
}
UMassEntityConfigAsset
实体配置。其实就是用来配置实体上有哪些数据片段FMassFragment,只不过用Trait包装了一组相关数据片段方便配置而已。
UMassEntityTraitBase源码如下:
/**
* Base class for Mass Entity Traits.
* An entity trait is a set of fragments that create a logical trait tha makes sense to end use (i.e. replication, visualization).
* The template building method allows to configure some fragments based on properties or cached values.
* For example, a fragment can be added based on a referenced asset, or some memory hungry settings can be
* cached and just and index stored on a fragment.
*/
UCLASS(Abstract, BlueprintType, EditInlineNew, CollapseCategories)
class MASSSPAWNER_API UMassEntityTraitBase : public UObject
{
GENERATED_BODY()
public:
/** Appends items into the entity template required for the trait. */
virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const PURE_VIRTUAL(UMassEntityTraitBase::BuildTemplate, return; );
virtual void DestroyTemplate() const {}
/** Called when all Traits have had BuildTemplate() called. */
virtual void ValidateTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const {};
};
FMassEntityManager
FMassEntityManager主要负责:FMassEntityHandle的分配,FMassArchetypeData的维护,以及FMassCommandBuffer队列的维护。
内存布局
一个Archetype对应一种数据片段组合,Chunk保证了cache友好。
借大钊的图:
FMassEntityView
FMassEntityView可以用来查看实体的数据片段。一般在Processor的Execute之外会需要用到。代码示例:
void UMassRepresentationActorManagement::ReleaseAnyActorOrCancelAnySpawning(FMassEntityManager& EntityManager, const FMassEntityHandle MassAgent)
{
FMassEntityView EntityView(EntityManager, MassAgent);
FMassActorFragment& ActorInfo = EntityView.GetFragmentData<FMassActorFragment>();
FMassRepresentationFragment& Representation = EntityView.GetFragmentData<FMassRepresentationFragment>();
UMassRepresentationSubsystem* RepresentationSubsystem = EntityView.GetSharedFragmentData<FMassRepresentationSubsystemSharedFragment>().RepresentationSubsystem;
check(RepresentationSubsystem);
ReleaseAnyActorOrCancelAnySpawning(*RepresentationSubsystem, MassAgent, ActorInfo, Representation);
}
常用接口使用示例
ExecutionContext.Defer().PushCommand<FMassCommandAddFragmentInstances>(ReservedEntity, RequestFragment, ResultFragment);
EntityManager->Defer().AddFragment<FTestFragment_Int>(Entities[i]);
Context.Defer().AddTag<FMassStateTreeActivatedTag>(Entity);
实现细节
Entity的Spawn过程
加载所有的EntityConfig
TArray<FSoftObjectPath> AssetsToLoad;
for (const FMassSpawnedEntityType& EntityType : EntityTypes)
{
if (!EntityType.IsLoaded())
{
AssetsToLoad.Add(EntityType.EntityConfig.ToSoftObjectPath());
}
}
、、、
if (AssetsToLoad.Num())
{
FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager();
StreamingHandle = StreamableManager.RequestAsyncLoad(AssetsToLoad, GenerateSpawningPoints);
}
根据权重计算SpawnDataGenerators对应的数量,遍历位置生成器生成对应权重数量的坐标信息。
auto GenerateSpawningPoints = [this, TotalSpawnCount, TotalProportion]()
{
int32 SpawnCountRemaining = TotalSpawnCount;
float ProportionRemaining = TotalProportion;
for (FMassSpawnDataGenerator& Generator : SpawnDataGenerators)
{
if (Generator.Proportion == 0.0f || ProportionRemaining <= 0.0f)
{
// If there's nothing to spawn, mark the generator done as OnSpawnDataGenerationFinished() will wait for all generators to complete before the actual spawning.
Generator.bDataGenerated = true;
continue;
}
if (Generator.GeneratorInstance)
{
const float ProportionRatio = FMath::Min(Generator.Proportion / ProportionRemaining, 1.0f);
const int32 SpawnCount = FMath::CeilToInt(SpawnCountRemaining * ProportionRatio);
FFinishedGeneratingSpawnDataSignature Delegate = FFinishedGeneratingSpawnDataSignature::CreateUObject(this, &AMassSpawner::OnSpawnDataGenerationFinished, &Generator);
Generator.GeneratorInstance->Generate(*this, EntityTypes, SpawnCount, Delegate);
SpawnCountRemaining -= SpawnCount;
ProportionRemaining -= Generator.Proportion;
}
}
};
当所有位置生成器坐标生成完毕,才正真的去生成entity。
void AMassSpawner::SpawnGeneratedEntities(TConstArrayView<FMassEntitySpawnDataGeneratorResult> Results)
{
UMassSpawnerSubsystem* SpawnerSystem = UWorld::GetSubsystem<UMassSpawnerSubsystem>(GetWorld());
if (SpawnerSystem == nullptr)
{
UE_VLOG_UELOG(this, LogMassSpawner, Error, TEXT("UMassSpawnerSubsystem missing while trying to spawn entities"));
return;
}
UWorld* World = GetWorld();
check(World);
for (const FMassEntitySpawnDataGeneratorResult& Result : Results)
{
if (Result.NumEntities <= 0)
{
continue;
}
check(EntityTypes.IsValidIndex(Result.EntityConfigIndex));
check(Result.SpawnDataProcessor != nullptr);
const FMassSpawnedEntityType& EntityType = EntityTypes[Result.EntityConfigIndex];
if (const UMassEntityConfigAsset* EntityConfig = EntityType.GetEntityConfig())
{
const FMassEntityTemplate& EntityTemplate = EntityConfig->GetOrCreateEntityTemplate(*World);
if (EntityTemplate.IsValid())
{
FSpawnedEntities& SpawnedEntities = AllSpawnedEntities.AddDefaulted_GetRef();
SpawnedEntities.TemplateID = EntityTemplate.GetTemplateID();
SpawnerSystem->SpawnEntities(EntityTemplate.GetTemplateID(), Result.NumEntities, Result.SpawnData, Result.SpawnDataProcessor, SpawnedEntities.Entities);
}
}
}
'''
}
生成代码:
SpawnerSystem->SpawnEntities(EntityTemplate.GetTemplateID(), Result.NumEntities, Result.SpawnData, Result.SpawnDataProcessor, SpawnedEntities.Entities);
继续跟进:
EntityManager->BatchCreateEntities(EntityTemplate.GetArchetype(), EntityTemplate.GetSharedFragmentValues(), NumToSpawn, SpawnedEntities);
继续:
FMassEntityManager::BatchBuildEntities
构建Archetype
// "built" entities case, this is verified during FMassArchetypeEntityCollectionWithPayload construction
FMassArchetypeHandle TargetArchetypeHandle = CreateArchetype(Composition, ArchetypeDebugName);
申请EntityHandle
// @todo optimize
for (; Index < OutEntities.Num(); ++Index)
{
FMassEntityHandle& Result = OutEntities[Index];
Result.Index = (EntityFreeIndexList.Num() > 0) ? EntityFreeIndexList.Pop(/*bAllowShrinking=*/ false) : Entities.Add();
Result.SerialNumber = SerialNumberGenerator++;
FEntityData& EntityData = Entities[Result.Index];
EntityData.CurrentArchetype = ArchetypeHandle.DataPtr;
EntityData.SerialNumber = Result.SerialNumber;
ArchetypeData.AddEntity(Result, SharedFragmentValues);
}
Archetype对应chunk内申请Entity。chunk内的实体说连续存储,如果有实体要删除会通过swap的方式跟最后一个有效实体进行交换。
int32 FMassArchetypeData::AddEntityInternal(FMassEntityHandle Entity, const FMassArchetypeSharedFragmentValues& SharedFragmentValues)
{
checkf(SharedFragmentValues.IsSorted(), TEXT("Expecting shared fragment values to be previously sorted"));
checkfSlow(SharedFragmentValues.HasExactFragmentTypesMatch(CompositionDescriptor.SharedFragments), TEXT("Expecting values for every specified shared fragment in the archetype and only those"))
int32 IndexWithinChunk = 0;
int32 AbsoluteIndex = 0;
int32 ChunkIndex = 0;
int32 EmptyChunkIndex = INDEX_NONE;
int32 EmptyAbsoluteIndex = INDEX_NONE;
FMassArchetypeChunk* DestinationChunk = nullptr;
// Check chunks for a free spot (trying to reuse the earlier ones first so later ones might get freed up)
//@TODO: This could be accelerated to include a cached index to the first chunk with free spots or similar
for (FMassArchetypeChunk& Chunk : Chunks)
{
if (Chunk.GetNumInstances() == 0)
{
// Remember first empty chunk but continue looking for a chunk that has space and same group tag
if (EmptyChunkIndex == INDEX_NONE)
{
EmptyChunkIndex = ChunkIndex;
EmptyAbsoluteIndex = AbsoluteIndex;
}
}
else if (Chunk.GetNumInstances() < NumEntitiesPerChunk && Chunk.GetSharedFragmentValues().IsEquivalent(SharedFragmentValues))
{
IndexWithinChunk = Chunk.GetNumInstances();
AbsoluteIndex += IndexWithinChunk;
Chunk.AddInstance();
DestinationChunk = &Chunk;
break;
}
AbsoluteIndex += NumEntitiesPerChunk;
++ChunkIndex;
}
if (DestinationChunk == nullptr)
{
// Check if it is a recycled chunk
if (EmptyChunkIndex != INDEX_NONE)
{
DestinationChunk = &Chunks[EmptyChunkIndex];
DestinationChunk->Recycle(ChunkFragmentsTemplate, SharedFragmentValues);
AbsoluteIndex = EmptyAbsoluteIndex;
}
else
{
DestinationChunk = &Chunks.Emplace_GetRef(GetChunkAllocSize(), ChunkFragmentsTemplate, SharedFragmentValues);
}
check(DestinationChunk);
DestinationChunk->AddInstance();
}
// Add to the table and map
EntityMap.Add(Entity.Index, AbsoluteIndex);
DestinationChunk->GetEntityArrayElementRef(EntityListOffsetWithinChunk, IndexWithinChunk) = Entity;
return AbsoluteIndex;
}
通知ObserverProcessor执行
FEntityCreationContext* CreationContext = new FEntityCreationContext(Count);
// @todo this could probably be optimized since one would assume we're adding elements to OutEntities in order.
// Then again, if that's the case, the sorting will be almost instant
new (&CreationContext->EntityCollection) FMassArchetypeEntityCollection(ArchetypeHandle, MakeArrayView(&OutEntities[OutEntities.Num() - Count], Count), FMassArchetypeEntityCollection::NoDuplicates);
if (ObserverManager.HasObserversForBitSet(ArchetypeData.GetCompositionDescriptor().Fragments, EMassObservedOperation::Add))
{
CreationContext->OnSpawningFinished = [this](FEntityCreationContext& Context){
ObserverManager.OnPostEntitiesCreated(Context.EntityCollection);
};
}
Processor间依赖的解析
FProcessorDependencySolver负责解析Processor的执行顺序。
首先把A.ExecuteBefore(B) 转换成 B.ExecuteAfter(A)。代码在:
void FProcessorDependencySolver::BuildDependencies()
解析的过程就是不断寻找没有其他依赖的Processor。代码在:
void FProcessorDependencySolver::Solve(TArray<FProcessorDependencySolver::FOrderInfo>& OutResult)
UMassAgentComponent
给Puppet添加额外的Fragments,AgentEntity则是根据EntityConfig去SpawnEntities。运行Fragment的初始化器。通过UMassActorSpawnerSubsystem生成的Actor是Puppet.
SetPuppetHandle
LOD计算
因为大量使用Template,代码阅读起来晦涩了许多。
UMassLODCollectorProcessor收集Entity到View的最近距离。
MassSimulationLOD主干核心部分代码:
void UMassSimulationLODProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SimulationLOD"))
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("PrepareExecution"));
const UMassLODSubsystem& LODSubsystem = Context.GetSubsystemChecked<UMassLODSubsystem>(EntityManager.GetWorld());
const TArray<FViewerInfo>& Viewers = LODSubsystem.GetViewers();
EntityManager.ForEachSharedFragment<FMassSimulationLODSharedFragment>([&Viewers](FMassSimulationLODSharedFragment& LODSharedFragment)
{
LODSharedFragment.LODCalculator.PrepareExecution(Viewers);
});
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("CalculateLOD"));
EntityQueryCalculateLOD.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
TConstArrayView<FMassViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
TArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
LODSharedFragment.LODCalculator.CalculateLOD(Context, ViewersInfoList, SimulationLODFragments);
});
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("AdjustDistancesAndLODFromCount"));
EntityManager.ForEachSharedFragment<FMassSimulationLODSharedFragment>([](FMassSimulationLODSharedFragment& LODSharedFragment)
{
LODSharedFragment.bHasAdjustedDistancesFromCount = LODSharedFragment.LODCalculator.AdjustDistancesFromCount();
});
EntityQueryAdjustDistances.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
TConstArrayView<FMassViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
TArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
LODSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewersInfoList, SimulationLODFragments);
});
}
UWorld* World = EntityManager.GetWorld();
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VariableTickRates"))
check(World);
const float Time = World->GetTimeSeconds();
EntityQueryVariableTick.ForEachEntityChunk(EntityManager, Context, [Time](FMassExecutionContext& Context)
{
FMassSimulationVariableTickSharedFragment& TickRateSharedFragment = Context.GetMutableSharedFragment<FMassSimulationVariableTickSharedFragment>();
TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
TArrayView<FMassSimulationVariableTickFragment> SimulationVariableTickFragments = Context.GetMutableFragmentView<FMassSimulationVariableTickFragment>();
TickRateSharedFragment.LODTickRateController.UpdateTickRateFromLOD(Context, SimulationLODFragments, SimulationVariableTickFragments, Time);
});
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SetLODTags"))
check(World);
EntityQuerySetLODTag.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
const int32 NumEntities = Context.GetNumEntities();
for (int32 Index = 0; Index < NumEntities; ++Index)
{
const FMassSimulationLODFragment& EntityLOD = SimulationLODFragments[Index];
if (EntityLOD.PrevLOD != EntityLOD.LOD)
{
const FMassEntityHandle Entity = Context.GetEntity(Index);
UE::MassLOD::PushSwapTagsCommand(Context.Defer(), Entity, EntityLOD.PrevLOD, EntityLOD.LOD);
}
}
});
}
// Optional debug display
if (UE::MassLOD::bDebugSimulationLOD)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("DebugDisplayLOD"));
EntityQuery.ForEachEntityChunk(EntityManager, Context, [World](FMassExecutionContext& Context)
{
FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
TConstArrayView<FTransformFragment> LocationList = Context.GetFragmentView<FTransformFragment>();
TConstArrayView<FMassSimulationLODFragment> SimulationLODList = Context.GetFragmentView<FMassSimulationLODFragment>();
LODSharedFragment.LODCalculator.DebugDisplayLOD(Context, SimulationLODList, LocationList, World);
});
}
}
根据LOD距离计算出一个新的LOD
// Find new LOD
EntityLOD.PrevLOD = EntityLOD.LOD;
EntityLOD.LOD = ComputeLODFromSettings<bCalculateVisibility>(EntityLOD.PrevLOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, &bIsInAVisibleRange, RuntimeData);
然后进行各级LOD的bucket中Entity的数量统计
// Accumulate in buckets
const float LODSignificance = AccumulateCountInRuntimeData<bCalculateVisibility, FLODLogic::bCalculateLODSignificance>(EntityLOD.LOD, EntityViewersInfo.ClosestViewerDistanceSq, bIsVisibleByAViewer, RuntimeData);
SetLODSignificance<FLODLogic::bCalculateLODSignificance>(EntityLOD, LODSignificance);
根据配置进行各级LOD最大数量控制,重新计算LOD
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("AdjustDistancesAndLODFromCount"));
EntityManager.ForEachSharedFragment<FMassSimulationLODSharedFragment>([](FMassSimulationLODSharedFragment& LODSharedFragment)
{
LODSharedFragment.bHasAdjustedDistancesFromCount = LODSharedFragment.LODCalculator.AdjustDistancesFromCount();
});
EntityQueryAdjustDistances.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
FMassSimulationLODSharedFragment& LODSharedFragment = Context.GetMutableSharedFragment<FMassSimulationLODSharedFragment>();
TConstArrayView<FMassViewerInfoFragment> ViewersInfoList = Context.GetFragmentView<FMassViewerInfoFragment>();
TArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
LODSharedFragment.LODCalculator.AdjustLODFromCount(Context, ViewersInfoList, SimulationLODFragments);
});
LOD tick频率控制
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("VariableTickRates"))
check(World);
const float Time = World->GetTimeSeconds();
EntityQueryVariableTick.ForEachEntityChunk(EntityManager, Context, [Time](FMassExecutionContext& Context)
{
FMassSimulationVariableTickSharedFragment& TickRateSharedFragment = Context.GetMutableSharedFragment<FMassSimulationVariableTickSharedFragment>();
TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
TArrayView<FMassSimulationVariableTickFragment> SimulationVariableTickFragments = Context.GetMutableFragmentView<FMassSimulationVariableTickFragment>();
TickRateSharedFragment.LODTickRateController.UpdateTickRateFromLOD(Context, SimulationLODFragments, SimulationVariableTickFragments, Time);
});
更新LODTag
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("SetLODTags"))
check(World);
EntityQuerySetLODTag.ForEachEntityChunk(EntityManager, Context, [](FMassExecutionContext& Context)
{
TConstArrayView<FMassSimulationLODFragment> SimulationLODFragments = Context.GetMutableFragmentView<FMassSimulationLODFragment>();
const int32 NumEntities = Context.GetNumEntities();
for (int32 Index = 0; Index < NumEntities; ++Index)
{
const FMassSimulationLODFragment& EntityLOD = SimulationLODFragments[Index];
if (EntityLOD.PrevLOD != EntityLOD.LOD)
{
const FMassEntityHandle Entity = Context.GetEntity(Index);
UE::MassLOD::PushSwapTagsCommand(Context.Defer(), Entity, EntityLOD.PrevLOD, EntityLOD.LOD);
}
}
});
交叉路口红绿灯控制
TODO
角色移动
避障
车辆移动
- 主角附近使用真实的车辆进行完全物理模拟
修复Mass的一些bug
添加已经存在的Fragment会导致当前Entity的所有ObserverProcessor被调用、生成新的Entity、使用无效的EntityHandle
https://github.com/EpicGames/UnrealEngine/pull/10255
https://github.com/EpicGames/UnrealEngine/commits/ue5-main/Engine/Plugins/Runtime/MassEntity/Source
LOD计算错误
https://github.com/EpicGames/UnrealEngine/pull/10690