UE Crowds
UE Crowds
最近在项目中做了人群相关的功能,这篇文章记录一下相关的实现。人群主要分为两类:
- 交互:Mass System
- 表演:Niagara + Billboard
这两个方案都会使用到VAT技术,可以看这篇文章,对VAT技术有详细的讲解。同时也制作了一个小小的示例工程:CrowdsLab
Technical Choice
为什么最终选择这几个方案,这是几个方案的简单分析。最开始想直接使用Actor来实现一个人,其中只包含了一个骨骼网格组件和动画蓝图。但是发现需要对每个人都做行为树的处理,还需要编写一些额外的功能,如避让功能,LOD功能等。
后面发现UE官方示例CitySample中有人群实现的解决方案:Mass System
。各个功都有实现,同时也做了一定一下性能上的对比:


这一对比就决定使用Mass System
来做人群。
同时还有一个需求:大数量的人群,不可交互,只做表演。我们也预先设想了三个方案,同时也对三个方案做了性能测试:



粒子系统的帧率基本没有变化,所以最终选择用粒子系统。
Interactive NPCs
具体怎么使用 Mass System
这里就不做介绍,可以看这篇教程 Your First 60 Minutes with Mass,跟着做一遍就大概知道怎么用。做出来效果如下:

该效果可以在示例工程:/All/Game/Maps/MassSample
中看到。
再配合看这两篇文章MassGameplay Overview,MassEntity Overview,可以了解到这个系统的框架。它其实就是一个ECS系统。之前有写过这个系统的简单介绍。
下面主要记录一下在实现过程中遇到的问题和重点。
Movement Styles

简单描述一下这个配置的逻辑,在行人每次发起寻路请求时,会根据状态树上配置的移动方式来计算最后移动的速度,这里面有一个随机的速度配置,在Desired Speeds
数组中随机出一个速度配置。
但是这个功能是有问题的,因为这里配置的参数是FMassMovementParameters
,这个参数在系统初始化时,并没有成功的注册到实例化出来的FMassMovementParameters
中。所以需要在初始化时,复制一下参数即可。重写:UMassMovementTrait::BuildTemplate
方法即可:
void UMassNPCMovementTrait::BuildTemplate(FMassEntityTemplateBuildContext& BuildContext, const UWorld& World) const
{
FMassEntityManager& EntityManager = UE::Mass::Utils::GetEntityManagerChecked(World);
BuildContext.RequireFragment<FAgentRadiusFragment>();
BuildContext.RequireFragment<FTransformFragment>();
BuildContext.AddFragment<FMassVelocityFragment>();
BuildContext.AddFragment<FMassForceFragment>();
const FMassMovementParameters MovementValidated = Movement.GetValidated();
const FConstSharedStruct MovementFragment = EntityManager.GetOrCreateConstSharedFragment(MovementValidated);
BuildContext.AddConstSharedFragment(MovementFragment);
}
当然也可以直接修改引擎中的UMassMovementTrait
文件,我也向官方提交了PR:https://github.com/EpicGames/UnrealEngine/pull/12457。
NPC Actor
在NPC的实例上需要添加转向同步特质,在上面那篇文章中没有提到:

可以查看示例工程:/All/Game/Data/DA_MassPuppetEntityConfig。
在CitySample
中路人使用了MetaHuman
并且全身是可以通过不同的部件拼接而成。通过设置领导者姿势,来播放动画。
实现过程中遇到了,在切换显示静态模型时,Actor并不会被隐藏。因为在UMassRepresentationActorManagement::SetActorEnabled
这方法中,它只是单纯的关闭了Tick和碰撞。如果需要隐藏Actor需要重写一下该方法。
Instance Mesh
当NPC离主角远时,NPC会被渲染成示例网格,设置在MassEntityConfigAsset->Mass Movable Visualization Trait
中,如下图:

Warning
值得注意的是:如果使用模块化角色,这里配置的mesh就需要多个静态网格。同时在导出VAT的静态模型时,需要把所有骨骼模型的骨骼统一,这样导出来的静态模型才会有相对位置。比如说:人物的衣服,身体,头和头发都需要是统一成身体的骨骼。
如下图:

