案例1
https://github.com/Q1143316492/LochMassAITest
{
"FileVersion": 3,
"EngineAssociation": "5.6",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "LochMassAI",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
"Name": "MassAI",
"Enabled": true
},
{
"Name": "MassCrowd",
"Enabled": true
},
{
"Name": "MassGameplay",
"Enabled": true
},
{
"Name": "StateTree",
"Enabled": true
}
]
}
首先 build 点 c s 里面要把需要用到的模块先加上
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class LochMassAI : ModuleRules
{
public LochMassAI(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"EnhancedInput",
"MassEntity",
"MassCommon",
"MassActors",
"MassSignals",
"MassAIBehavior",
"StateTreeModule",
"MassMovement",
"MassNavigation",
"NavigationSystem",
"MassRepresentation"
});
}
}
然后我们找一个空文件,把我们的片段定义一下。
USTRUCT(BlueprintType)
struct FLchTransformBaseFragment : public FMassFragment
{
GENERATED_BODY()
UPROPERTY()
FTransform BaseTransform;
};
USTRUCT(BlueprintType)
struct FLchBlendTransformFragment : public FMassFragment
{
GENERATED_BODY()
UPROPERTY(EditAnywhere)
FTransform StartTransform;
UPROPERTY(EditAnywhere)
FTransform EndTransform;
UPROPERTY(EditAnywhere)
float MaxTime = 1.f;
UPROPERTY(EditAnywhere)
float CurrentTime = 0.f;
};

Mass Moveable visualization
在这里可以配置我们 spawn 中的 mass 的静态网格,还有对应的 actor,根据不同的 LOD 选择不同的生成方式
这里我暂时全部选成静态网格

Assorted Fragments 是片段特征。
把我们自己新建的加进去后。点击验证,它会不断的提示,让你加新的片段,黄色的框里面的就是。
Mass Spawn

