# SDL2_ESC **Repository Path**: naka507/sdl2_esc ## Basic Information - **Project Name**: SDL2_ESC - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-07-31 - **Last Updated**: 2024-07-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ECS * 在传统游戏开发中,将遵循继承方式来解决问题。例如,Monster(妖怪)继承自Actor,Goblin继承自Monster;Human(人类)继承自Actor,Shopkeeper继承自Human。Actor 类包含一个名为 Render() 的函数,它知道如何渲染 Actor,因此对于每个 Goblin,您可以调用 Goblin.Render(),对于每个 Shopkeeper,您可以调用 Shoperkeeper.Render()。 * 这种方法有两个主要问题。首先是灵活性问题。如果您决定在游戏中访问一个Goblin Shopkeepers,那么您的继承树就会混乱。您拥有 Shopkeeper 类中的所有商店管理功能(销售、以物易物等),但您的 Goblin Shopkeeper 不能从 Shopkeeper 继承,因为这会使 Goblin Shopkeeper 成为 Human。毫无疑问,继承在软件开发中占有一席之地,但在游戏编程中它可能会导致问题。 ## 缓存的滥用 * 在游戏中,您通常每秒多次迭代一组对象,每帧运行它们的方法。例如,您的物理系统可能会遍历所有受物理影响的对象并调用 Object.Integrate(dt),更新它们的位置、速度和加速度。因此,传统上,您将拥有包含其所有状态的大对象,包括物理所需的状态,并且您会在每个需要更新的对象上调用集成函数。在每个对象的 Integrate() 方法中,您可以访问对象的位置、速度和加速度成员变量。当您访问位置时,它与附近的成员变量一起被拉入缓存行。其中一些附近的成员变量将很有用(速度和加速度),而另一些则不会。这是对缓存的巨大浪费,在性能瓶颈是数据从主内存到 CPU 内存所需时间的时代,这是一件大事。 * 潮流已经转向基于组件的设计来解决第一个问题。以 Unity 为例,所有游戏对象都是基于组件的。您从一个只有默认所需的 Transform 组件的空白对象开始,然后添加更多组件以赋予对象功能。但这并没有解决第二个问题。 * 第二个问题是通过将所有将被迭代的数据定期紧紧地打包到内存中来解决的,以便可以一次加载整个缓存行的数据,并且当下一个项目被迭代时,它的数据已经在缓存。这可以通过将组件定义为普通旧数据 (POD) 来解决,POD 本质上是一个仅包含相关数据的简单结构。继续这个物理例子,你可能有位置的变换,速度和加速度的刚体,重力常数 g 的重力。 * 然后,物理系统将遍历所有“包含”这三个组件的“对象”,只将它关心的数据拉入缓存。 Unity 正朝着这个方向发展,引入了自己的 ECS 实现,以及 Jobs 系统和 Burst 编译器。 事实上,首先 是观看 Mike Acton(Unity 首席程序员领导 ECS 开发)的演讲让我对这些东西感兴趣。 * 实际上,“对象”的传统概念已经不复存在。相反,我们有一个实体,它只是一个 ID。它不“包含”任何东西。相反,ID 用作组件数组的索引。数组在内存中是连续的,这很适合作为选择的数据结构。因此,物理系统可能有一个包含 Transform、RigidBody 和 Gravity 组件的所有实体的列表,并使用实体的 ID 作为 Transform 数组、 RigidBody 数组和 Gravity 数组的索引。 * 所以从概念上讲,这一切都很简单。实体是一个 ID。组件是数据结构。系统是对组件进行操作的逻辑。这篇文章的重点是如何以简单、易于理解和易于使用的方式实现这三个元素。