在CitySample中并没有动画贴图导出工具,所以我们猜测人物模块化静态模型有可能是DDC软件导出的。如果是需要在引擎中导动画贴图,那么就需要统一骨骼。
CitySample中在Mass中随机静态模型的逻辑在CrowdVisualizationFragment
文件中,这是一个监听处理器,在添加了指定组件到实体上后,就会触发该处理器。
Instance Animation
在Mass中更新静态模型的动画信息,最终就是触发的InstancedMeshComponent->BatchUpdateInstancesData
方法去更新实例的自定义数据,然后更新VAT相关的数据。VAT执行播放动画及播放原理可以看这个。
Mass中一个实体对着IMC中的一个实例,Mass中对实体的IMC进行了封装,更新动画自定义数据用:
FMassInstancedStaticMeshInfo::AddBatchedCustomData<FAnimToTextureAutoPlayData>(PlayData, LODSignificance, PrevLODSignificance, NumFloatsToPad);
使用一个处理器UVertexAnimProcessor
来控制每个实体播放的动画,动画的切换可以在该处理器自定义规则进行更改,比如待机和移动动画的切换,可以使用实体的速度进行控制:
float Speed = VelocityFragment.Value.Length();
VertexAnimInfoFragment.AnimationStateIndex = Speed >= VertexAnimInfoFragment.SpeedThreshhold ? VertexAnimInfoFragment.RunAnimIndex : VertexAnimInfoFragment.IdleAnimIndex;
在示例工程中有对应的实现:/All/Game/Maps/MassSample_VAT_NPC
值得注意的是该处理器同样可以更新Actor中的动画蓝图,使用FActorFragment
获取即可操作。
Smart Object
人群中某些人可以与场景中的一些静态物体进行交互,可以使用Smart Object
来做静态物体。
Warning
ZoneGraph
和SmartObject
的结合使用现在还有bug。在打包的版本中会crash。详细可以看这个帖子:https://udn.unrealengine.com/s/question/0D54z00008E7bo2CAB/crash-occureed-after-packaging-with-using-smartobject-and-massair。问题是在版本中路径信息没有了,猜测是在序列化时出现了丢失的问题。报错点如下:
解决方案可以是在使用SmartObject的NPC上直接使用UE传统的导航模式。
使用Smart Object
可以查看这篇文章:Smart Objects Quick Start。
使用Smart Object
与ZoneGraph
结合需要配置的地方:Configuring ZoneGraph to Work with Mass Smart Objects
State Tree
Mass System
中每个实体的行为是由State Tree
来控制的,这是一个轻量版的行为树。可以查看这篇文章了解如何使用:Your First 60 Minutes with StateTree
State Trees and Smart Objects: Data-Driven State Machine Workflows for Open World AI Designs | Unreal Fest 2023这个讲座讲解了如何使用State Tree
和Smart Object
如何交互,推荐看看。
简单讲解一下如何使用Smart Object
:
Step 1:SmartObjectDefinition
首先创建一个SmartObjectDefinition
:SO_Definition_Cube

Step 2:SmartObject Actor
再创建一个Actor:BP_SO_Cude
,并添加SmartObjectComponent

并在组件上配置如下:

Step 3: 设置 Smart Object Tags
在 Project Setting -> Zone Graph -> Tags
设置 Smart Object 的 Tag:

在 Project Setting -> Mass -> Mass SmartObject
设置 Smart Object 的 Tag:

Step 4:ZoneAnnotations Actor
在创建Actor:BP_ZGAnnotations
并添加SmartObjectZoneAnnotations
组件

配置如下:

并把该Actor放在场景中,并把Behavior Tag
设为SmartObject
:

设置这个Tag只能在场景中设置
Step 5: 摆放Smart Object

Step 6: SmartObjectPersistentCollection
场景中添加SmartObjectPersistentCollection
,并重新构建集合,点击如下按钮:

见到如下试图表示构建成功

Step 7: 调整状态树
在AI走到Smart Object进行特殊行为时,这里使用的是让AI看向Smart Object来表现。

在Smart Objects
节点出需要设置一下进入条件:

最终效果如下:

Tips
Mass System显示调试信息按单引号那个键,也就是回车键左边那个键。
Large Crowds
这个功能是参考一下两个教程:
这是是在示例工程中有实现的效果:
粒子发射器设置:

这里记录一下一些特殊的处理
Random Animation
因为是使用VAT播放的动画,所以我们只需要修改VAT材质动态参数即可:

这里我们使用AutoPlay
的方式播放动画。
首先需要把材质中的静态变量UseDynamicParameters
设置为真,这里并没有手动去修改材质实例中的数值,因为材质实例是有顶点动画工具自动设置的,同时该材质实例可能会在其他地方也在使用。
在发射器中的Mesh Render->Bindings->Material Parameters->Static Bool Parameters
绑定UseDynamicParameters
:

同在在粒子系统中需要设置一个静态布尔值,在粒子初始化时设置该布尔值即可。
然后在Particle Spawn
中添加Dynamic Material Parameters
,设置如下:

这里的StartFrame
和EndFrame
可以中对应的AnimToTextureDataAssets
中读取到:

Reference
- https://dev.epicgames.com/community/learning/tutorials/JXMl/unreal-engine-your-first-60-minutes-with-mass
- https://dev.epicgames.com/documentation/zh-cn/unreal-engine/overview-of-mass-gameplay-in-unreal-engine
- https://dev.epicgames.com/community/learning/tutorials/qz6r/unreal-engine-zonegraph-quick-start-guide
- https://dev.epicgames.com/documentation/zh-cn/unreal-engine/working-with-modular-characters-in-unreal-engine
- https://vrealmatic.com/unreal-engine/mass
- https://github.com/jcoder58/UE5MassResources?tab=readme-ov-file
- https://youtu.be/CqXKSyAPWZY