这些东西和人群的一样,注意 Generator决定从哪里出来,暂时先用Zone Graph
创建一个Processor
#pragma once
#include "CoreMinimal.h"
#include "MassProcessor.h"
#include "LchBlendTransformProcessor.generated.h"
UCLASS()
class LOCHMASSAI_API ULchBlendTransformProcessor : public UMassProcessor
{
GENERATED_BODY()
public:
ULchBlendTransformProcessor();
protected:
virtual void ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager) override;
virtual void Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context) override;
FMassEntityQuery EntityQuery;
};
cpp
关键点是构造函数 : EntityQuery(*this)
ConfigureQueries 中绑定对应片段,同时拥有这些片段的 Entity会执行到这个Processor
然后在 Execute 中做具体逻辑
#include "LchBlendTransformProcessor.h"
#include "MassCommonFragments.h"
#include "MassCommonTypes.h"
#include "MassExecutionContext.h"
#include "MassMovementFragments.h"
#include "MassSignalSubsystem.h"
#include "RandomMoveType.h"
ULchBlendTransformProcessor::ULchBlendTransformProcessor()
: EntityQuery(*this)
{
ExecutionFlags = static_cast<int32>(EProcessorExecutionFlags::All);
ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
}
void ULchBlendTransformProcessor::ConfigureQueries(const TSharedRef<FMassEntityManager>& EntityManager)
{
EntityQuery.AddRequirement<FLchBlendTransformFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FLchTransformBaseFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.AddRequirement<FTransformFragment>(EMassFragmentAccess::ReadWrite);
EntityQuery.RegisterWithProcessor(*this);
}
void ULchBlendTransformProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(Context, [&](FMassExecutionContext& Context)
{
const int32 NumEntities = Context.GetNumEntities();
TArrayView<FLchBlendTransformFragment> BlendTransformList = Context.GetMutableFragmentView<FLchBlendTransformFragment>();
TArrayView<FLchTransformBaseFragment> TransformBaseList = Context.GetMutableFragmentView<FLchTransformBaseFragment>();
TArrayView<FTransformFragment> TransformList = Context.GetMutableFragmentView<FTransformFragment>();
for (int32 i = 0; i < NumEntities; ++i)
{
FLchBlendTransformFragment& BlendTransform = BlendTransformList[i];
FLchTransformBaseFragment& TransformBase = TransformBaseList[i];
FTransformFragment& Transform = TransformList[i];
// do something
}
});
}
就是一个魔杖static mesh 乱飞的示例
void ULchBlendTransformProcessor::Execute(FMassEntityManager& EntityManager, FMassExecutionContext& Context)
{
EntityQuery.ForEachEntityChunk(Context, [&](FMassExecutionContext& Context)
{
const int32 NumEntities = Context.GetNumEntities();
TArrayView<FLchBlendTransformFragment> BlendTransformList = Context.GetMutableFragmentView<FLchBlendTransformFragment>();
TArrayView<FLchTransformBaseFragment> TransformBaseList = Context.GetMutableFragmentView<FLchTransformBaseFragment>();
TArrayView<FTransformFragment> TransformList = Context.GetMutableFragmentView<FTransformFragment>();
for (int32 i = 0; i < NumEntities; ++i)
{
FLchBlendTransformFragment& BlendTransform = BlendTransformList[i];
FLchTransformBaseFragment& TransformBase = TransformBaseList[i];
FTransformFragment& Transform = TransformList[i];
if (BlendTransform.CurrentTime < BlendTransform.MaxTime)
{
// 正常的混合插值
BlendTransform.CurrentTime += Context.GetDeltaTimeSeconds();
float Alpha = FMath::Clamp(BlendTransform.CurrentTime / BlendTransform.MaxTime, 0.f, 1.f);
FTransform NewTransform = FTransform::Identity;
NewTransform.Blend(BlendTransform.StartTransform, BlendTransform.EndTransform, Alpha);
TransformBase.BaseTransform = NewTransform;
Transform.SetTransform(NewTransform);
}
else
{
// 到达终点后,交换起点和终点,生成新的随机目标位置,实现循环往复运动
FTransform OldStart = BlendTransform.StartTransform;
FTransform OldEnd = BlendTransform.EndTransform;
// 当前位置作为新的起点
BlendTransform.StartTransform = OldEnd;
// 生成随机的新终点(在当前位置附近的随机偏移)
FVector RandomOffset = FVector(
FMath::RandRange(-500.f, 500.f), // X 轴随机偏移
FMath::RandRange(-500.f, 500.f), // Y 轴随机偏移
FMath::RandRange(0.f, 100.f) // Z 轴随机偏移(较小的垂直变化)
);
FTransform NewEndTransform = OldEnd;
NewEndTransform.AddToTranslation(RandomOffset);
BlendTransform.EndTransform = NewEndTransform;
// 重置时间,开始新的循环
BlendTransform.CurrentTime = 0.f;
// 可选:随机化每次循环的持续时间
BlendTransform.MaxTime = FMath::RandRange(2.f, 5.f);
}
}
});
}
另外,东西看不见?写一个这个东西注册一下。
#pragma once
#include "CoreMinimal.h"
#include "MassVisualizationLODProcessor.h"
#include "MassRepresentationProcessor.h"
#include "LchMassAIVisualizationProcessor.generated.h"
UCLASS()
class ULchMassAIVisualizationProcessor : public UMassVisualizationProcessor
{
GENERATED_BODY()
public:
ULchMassAIVisualizationProcessor()
{
bAutoRegisterWithProcessingPhases = true;
}
};
效果就是魔杖乱飞

案例2: 加上状态树

新建场景,Mass Spawn,但是这次我们选择 EQS

EQS里面简单写个Grid,基本就是在spawn周围一点范围的格子。具体后面张开
EQS是,environment query
配置DA
一直点验证,最后加出了这么多东西
在可视化里面还要配置一下mesh
另外就是片段中加上了我们自己的片段
USTRUCT(BlueprintType)
struct FLchRandomMoveSTFragment : public FMassFragment
{
GENERATED_BODY()
UPROPERTY()
FVector TargetLocation;
};

点到没有报错的时候,还有几个片段,是个什么的(红框)
最重要的是状态树的关联


