Просмотр исходного кода

feat: 添加 MassAI 的使用说明

nicetry12138 5 месяцев назад
Родитель
Сommit
9401a57385
4 измененных файлов с 356 добавлено и 0 удалено
  1. 19 0
      UE5/DS/README.md
  2. BIN
      UE5/Mass/Image/014.png
  3. BIN
      UE5/Mass/Image/015.png
  4. 337 0
      UE5/Mass/README.md

+ 19 - 0
UE5/DS/README.md

@@ -49,6 +49,23 @@ https://zhuanlan.zhihu.com/p/684597439
 
 因为 `ENetRole` 在 `AActor` 创建之初并未正确指定, 在构造函数中或者 `BeginPlayEvent` 中可能获取错误的值
 
+除了上述两种方式之外,还有一个 `World->GetNetMode()` 
+
+```cpp
+enum ENetMode
+{
+	NM_Standalone,			// 纯单机运行模式
+	NM_DedicatedServer,		// 专用服务器,无本地玩家参与游戏
+	NM_ListenServer,		// 监听服务器(玩家主机),同时具有服务器和客户端身份
+	NM_Client,				// 纯客户端,连接到远程服务器
+	NM_MAX,
+};
+```
+
+日常开发首选 `GetNetMode`,更加安全可靠,无需空指针检查
+
+使用 `IsServer` 之前,需要判断 `GetNewDriver` 是否为空
+
 ### 属性复制
 
 无论是 `RPC` 还是 **属性赋值** 都需要设置 `Replicates` 属性为 `True`
@@ -173,3 +190,5 @@ void Multiable_DrawDebug();
 > 上述所有测试,输入都是在客户端 2 输入
 
 通过上述四种情况对比,可以发现四种不同的情况,对应的四种结果
+
+

BIN
UE5/Mass/Image/014.png


BIN
UE5/Mass/Image/015.png


+ 337 - 0
UE5/Mass/README.md

@@ -589,3 +589,340 @@ enum class EMassProcessingPhase : uint8
 采用信号的订阅处理系统来协调 Entity 之间的通信,需要继承 `UMassSignalProcessorBase` 来处理特定信号,这个信号的注册需要依赖 `UMassSignalSubsystem`
 
 具体信号传递的案例可以查看 `UMassStateTreeProcessor` 类,该类并没有重写 `Execute` 函数,而是重写了 `SignalEntities` 函数,因为 `UMassSignalProcessorBase` 基类中处理了本帧收到的信号,并将影响到的 Entity 都通过调用 `SignalEntities` 传递给子类
