英文原文:https://github.com/mzaks/EntitasCookBook
ECS 或面向数据的设计的主要目标是分离状态形式的行为。系统是我们定义行为的地方。在系统中,我们可以编写创建新状态、更改给定状态或破坏状态的代码。
在 Entitas-CSharp 中,我们必须实现多个接口才能将类标记为系统。 ISystem 接口是一个基础接口,我们不必自己实现。它只是内部使用的标记(所谓的幽灵协议)。
如果我们想有一个应该定期执行的系统,我们需要实现 IExecuteSystem。这个接口只有一个方法void Execute();。这是我们放置应该在每个tick 上执行的代码的方法。
另一种定期执行的系统是 ICleanupSystem。这用于在所有 IExecuteSystems 运行后执行的逻辑。顾名思义,你应该把清理代码放在它的 void Cleanup();方法。由于这些只是接口,我们可以拥有一个实现这两种协议的类。有时,从游戏逻辑的角度来看,这完全是有道理的。
通常,当我们开始游戏时,我们需要先创建初始状态。这就是为什么在 Entitas-CShapr 中我们有 IInitializeSystem 接口。它有一个 void Initialize();应该包含您的游戏初始化逻辑的方法 - 基本上创建您需要开始玩的所有实体和其他状态。
IInitializeSystem 的对应物是 ITearDownSystem。这个有 void TearDown();方法,我们在其中放置将在关闭游戏/关卡/场景之前执行的代码(适合您的用例)。
到目前为止,我在本章中描述的所有内容都只是反映我们用来分解行为代码的约定的接口。我看到人们在没有系统的情况下使用 Entitas 的项目。他们在那里实现了自己的命令模式。但是,如果您想遵循系统方法,您可能希望以特定层次结构将系统组合在一起。为了做到这一点,我们提供了一个系统类,它实现了 IInitializeSystem、IExecuteSystem、ICleanupSystem、ITearDownSystem 接口。我们可以将系统添加到 Systems 类的实例中。因此,当我们在此实例上调用 Execute()、Cleanup()、Initialize()、TearDown() 方法时,它将在添加的系统上调用这些方法。系统类是复合模式意义上的典型父节点。
当我们查看 MatchOne 示例时,我们可以看到我们没有直接使用 Systems 类:
public class MatchOneSystems : Feature {
public MatchOneSystems(Contexts contexts) {
// Input
Add(new InputSystems(contexts));
// Update
Add(new GameBoardSystems(contexts));
Add(new GameStateSystems(contexts));
// Render
Add(new ViewSystems(contexts));
// Destroy
Add(new DestroySystem(contexts));
}
}
这里我们扩展了 Feature 类。Feature 类是生成的类,它扩展了 Systems 类或 DebugSystems 类,这取决于我们是否要在启用可视调试的情况下运行。可视化调试会消耗大量资源,不应在您进行生产构建或运行游戏移动设备时启用。这就是生成 Feature 类以使我们的真实代码更简单的原因。
您可能从上面的代码片段中注意到的另一件事是,我们传入了一个 Contexts 类。 Contexts 类是在 Entitas-CSharp 中生成的另一个便利类,我们可以引用不同的上下文实例。生成的代码包含每个上下文类型实例的 getter(在设备章节中阅读有关代码生成器的更多信息)。
在我们实现系统并将它们组合成层次结构之后,我们需要在某处调用 Execute()、Cleanup()、Initialize()、TearDown() 方法。为此,我们通常会创建一个执行此操作的 MonoBehaviour。如果您不使用 Unity3D,您需要自己查看要触发这些方法的位置。我想再次使用 MatchOne 来展示这样的 MonoBehaviour 类的外观:
using Entitas;
using UnityEngine;
public class GameController : MonoBehaviour {
Systems _systems;
void Start() {
Random.InitState(42);
var contexts = Contexts.sharedInstance;
_systems = new MatchOneSystems(contexts);
_systems.Initialize();
}
void Update() {
_systems.Execute();
_systems.Cleanup();
}
void OnDestroy() {
_systems.TearDown();
}
}
一个经常出现的问题是,定期系统是否应该在 FixedUpdate 而不是 Update 上执行。这通常是您个人的决定。我通常将系统安排在 Update 上,如果在您的情况下安排 FixedUpdate 甚至 LateUpdate 很重要,这是您的决定。您甚至可以去香蕉并拥有多个系统层次结构,其中一个在 Update 上执行,另一个在 FixedUpdate 上执行,但不确定这是一个好主意。
执行系统会定期运行,所以我们通常做的是,我们在系统构造函数中设置一个或多个组,然后在 Execute 中迭代这些组中的实体并更改它们或创建新实体。
一般来说,我们是从上下文中提取数据并对其进行处理。在 Entitas-CSharp 中还有另一种处理数据的方法,您将在下一章中了解它的全部内容。