0

0

UE5的ECS:MASS框架(一)

絕刀狂花

絕刀狂花

发布时间:2025-07-09 11:38:33

|

417人浏览过

|

来源于php中文网

原创

最近官方发布了《黑客帝国:觉醒》的试玩游戏,观看演示视频后备受震撼。视频中提到了街头海量人群是通过mass ai框架实现的,这套框架在实现如此逼真的效果上功不可没。尽管mass代码的公开版本尚未发布,但实际上它已经在github的ue5-main分支上存在了很久。我之前也浏览过该代码,并且近期它还在不断更新。因此,我想借此热度总结一下其内部实现原理。如果你熟悉ecs(实体-组件-系统),阅读下面的内容将会非常轻松,因为mass实际上就是ue5实现的ecs框架。

在拉取最新代码并编译后,启动闪屏显示的版本号是5.1.0:

UE5的ECS:MASS框架(一)

Mass框架分为几个库,以插件形式存放在引擎源码中,因此即使是低版本也相对容易使用。默认情况下这些库是关闭的,需要手动开启。以下是在引擎插件目录中手动开启的方式:

UE5的ECS:MASS框架(一)

其中,MassEntity是基础库,还包括MassGameplay、MassAI、MassCrowd等。各库之间的关系如下图所示:

UE5的ECS:MASS框架(一)

MassEntity是整个Mass框架的基础。先来看一下MassEntity中的代码文件:

UE5的ECS:MASS框架(一)

仅从代码命名就能猜出这是一套ECS框架。如果你对Unity的ECS和UE的渲染框架比较熟悉,看到这套代码的结构会觉得非常熟悉和亲切。Archetype对应于Unity的ECS中的Archetype,其实现与Unity的ECS非常相似。而CommandBuffer则类似于UE渲染线程中的CommandBuffer。

之前UE渲染管线的效率非常高,很大程度上是因为面向数据的设计,并且紧密结合TaskGraph利用多线程渲染的优势。现在在逻辑层也搭建了一套类似的管线,就是为了让逻辑处理也能发挥出UE5的性能优势。官方已经提供了实机演示成品,你肯定能感受到其卓越的运行效率。

接下来具体介绍内部实现:

Entity和Archetype与Unity的ECS除了名称不同外,实现完全一致。Entity在Mass中的名称是struct FMassEntityHandle,只有两个成员变量Index和SerialNumber,共占8字节。看到这样的结构,肯定知道有一个全局的大数组,这个Index就是数组下标,而序列号用于数据校验,可以说与FWeakObjectPtr的原理相同。

UE5的ECS:MASS框架(一)

这个大数组保存在MassEntitySubsystem中,如下图所示:

UE5的ECS:MASS框架(一)

这个大数组的类型是TChunkedArray,默认16K一个Chunk,每个元素类型是FEntityData。16K是因为大部分CPU的L1缓存都是16K的倍数。参考我之前的一篇文章,详细说明了:UE4/UE5的LockFreeList - 知乎 (zhihu.com)

UE5的ECS:MASS框架(一)

内部有一个智能指针,指向实际的数据结构FMassArchetypeData。除了Index外,还有一个序列号SerialNumber,其作用是当某个Index上的Entity被删除后,再创建一个新的Entity,如果原来Index指向的EntityData和EntityHandle的序列号不匹配,就可以明确EntityHandle指向的是旧的Entity而不是新的,这样就避免了只用Index标记Entity导致的冲突问题。

FMassArchetypeData结构如下:

UE5的ECS:MASS框架(一)UE5的ECS:MASS框架(一)

可以看到,这个其实定义的是Entity数据的Archetype。什么是Archetype(原型)?可以简单这样理解:类是对象的原型,结构体是结构体实例的原型,UClass中的CDO是对应UObject的原型。我们游戏要创建很多Entity,这里就需要先有Entity的原型定义,可以描述内存布局等信息。总之,创建Entity前肯定需要原型信息。

在定义原型时需要以下四种信息作为参数:

UE5的ECS:MASS框架(一)

一般情况下使用FMassFragment就好了,这个就是定义每个Entity内部的数据结构,在传统的ECS中这个FMassFragment其实就是Component。而FMassTag不能有实际的成员变量,只是作为ECS执行时的标记,可以认为是传统ECS中的额外过滤器标签,而UE中的过滤器称为Query。FMassChunkFragment是Chunk的额外内存数据,每个Chunk内共享一份。FMassSharedFragment是共享的布局,相当于Unity的ECS中的共享Component。如果FMassFragment可以理解为Entity的成员变量,那么FMassSharedFragment就可以理解为Entity的static成员变量,而FMassChunkFragment可以理解为每个Chunk的static成员变量,Chunk具体是什么接下来会说。这四种结构,在创建原型时都会分别记录下来,保存在FMassArchetypeCompositionDescriptor中。