摆一个 Nav Mesh Bounds Volume,按P显示区域

然后就动起来了
具体可以看看这两个Task的代码
。。。
自定义信号
#pragma once
#include "CoreMinimal.h"
#include "MassSignalProcessorBase.h"
#include "LchCustomSignalProcessor.generated.h"
namespace UE::Mass::Signals
{
const FName LchCustomSignalTest = FName(TEXT("LchCustomSignalTest"));
};
UCLASS()
class ULchCustomSignalProcessor : public UMassSignalProcessorBase
{
GENERATED_BODY()
public:
ULchCustomSignalProcessor(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
protected:
virtual void InitializeInternal(UObject& Owner, const TSharedRef<FMassEntityManager>& InEntityManager) override;
virtual void SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals) override;
};
定义信号类,信号是一个 FName
一个处理器,继承 UMassSignalProcessorBase
InitializeInternal中注册型号,SignalEntities 处理型号回调
#include "LchCustomSignalProcessor.h"
#include "MassSignalSubsystem.h"
#include "MassExecutionContext.h"
#include "RandomMoveSTType.h"
ULchCustomSignalProcessor::ULchCustomSignalProcessor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bRequiresGameThreadExecution = true;
}
void ULchCustomSignalProcessor::InitializeInternal(UObject& Owner, const TSharedRef<FMassEntityManager>& InEntityManager)
{
Super::InitializeInternal(Owner, InEntityManager);
UMassSignalSubsystem* SignalSubsystem = UWorld::GetSubsystem<UMassSignalSubsystem>(Owner.GetWorld());
SubscribeToSignal(*SignalSubsystem, UE::Mass::Signals::LchCustomSignalTest);
EntityQuery.AddRequirement<FLchRandomMoveSTFragment>(EMassFragmentAccess::ReadOnly);
ProcessorRequirements.AddSubsystemRequirement<UMassSignalSubsystem>(EMassFragmentAccess::ReadWrite);
}
void ULchCustomSignalProcessor::SignalEntities(FMassEntityManager& EntityManager, FMassExecutionContext& Context, FMassSignalNameLookup& EntitySignals)
{
EntityQuery.ForEachEntityChunk(Context, [](FMassExecutionContext& Context)
{
const int32 NumEntities = Context.GetNumEntities();
// UE_LOG(LogTemp, Warning, TEXT("LchCustomSignalProcessor received signal for %d entities"), NumEntities);
});
}
bRequiresGameThreadExecution 会让信号的逻辑在主线程中被处理
InitializeInternal 中注册这个处理西要处理这个型号
关联FLchRandomMoveSTFragment片段只是为了让这个处理器有条件跑起来,
然后在型号发出的时候,SignalEntities 就会被调用。
if (!EntitiesToSignal.IsEmpty())
{
SignalSubsystem.SignalEntities(UE::Mass::Signals::NewStateTreeTaskRequired, EntitiesToSignal);
SignalSubsystem.SignalEntities(UE::Mass::Signals::LchCustomSignalTest, EntitiesToSignal);
}
调用的地方就写在前面的状态树下面。
求值器

