1. 系统、实体、组件

组件是游戏数据的集合 [1]
实体是组件的集合
系统是方法的集合

因此:
一个实体的意义取决于其组件的组合
一个系统的意义取决于其方法集处理的组件集

例如:
一个实体是否拥有运动组件代表着玩家能否操控这个实体运动
是否拥有碰撞体组件代表着这个实体是否参与碰撞
是否拥有刚体组件代表着这个实体是否参与物理模拟
是否拥有角色组件代表这个实体是否参与伤害流程
一个系统获取到实体的运动组件,计算实体的速度,然后获取到实体的碰撞体组件,检查是否干涉,最后得到实体的移动后的位置,赋回给实体的运动组件,并设置实体的位置,那么这个系统就是运动系统

2. 为什么要用 ECS

2.1 逻辑与数据解耦

游戏的状态完全可以用一段数据来定义。
游戏的所有内容,就是游戏数据和处理数据的方法。[2]

从头开始构建一个游戏,首先要思考数据和方法的关系。
数据和方法都封装到一个地方,就是面向对象。

如果我们尝试使用将数据封装在操作中的对象来构建游戏,那么我们将在所有这些不同的系统之间建立依赖关系,因为它们都希望用玩家的位置数据进行封装
除非我们将游戏编码为一个单一的、庞大的类,但显然这个庞大的类就是一坨屎山
因此我们不可避免地将游戏的某些部分分解为单独的系统,并向这些系统(物理系统、图形系统)提供数据,同时将游戏逻辑的其他元素包含在游戏对象本身中

例如,我有两个逻辑,移动和渲染,如果我需要
可以移动但是不能渲染的
可以渲染但是不能移动的
可以渲染并且可以移动的
那么我就会去继承移动类和渲染类,做出一个若干层的继承树 [3]

可以发现,游戏逻辑完全没有必要放在对象中,这会增加不必要的耦合
只需要做两个东西,就能得到 2^n 种组合

既然如此,那么可以把具有一定功能的逻辑做成一个组件,给对象装上去,这样也能避免庞大的继承树

如此一来,当你把所有逻辑都做成组件和相应的组件管理器时,你就会发现,对象只是起到一个承载组件的作用
此时,你的组件中可能还有一些逻辑,比如一些具体的函数,组件管理器中也可能存在着一些逻辑,比如控制组件生命周期。例如,Renderable 组件中存放着资源引用,存在一个 render() 方法, 可以渲染图片,它的组件管理器的 Update() 控制了渲染函数的使用频率。
这样做,游戏当然也可以运行,但是你还可以进一步归纳逻辑,把逻辑都放在一个管理器里面,组件之中只有数据。
原来你可能希望两个对象具有相同的组件,但是在使用这些组件的方法上具有不同之处,但他们又都受到同一个管理器控制。现在如果组件中只有数据,你就不能在组件中写出这些区别,但是因为你把逻辑移动到了管理器中,实际上你应该在管理器中写出这些区别。也就是说,现在的管理器已经不能叫做管理器,可以叫做进程或者系统更好一点。
如果你希望有两个对象组件相同逻辑不同,那么原来你会写成 (data1,logic1), (data1,logic2), (mgr),现在你会写成 (data1), (data2), (system1), (system2)
现在文件变多了,但是逻辑和数据完全分离了,这是完美的解耦。
从不使用组件的面向对象,到使用组件的面向对象,再到面向数据,逻辑与数据的解耦程度逐渐增大。

2.2 管理组件内存

如果每一个对象都存在连续内存里面,那么相同类型的组件所在的内存实际上是不连续的。当一个系统希望遍历所有对象的同一组件时,它会在内存之间跳跃,这对于性能是不利的。[4]

你可以将组件数据的所有权从实体手中夺走,并将它们存储在每种类型的连续内存中,然后每个实体存储指向其绑定的组件的指针列表。这肯定对性能更好,假设您正在更新每个类型的组件。然而,关于代码组织,这仍然不理想。

更好地管理内存的方法有很多:

2.2.1 ID 和 位掩码

每个实体只是一个ID号和一个位掩码。每个组件都有一个类型 ID,我们可以使用位掩码来找出实体具有哪些组件。
组件存储在普通的旧内存池中,我们可以使用实体索引来检索实际的组件数据。场景视图包装一个迭代器,该迭代器将遍历实体,检查哪些实体具有正确的组件掩码,并返回这些实体 ID。

这个方法有一个潜在的问题:实体可能不使用的组件分配内存

2.2.2 稀疏集

