英文原文: https://github.com/mzaks/EntitasCookBook
组件是 ECS 中最简单的成分。它是数据的原子表示。它可以是空的,具有一个或多个属性,甚至可以标记为唯一。在下文中,我将解释关于 Entitas-CSharp 的差异。
最简单的组件——标志(Flag)组件
我将以 Match-One 示例项目中定义的组件为例。
using Entitas;
public sealed class MovableComponent : IComponent {
}
如您所见,组件是实现 IComponent 接口的类。它没有任何属性,因此它是一个标志组件。标志组件被定义为标志实体。在这种情况下,我们说某物是可移动的。因此,如果我们有一个实体,我们可以询问 entity.isMovable 并返回 true 或 false。我们也可以要求所有具有 MovableComponent 的实体,但我宁愿稍后讨论这个主题。
数据组件
数据组件可以有多个可以存储纯数据的属性:
using Entitas;
public sealed class PositionComponent : IComponent {
public int x;
public int y;
}
在 Entitas-CSharp 中,我们可以使用以下语句向实体添加位置:
entity.AddPosition(1, 2);
有一种方法可以检查实体是否具有组件(hasPosition)。我们还可以获取(position)、replace(ReplacePosition) 和remove(RemovePosition) 组件。每个实体只能有一种类型的组件集。这就是我们有 Replace 方法的原因。但是我们可以将所有不同类型的组件组合在一个实体中。这就是为什么最好将组件切得尽可能薄的原因。这在功能即兴创作方面为您带来了巨大的好处。
引用组件
引用组件在技术上等于数据组件,差异是相当合乎逻辑的。
using Entitas;
using UnityEngine;
public sealed class ViewComponent : IComponent {
public GameObject gameObject;
}
从技术上讲,引用组件也只是具有多个属性的组件,但这些属性并不代表数据,它们引用了一个复杂的对象。这有相当深刻的含义。这些组件更难序列化。它们指向一些在运行时创建的对象,因此按原样保留指针是没有用的。我们将在食谱部分深入探讨引用组件。
Action组件
这又只是数据组件的派生。但在这种情况下,该属性是一个函数/action。
using Entitas;
using System;
public sealed class DelegateComponent : IComponent {
public Action action;
}
在这种情况下,我们可以将函数/委托/Action存储在组件内,从而将其附加到实体上。这是对组件的有效使用,但它弊大于利,正如我们将在食谱部分讨论的那样。
独特的组件
在每个应用程序中,都有很多情况下您希望只拥有一个实例。这个想法体现在众所周知且经常令人讨厌的单例模式中。在 Entitas 中,我们有类似的东西,但更好。
我们之前讨论的每种类型的组件都可以定义为唯一的组件。
using Entitas;
using Entitas.CodeGeneration.Attributes;
[Unique]
public sealed class GameBoardComponent : IComponent {
public int columns;
public int rows;
}
为此,我们只需将类注释为唯一的。
该框架将确保只有一个独特组件的实例可以出现在您的上下文中(参见上下文章节)。这就是为什么在 Entitas-CSharp 中我们可以使用以下表达式获得一个独特组件的实例 - context.gameBoard。
现在它比单例模式更好吗?更好的是,我们将状态与行为分开。该组件也可以更换和移除。因此,它打破了单例模式的习惯用法,即对象在整个应用程序生命周期中是唯一且持久的。一个独特的组件更像是一个全局变量而不是一个单例。
一个应用程序需要多少个组件?
这个问题一直出现在 ECS 新手身上。和往常一样,正确的答案是 - 这取决于。但是根据我的经验,150 对于中核手游来说是一个相当不错的数字。事实上,我比较了我开发的两款不同的手机游戏,它们都有大约 150 个组件。也就是说,我使用 EntitasKit(Swift 实现)构建的 iOS 应用程序有大约 50 个组件。这也不足为奇,因为游戏往往比应用程序复杂得多。