状态树的这个地方可以加一个求值器。
这个地方可以从其他像角色身上获取一些数值,然后修改一些变量,然后这个变量就可以在状态树的其他节点中使用。
#pragma once
#include "MassComponentHitSubsystem.h"
#include "MassStateTreeTypes.h"
#include "RandomMoveSTType.h"
#include "LchMassTestSubsystem.h"
#include "LchMassTestRvaluator.generated.h"
namespace UE::MassBehavior
{
struct FStateTreeDependencyBuilder;
}
USTRUCT(BlueprintType)
struct FLchMassTestEvaluatorInstanceData
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere, Category = "Output")
bool bGotHit = false;
UPROPERTY(VisibleAnywhere, Category = "Output")
FMassEntityHandle HitEntity;
};
USTRUCT(meta = (DisplayName = "Lch Mass Test Evaluator"))
struct FLchMassTestEvaluator : public FMassStateTreeEvaluatorBase
{
GENERATED_BODY()
protected:
virtual bool Link(FStateTreeLinker& Linker) override;
virtual const UStruct* GetInstanceDataType() const override { return FLchMassTestEvaluatorInstanceData::StaticStruct(); }
virtual void Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const override;
virtual void GetDependencies(UE::MassBehavior::FStateTreeDependencyBuilder& Builder) const override;
TStateTreeExternalDataHandle<ULchMassTestSubsystem> TestSubsystemHandle;
TStateTreeExternalDataHandle<FLchRandomMoveSTFragment> RandomMoveFragmentHandle;
};
通过继承 FMassStateTreeEvaluatorBase 实现一个求值器
#include "LchMassTestRvaluator.h"
#include "StateTreeLinker.h"
#include "MassStateTreeExecutionContext.h"
#include "MassStateTreeDependency.h"
UE_DISABLE_OPTIMIZATION
bool FLchMassTestEvaluator::Link(FStateTreeLinker& Linker)
{
Linker.LinkExternalData(TestSubsystemHandle);
Linker.LinkExternalData(RandomMoveFragmentHandle);
return true;
}
void FLchMassTestEvaluator::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const
{
const ULchMassTestSubsystem& TestSubsystem = Context.GetExternalData(TestSubsystemHandle);
const FLchRandomMoveSTFragment& RandomMoveFragment = Context.GetExternalData(RandomMoveFragmentHandle);
}
void FLchMassTestEvaluator::GetDependencies(UE::MassBehavior::FStateTreeDependencyBuilder& Builder) const
{
Builder.AddReadOnly(TestSubsystemHandle);
Builder.AddReadOnly(RandomMoveFragmentHandle);
}
UE_ENABLE_OPTIMIZATION
我们可以在 tick 中获取一些 sub system 和片段来进行一些自己的逻辑处理
如何定义sub system呢?
#pragma once
#include "MassEntityTypes.h"
#include "MassComponentHitTypes.h"
#include "MassSubsystemBase.h"
#include "LchMassTestSubsystem.generated.h"
class UMassSignalSubsystem;
class UMassAgentSubsystem;
UCLASS(MinimalAPI)
class ULchMassTestSubsystem : public UMassTickableSubsystemBase
{
GENERATED_BODY()
protected:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override;
UPROPERTY()
TObjectPtr<UMassSignalSubsystem> SignalSubsystem;
UPROPERTY()
TObjectPtr<UMassAgentSubsystem> AgentSubsystem;
};
#include "LchMassTestSubsystem.h"
#include "MassAgentComponent.h"
#include "MassAgentSubsystem.h"
#include "MassSignalSubsystem.h"
#include "MassSimulationSubsystem.h"
UE_DISABLE_OPTIMIZATION
void ULchMassTestSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
SignalSubsystem = Collection.InitializeDependency<UMassSignalSubsystem>();
AgentSubsystem = Collection.InitializeDependency<UMassAgentSubsystem>();
}
void ULchMassTestSubsystem::Deinitialize()
{
Super::Deinitialize();
}
void ULchMassTestSubsystem::Tick(float DeltaTime)
{
}
TStatId ULchMassTestSubsystem::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ULchMassTestSubsystem, STATGROUP_Tickables);
}
UE_ENABLE_OPTIMIZATION
状态树的其他内容
USTRUCT(BlueprintType)
struct FLchMeshPathFollowTaskInstanceData
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, Category = "Input")
FVector TargetLocation;
UPROPERTY(EditAnywhere, Category = "Parameter")
FMassMovementStyleRef MovementStyle;
UPROPERTY(EditAnywhere, Category = "Parameter")
float SpeedScale = 1.0f;
};
这个分类的 Input,Output, Parameter
相当于
void Test(int & Out, int Input, int Parm = 100) {}


Try Enter 只执行这个节点,子节点不执行了
Try Select Children In Order 执行,并且顺序执行子节点
Try Select Children At Random 执行,然后随机【一个】子节点
。。。。 With Highest 得分最高的执行,具体的折腾一下这里
