UE5 Mass初体验

核心概念

复习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 Avoidance Overview

车辆移动

  • 主角附近使用真实的车辆进行完全物理模拟

修复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

资料推荐

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值