UE5的ECS:MASS框架(一)

可以看到,这里提供了联合计算Hash的函数,也就是说通过这些原型,就可以得到一个唯一码。最终也会和Entity一样保存到MassEntitySubsystem中:

LangChain
LangChain

一个开源框架,用于构建基于大型语言模型(LLM)的应用程序。

下载

UE5的ECS:MASS框架(一)

在FMassArchetypeData初始化时,就会根据这个描述符来生成内存布局和相关变量,具体可以看FMassArchetypeData::Initialize函数。这里也会解释清楚上面四种基类的区别

其中FragmentSizeTallyBytes是每个Entity的大小,是由sizeof(FMassEntityHandle)加上每个FMassFragment子类的大小总和。而Tags本身不占Entity大小,只保存在Archetype中。FMassSharedFragment是多个同类型Entity共享的Fragment,所以也保存在Archetype中,不占用Entity内存。

实际的Entity数据保存在FMassArchetypeData的Chunks这个成员变量里:

UE5的ECS:MASS框架(一)

内部会一次创建一个固定64K大小的Chunk,给多个Entity使用。

UE5的ECS:MASS框架(一)

为什么是64K?同样是因为大部分CPU的L2缓存是64K的倍数(Unity一个Chunk是16K),这里L1和L2都是CPU单核的独立缓存,所以都很快,如果到了L3因为涉及到多核共享,就会显著降速。所以根据Entity本身的大小不同,每个Chunk可以容纳64K / EntitySize个。当删除掉其中一个Entity时,内部的其他Entity并不会移动,所以这个Entity会在Chunk中空出来,这时如果再Add新的Entity会复用这个空出来的内存,当删除掉Chunk中所有Entity时,Chunk的内存会自动释放掉。整个数据结构实现,相当于是TSparseArray和TChunkedArray的结合,因为UE没有自带这种泛型容器,所以这里就单独实现了。

在定义ArchetypeData时,暴露给业务的其实是FMassEntityHandle。就像FMassEntityHandle一样。具体对应关系如下图所示:

UE5的ECS:MASS框架(一)

这里要注意的一点是,EntityHandle中的Index,并不是Archetype中实际Entity的下标,而是System里Entities的下标。这两个下标会通过Achetype中的一个Map做映射,如下图所示。Entity中的Index是全局唯一的,而Archetype中的Index是在当前Archetype内唯一,多个Archetype中是会有相同的下标,所以需要这样一个映射才能确定实际的Entity数据是什么。

UE5的ECS:MASS框架(一)

关于实际的Entity存储:

UE5的ECS:MASS框架(一)

实际数据存储在RawMemory中,具体大小在AllocSize里。可以看到每个Chunk内还有一个ChunkFragmentData的。这个就是前面说的每个Chunk一份的static数据。

示例上面这样描述对于不了解ECS的读者来说可能有些晕,下面用我具体写的这个例子说明更直观一些。我们先定义一下FMassFragment,这个就是Entity的内部数据结构。这里我准备创建3种类型的Entity,第一种内部数据是float的,第二种是int32的,第三种是float和int32组合在一起的。

UE5的ECS:MASS框架(一)

Entity具体创建和删除示例如下:

UE5的ECS:MASS框架(一)

可以看到上图,在创建前,我先定义了原型,原型其实就相当于是在运行时定义数据结构,这里需要强调的是运行时,自己手写一个struct其实是在编译期定义数据结构,因为都是使用的UScriptStruct,所以理论上可以使用蓝图定义的结构体。上图也可以看到,定义好了Entity的Archetype后,也可以随时修改,相当于ECS的ReplaceComponent。

我这里分别创建了100个,200个,150个。实际创建的就是下面这样的数据,在第一次创建的时候是连续的,连续创建和删除有可能产生空洞。

UE5的ECS:MASS框架(一)

注意上图只是个示意,实际会分到Chunk里。借用一下Unity的ECS老图,具体结构是下面这样,我就不自己画了,原理和Unity的ECS是完全一样的。

UE5的ECS:MASS框架(一)

本章主要介绍了Mass内部的内存布局,后续章节会继续讲解具体操作。

相关专题

更多
css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

567

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

99

2025.10.23

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

197

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

189

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

535

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

21

2026.01.06

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

482

2023.08.10

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.5万人学习

Git 教程
Git 教程

共21课时 | 2.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号