稀疏集是一种将稀疏索引映射到紧密打包的数组的方法。它基本上相当于两个列表,一个稀疏地填充了紧密排列的列表的索引。打包列表包含返回稀疏列表元素的索引。图表可能有助于实现此概念。

在 ECS 中使用它的方式是,每个组件池都有一个分配给它的稀疏集。稀疏索引列表包含内存池中可从中检索组件数据的实际索引。打包数组包含一个实体索引的列表,这些实体索引具有分配给它们的此类型的组件。通过这种方式,您可以取消位掩码,并使用稀疏集来访问紧密打包的内存,并确定实体是否为其分配了组件。

这显然节省了大量的内存,而且组件数据也紧密打包,因此迭代任何一个组件的速度都尽可能快。

这样做的另一大好处是,如果您有一个循环访问两种类型的组件的 SceneView,则只需循环访问最小组件池的数组即可。从而最大限度地减少对每个实体的检查量。对于具有大量实体和组件的大型场景,这要快得多。可能和你需要的一样快。

2.2.3 原型

这种类型的ECS是Unity在其面向数据的技术堆栈(DOTS)中使用的ECS,因此目前非常流行。它不是严格打包组件数据,而是专注于将具有相似组件集的实体保存在内存中。我发现这是正确实现的最复杂的ECS类型。

核心思想是,具有相同组件集的所有实体都称为"原型",并一起存储在一个连续的数组中。循环访问具有一组特定组件的实体,然后成为迭代原型,然后返回这些匹配原型中的所有实体的情况。假设原型的数量远低于实体的数量(你希望是这样),迭代变得非常快。几乎没有什么检查要做。因为数据有效地布局在内存中,以最大限度地减少缓存未命中并最大限度地提高速度。[3]

但是,查找特定实体的组件会变得更加复杂和缓慢。
(或许对于特殊逻辑,应该再加上一个映射……)

这样做的缺点是,在实体中添加和删除组件涉及将该实体的所有组件数据从一个池移动到另一个池。理想情况下,您将构建游戏,以最大限度地减少组件更改的数量,并最大限度地减少原型的数量。

2.3 存储游戏状态

由于游戏状态完全包含在组件中,并且由于这些组件是简单的值对象,因此保存游戏状态是序列出组件的相对简单的问题,而还原游戏状态只需将数据重新分解为一个相对简单的问题 [2]
在大多数情况下,序列化值对象非常简单,可以简单地对每个组件进行 json 编码,并使用其他数据来指示其实体所有者(id)及其组件类型(字符串),以保存游戏状态
Adam Martin 写了一篇关于将实体系统框架中的组件与关系数据库中的数据进行比较的文章(Adam 的博客上有很多有趣的与实体相关的东西),并强调用于长期存储的关系数据库和用于游戏玩法的组件之间的转换不需要任何对象/关系映射,因为组件是关系数据库数据结构的简单副本,而不是复杂的对象
这进一步得出的结论是,组件/系统架构是MMO游戏的理想选择,因为状态将存储在游戏服务器上的关系数据库中,并且该状态的大部分处理将发生在服务器上,其中使用一组离散的独立系统在游戏展开时处理数据非常适合状态和并行性的数据存储要求。在服务器上可用

2.4 并发

事实上,组件/系统架构非常适合将并发应用于游戏。在大多数游戏中,一些系统彼此完全独立,包括独立于它们的应用顺序。这使得并行运行这些系统变得容易 [2]
此外,大多数系统由一个循环组成,其中所有节点都按顺序处理。在许多情况下,循环可以并行化,因为节点可以彼此独立地更新。也就是说,系统中和组件中都可以添加并行处理

参考:
[1]【青幻译制】GDC讲座系列之三 守望先锋的游戏架构和网络代码 青铜的幻想
https://www.bilibili.com/video/av65246084
[2] 为什么使用实体组件系统架构进行游戏开发?
https://www.richardlord.net/blog/ecs/why-use-an-entity-framework.html
[3] 什么是用于游戏开发的实体组件系统架构?
https://www.richardlord.net/blog/ecs/what-is-an-entity-framework.html
[4] 如何在C++中制作一个简单的实体-组件-系统 大卫·科尔森
https://www.david-colson.com/2020/02/09/making-a-simple-ecs.html