+
+## 自己看代码
+
+以 `MassZombies` 项目为例
+
+> https://github.com/stopthem/MassZombies
+
+### 配置 AMassSpawner 
+
+![](Image/014.png)
+
+在场景中配置 `AMassSpawner` 用于生成对象 
+
+通过 `EntityTypes` 来配置
+
+```cpp
+
+/**
+ * Describes an entity type to spawn.
+ */
+USTRUCT(BlueprintType)
+struct FMassSpawnedEntityType
+{
+	GENERATED_BODY()
+
+	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mass|Spawn")
+	TSoftObjectPtr<UMassEntityConfigAsset> EntityConfig;
+
+	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Mass|Spawn", meta = (ClampMin = 0.0f, UIMin = 0.0f))
+	float Proportion = 1.0f;
+};
+```
+
+`FMassSpawnedEntityType` 用于配置一个 `Entity` 
+
+`Proportion` 用于表示一个比例,比如一次性可能需要生成多种类型的 `Entity`,每个类型的 `Entity` 占的比例是多少?这个数量是 `Proportion` 来控制的
+
+> 将 `EntityTypes` 所有 `Entity` 的 `Proportion` 求和并归一化,进而得到每个 `Entity` 的比例
+
+`EntityConfig` 是一个 `UMassEntityConfigAsset`,是一个 `UDataAsset`,用于配置每个 `Entity` 包含的 `FMassFragment` 
+
+> `FMassFragment` 前面解释过,用于表示 `ECS` 中的一个 `Component`,用于存储纯数据
+
+不过 `EntityConfig` 并没有直接配置 `FMassFragment` 而是通过 `UMassEntityTraitBase` 进行配置
+
+以 `UMassLookAtTargetTrait` 为例,通过重写 `BuildTemplate` 将所需的 `FMassFragment` 和 `FMassTag` 添加到 `BuildContext` 中 
+
+```cpp
+UCLASS(meta=(DisplayName="Look At Target"))
+class UMassLookAtTargetTrait : public UMassEntityTraitBase
+{
+	GENERATED_BODY()
+
+protected:
+
+	virtual void BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const override
+    {
+    	BuildContext.AddTag<FMassLookAtTargetTag>();
+	    BuildContext.AddFragment<FTransformFragment>();
+    }
+};
+```
+
+![](Image/015.png)
+
+在遍历完所有配置的 `Traits` 之后,会将该 `UDataAsset` 的 `GUID` 和 配置的 Traits 信息注册到 `UMassSpawnerSubsystem` 中,并构建出 `FMassEntityTemplate`
+
+```cpp
+UMassSpawnerSubsystem* SpawnerSystem = UWorld::GetSubsystem<UMassSpawnerSubsystem>(&World);
+
+// 获取 UDataAsset 文件的 GUID 和 TemplateData
+
+return TemplateRegistry.FindOrAddTemplate(TemplateID, MoveTemp(TemplateData)).Get();
+```
+
+> `FMassEntityConfig::GetOrCreateEntityTemplate` 函数源码
+
+如果该 `GUID` 在 `UMassSpawnerSubsystem` 中保存过,则从缓存中直接得到包含 `Data` 和 `GUID` 的 `FMassEntityTemplate`
+
+那么什么是 `FMassEntityTemplate` ?
+
+核心作用是将实体配置数据 (`FMassEntityTemplateData`) 与运行时实体类型 (`FMassArchetypeHandle`) 进行绑定,创建不可变的、可直接用于生成实体的模板
+
+```cpp
+struct MASSSPAWNER_API FMassEntityTemplate final : public TSharedFromThis<FMassEntityTemplate> 
+{
+private:
+	FMassEntityTemplateData TemplateData;
+	FMassArchetypeHandle Archetype;
+	FMassEntityTemplateID TemplateID;
+}
+```
+
+在构造 `FMassEntityTemplate` 对象的时候,会通过传入 `TemplateID` 和 `TemplateData` 计算得到 `Archetype` 
+
+```cpp
+const FMassArchetypeHandle ArchetypeHandle = EntityManager.CreateArchetype(GetCompositionDescriptor(), FName(GetTemplateName()));
+SetArchetype(ArchetypeHandle);
+```
+
+> `Archetype` 可以理解为原型,`Entity` 的数据构成具体包括哪些 `FMassFragment` 类型,通过 `Archetype` 可以知道一个 `Entity` 所需的内存大小是多少,进而计算得到一个 `Chunk` (内存块) 可以存储多少个 `Entity` 的数据
+
+### UMassProcessor
+
+`ECS` 是由 `Entity` + `Component` + `System` 组成
+
+- `Entity` 表示一个实体
+- `Component` 用于存储实体的数据,通常一个 `Entity` 对应多个 `Component`
+- `System` 用于处理数据
+
+对应 `MassAI` 来说
+
+`FMassArchetypeData` 可以表示 `Entity` 中具有哪些 `Component`,并且存储这些 `Component` 的内容到 `Chunk` 中
+
+> 通常来说,一个 `Chunk` 的大小是 128KB
+
+```cpp
+namespace UE::Mass
+{
+	constexpr int32 ChunkSize = 128*1024;
+}
+```
+
+`FMassFragment` 则表示一个 `Component`,用于存储一个纯信息
+
+那么 `System` 是什么?
+
+在 `MassAI` 中,使用 `UMassProcessor` 来处理数据
+
+> `Processor` 加工; 数据处理器
+
+```cpp
+UCLASS(abstract, EditInlineNew, CollapseCategories, config = Mass, defaultconfig, ConfigDoNotCheckDefaults)
+class MASSENTITY_API UMassProcessor : public UObject
+{
+// 常用函数
+public:
+	virtual void Initialize(UObject& Owner) {}
+	virtual void PostInitProperties() override;
+
+	virtual void DebugOutputDescription(FOutputDevice& Ar, int32 Indent = 0) const;
+	virtual FString GetProcessorName() const { return GetName(); }
+	virtual void ExportRequirements(FMassExecutionRequirements& OutRequirements) const;
+	virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context);
+	virtual void ConfigureQueries() override;
+
+// 常用属性
+private:
+	UPROPERTY(EditAnywhere, Category = "Pipeline", meta = (Bitmask, BitmaskEnum = "/Script/MassEntity.EProcessorExecutionFlags"), config)
+	int32 ExecutionFlags;
+
+	UPROPERTY(EditDefaultsOnly, Category = Processor, config)
+	EMassProcessingPhase ProcessingPhase = EMassProcessingPhase::PrePhysics;
+
+    UPROPERTY(EditDefaultsOnly, Category = Processor, config)
+	FMassProcessorExecutionOrder ExecutionOrder;
+
+	TArray<FMassEntityQuery*> OwnedQueries;
+}
+```
+
+| 属性 | 作用 | 使用示例 |
+| --- | --- | --- |
+| ExecutionFlags | 控制 Processor 执行环境,Server、Client、Standalone | ExecutionFlags((int32)(EProcessorExecutionFlags::Server | EProcessorExecutionFlags::Standalone)) |
+| ProcessingPhase  | 定义 Processor 的执行阶段 | EMassProcessingPhase::PrePhysics |
+| ExecutionOrder | 精细控制同阶段内的执行顺序 |  |
+| OwnedQueries | 存储 RegisterQuery  |  |
+
+`ProcessingPhase` 的值,有以下几种类型
+
+> 这个按需修改,通常来说都是 `PrePhysics`
+
+```cpp
+UENUM()
+enum class EMassProcessingPhase : uint8
+{
+	PrePhysics,
+	StartPhysics,
+	DuringPhysics,
+	EndPhysics,
+	PostPhysics,
+	FrameEnd,
+	MAX,
+};
+```
+
+`ExecutionOrder` 的值有以下几种
+
+```cpp
+namespace UE::Mass::ProcessorGroupNames
+{
+	const FName UpdateWorldFromMass = FName(TEXT("UpdateWorldFromMass"));
+	const FName SyncWorldToMass = FName(TEXT("SyncWorldToMass"));
+	const FName Behavior = FName(TEXT("Behavior"));
+	const FName Tasks = FName(TEXT("Tasks"));
+	const FName Avoidance = FName(TEXT("Avoidance"));
+	const FName Movement = FName(TEXT("Movement"));
+}
+```
+
+#### 构造函数
+
+以 `UMoveToPlayerProcessor` 为例
+
+设置 自动注册 (`bAutoRegisterWithProcessingPhases`)
+
+设定执行顺序在 `Avodiance` 和 `Movement` 之前
+
+```cpp
+UMoveToPlayerProcessor::UMoveToPlayerProcessor()
+{
+	// Automatically create a instance
+	bAutoRegisterWithProcessingPhases = true;
+
+	// Execute before avoidance and movement because its best that we set our goal before
+	ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Avoidance);
+	ExecutionOrder.ExecuteBefore.Add(UE::Mass::ProcessorGroupNames::Movement);
+}
+```
+
+除了 `ExecuteBefore` 之外,还有几个
+
+```cpp
+
+USTRUCT()
+struct FMassProcessorExecutionOrder
+{
+	GENERATED_BODY()
+
+	/** Determines which processing group this processor will be placed in. Leaving it empty ("None") means "top-most group for my ProcessingPhase" */
+	UPROPERTY(EditAnywhere, Category = Processor, config)
+	FName ExecuteInGroup = FName();
+
+	UPROPERTY(EditAnywhere, Category = Processor, config)
+	TArray<FName> ExecuteBefore;
+
+	UPROPERTY(EditAnywhere, Category = Processor, config)
+	TArray<FName> ExecuteAfter;
+};
+```
+
+`ExecutionOrder` 的作用主要是为了排序,给各个 Processor 指定执行顺序
+
+#### ConfigureQueries
+
+以 `UMassLookAtProcessor::ConfigureQueries` 为例
+
+```cpp
+void UMassLookAtProcessor::ConfigureQueries()
+{
+	EntityQuery_Conditional.AddRequirement<FMassLookAtFragment>(EMassFragmentAccess::ReadWrite);
+	EntityQuery_Conditional.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadOnly);
+	EntityQuery_Conditional.AddRequirement<FMassMoveTargetFragment>(EMassFragmentAccess::ReadOnly);
+	EntityQuery_Conditional.AddRequirement<FMassZoneGraphLaneLocationFragment>(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);
+	EntityQuery_Conditional.AddRequirement<FMassLookAtTrajectoryFragment>(EMassFragmentAccess::ReadWrite, EMassFragmentPresence::Optional);
+	EntityQuery_Conditional.AddRequirement<FMassZoneGraphShortPathFragment>(EMassFragmentAccess::ReadOnly, EMassFragmentPresence::Optional);
+	EntityQuery_Conditional.AddTagRequirement<FMassMediumLODTag>(EMassFragmentPresence::None);
+	EntityQuery_Conditional.AddTagRequirement<FMassLowLODTag>(EMassFragmentPresence::None);
+	EntityQuery_Conditional.AddTagRequirement<FMassOffLODTag>(EMassFragmentPresence::None);
+	EntityQuery_Conditional.AddChunkRequirement<FMassVisualizationChunkFragment>(EMassFragmentAccess::ReadOnly);
+	EntityQuery_Conditional.SetChunkFilter(&FMassVisualizationChunkFragment::AreAnyEntitiesVisibleInChunk);
+	EntityQuery_Conditional.AddSubsystemRequirement<UMassNavigationSubsystem>(EMassFragmentAccess::ReadOnly);
+	EntityQuery_Conditional.AddSubsystemRequirement<UZoneGraphSubsystem>(EMassFragmentAccess::ReadOnly);
+}
+```
+
+`FMassEntityQuery` 主要用于高效检索和处理满足特定条件的实体集合。通过声明式查询语法,实现了对海量实体的高性能筛选和处理,是 Mass 框架实现高效数据处理的关键机制
+
+> `MassEntityQuery` 的类型是 `FMassEntityQuery` 
+
+对 `FMassEntityQuery` 的使用,常见于下面这些
+
+| 常用接口 | 作用 |
+| --- | --- |
+| AddRequirement | 设定 `Processor` 对指定类型的 `FMassFragment` 的读写权限 |
+| AddTagRequirement | 设置查找的 FMassFragment 必须包含某些 Tag,根据参数配置进行筛选 |
+| AddChunkRequirement | 设置对 FMassChunkFragment 的读写权限 |
+| SetChunkFilter | 添加 Chunk 的过滤器 |
+| AddConstSharedRequirement | 设定对 FMassSharedFragment 的过滤 |
+| AddSharedRequirement |  |
+| AddSubsystemRequirement | 设置对 Subsystem 的读写权限 |
+| RegisterWithProcessor | 注册这个 FMassEntityQuery  |
+
+通过前面这些对 `Requirement` 的设定,通过 ForEach 操作可以在 `MassEntityManager` 中获取符合条件的 `MassEntity` 并对其进行相应的操作
+
+至于查询条件枚举,内容如下
+
+```cpp
+UENUM()
+enum class EMassFragmentPresence : uint8
+{
+	/** All of the required fragments must be present */
+	All,
+	/** One of the required fragments must be present */
+	Any,
+	/** None of the required fragments can be present */
+	None,
+	/** If fragment is present we'll use it, but it missing stop processing of a given archetype */
+	Optional,
+	MAX
+};
+```
+
+| 枚举类型 | 作用 
+| --- | --- |
+| All | 必须存在 |
+| Any | 至少存在一个 |
+| None | 禁止存在 |
+| Optional | 可选存在,存在时会被使用,不存在不影响匹配 |
+
+#### Execute
+
+```cpp
+const UMassNavigationSubsystem& MassNavSystem = Context.GetSubsystemChecked<UMassNavigationSubsystem>();
+const UZoneGraphSubsystem& ZoneGraphSubsystem = Context.GetSubsystemChecked<UZoneGraphSubsystem>();
+
+const int32 NumEntities = Context.GetNumEntities();
+const TArrayView<FMassLookAtFragment> LookAtList = Context.GetMutableFragmentView<FMassLookAtFragment>();
+const TConstArrayView<FTransformFragment> TransformList = Context.GetFragmentView<FTransformFragment>();
+const TConstArrayView<FMassMoveTargetFragment> MoveTargetList = Context.GetFragmentView<FMassMoveTargetFragment>();
+const TConstArrayView<FMassZoneGraphLaneLocationFragment> ZoneGraphLocationList = Context.GetFragmentView<FMassZoneGraphLaneLocationFragment>();
+const TConstArrayView<FMassZoneGraphShortPathFragment> ShortPathList = Context.GetFragmentView<FMassZoneGraphShortPathFragment>();
+const TArrayView<FMassLookAtTrajectoryFragment> LookAtTrajectoryList = Context.GetMutableFragmentView<FMassLookAtTrajectoryFragment>();
+```
+
+通过 `Context` 参数,可以通过不同的接口,获取不同的内容
+
+- 因为 `FMassLookAtFragment` 的读写权限是 `ReadWrite`,使用 `GetMutableFragmentView` 获取
+
+- 因为 `FTransformFragment` 的读写权限是 `ReadOnly`,使用 `GetFragmentView` 获取
+
+- 因为 `UZoneGraphSubsystem` 的读写权限是 `ReadOnly`,使用 `GetSubsystemChecked` 获取
+
+- 如果 `Subsystem` 的读写权限是 `ReadWrite`,使用 `GetMutableSubsystem` 获取
+
+- 如果想要获取 `Chunk` 也可以通过 `GetChunkFragmentPtr` 和 `GetChunkFragment` 两个接口
+