ECS 游戏框架背景知识相关推荐

  1. 基础知识漫谈(3) 组合基础知识,设计游戏框架

    如何让画面动起来? 都知道动画的原理,想想看跑马灯,在任意时刻把跑马灯按停,面对眼睛的那附图,它叫做帧(Frame). 帧,就是一串儿连贯动画里的单个截面. 玩儿游戏的人知道,要达到基本流畅的水准,3 ...

  2. pygame为游戏添加背景_万能的Python和Pygame模块构建一个游戏框架

    通过创建一个简单的骰子游戏来探究 Python.现在是来从零制作你自己的游戏的时间. 在我的这系列的第一篇文章中, 我已经讲解如何使用 Python 创建一个简单的.基于文本的骰子游戏.这次,我将展示 ...

  3. pygame为游戏添加背景_为游戏添加背景使用Python和Pygame模块构建一个游戏框架

    这系列的第一篇通过创建一个简单的骰子游戏来探究 Python.现在是来从零制作你自己的游戏的时间. 在我的这系列的第一篇文章 中, 我已经讲解如何使用 Python 创建一个简单的.基于文本的骰子游戏 ...

  4. 怎样使用GPT案例:使用GPT获得OPPO终止ZEKU芯片业务需要的背景知识

    使用GPT获得OPPO终止ZEKU芯片业务需要的背景知识 引子:微博上对OPPO关停ZEKU芯片业务的分析 Q: Nuvia 公司主要生产的是什么? Q: Nuvia芯片和Arm芯片之间有什么关联? ...

  5. 《分布式虚拟现实系统(DVR)》(Yanlz+Unity+SteamVR+分布式+DVR+人工智能+边缘计算+人机交互+云游戏+框架编程+立钻哥哥+)

    <分布式虚拟现实系统(DVR)> <分布式虚拟现实系统(DVR)> 版本 作者 参与者 完成日期 备注 YanlzVR_DVR_V01_1.0 严立钻 2019.07.11 # ...

  6. 人工智能——背景知识、知识体系、应用领域

    一.背景知识 1.图灵测试 图灵在1950年提出,指测试者与被测试者(一个人和一台机器)隔开的情况下,通过一些装置(如键盘)向被测试者提问.进行多次测试后,如果有超过30%的测试者不能确定出被测试者是 ...

  7. 简单的Windows游戏-第1部分:游戏框架

    我已决定使用C#和WinForms创建一个简单的Windows游戏,从而得出一系列见解. 还有其他方法可以完成此任务,但我选择了使事情保持简单并演示如何制作游戏的方法. 更有经验的开发人员会注意到我的 ...

  8. 目前大部分的游戏框架_简单的Windows游戏-第1部分:游戏框架

    目前大部分的游戏框架 我已决定使用C#和WinForms创建一个简单的Windows游戏,从而得出一系列见解. 还有其他方法可以完成此任务,但我选择了使事情保持简单并演示如何制作游戏的方法. 经验丰富 ...

  9. JS小型游戏框架coquette学习(持续更新)

    2019独角兽企业重金招聘Python工程师标准>>> coquette游戏框架学习 框架地址:https://github.com/maryrosecook/coquette 框架 ...

最新文章

  1. .NET Core竟然无法在Mac下进行build
  2. linux内核参数注释与优化
  3. Log4j2的性能为什么这么好?
  4. java中接口的定义与实现
  5. goahead php,Goahead移植教程 | 学步园
  6. 看动画学算法之:排序-快速排序
  7. springboot使用JSR303对数据进行校验
  8. 你知道K8S暴露服务的方式有哪些吗?
  9. PowerBI功能发布时间线
  10. java 查找文件_Java 实例 – 在指定目录中查找文件
  11. java人脸识别Demo(数据库mongo)
  12. 【手把手教安装】VUE安装教程!!!
  13. Spring源码解析(一)
  14. (转)文件名后缀大全
  15. 网站建设的费用一般与功能要求是成正比的
  16. Anroid app版本更新
  17. 与第三方接口调用时白名单功能
  18. C语言学习day1、2
  19. AI如何实现安全生产智能监控
  20. XCTF攻防世界BABYRE逆向

热门文章

  1. 小故事分享:千里马与苍蝇的故事
  2. OracleExcel VBA写获取表字段类型及约束语句
  3. CentOS 7系统上部署Apache+PHP+MariaDB+xcache使用rpm,php module
  4. 一台服务器的黑道生涯之八 谁信任谁
  5. StarkSoft题库管理系统
  6. WPF ListBox(ListView) 自定义 Button 项,获取 ListBox(ListView)的SelectedValue
  7. 低脂肪肉能帮你减肥吗?
  8. 信息学奥赛一本通(1235:输出前k大的数)——堆排序
  9. 信息学奥赛一本通(2038:【例5.5】最大数位置)
  10. Convert to Ones(CF-998C)