Entitas 八 System 系统

系统(System)

ECS 或数据导向设计的主要目标是将状态与行为分离。系统是我们定义行为的地方。在系统中,我们可以编写创建新状态、更改给定状态或销毁状态的代码。

在 Entitas-CSharp 中,我们有多个接口,我们必须实现这些接口才能将一个类标记为系统。ISystem 接口是一个基础接口,我们不需要自己实现它。它只是一个被标记的接口(所谓的 ghost protocol),在内部使用。

如果我们想要一个应该周期性地执行的系统,我们需要实现 IExecuteSystem。这个接口只有一个方法 void Execute();。这是我们在每个时钟周期中执行代码的地方。

另一种周期性执行的系统类型是 ICleanupSystem。这个接口用于在所有 IExecuteSystem 运行后执行的逻辑。顾名思义,您应该将清理代码放在其 void Cleanup(); 方法中。由于这些仅仅是接口,我们可以有一个类同时实现这两个协议。有时从游戏逻辑的角度来看,这是完全有意义的。

设置和拆卸

通常情况下,在我们启动游戏时,我们需要首先创建初始状态。这就是为什么在 Entitas-CSharp 中我们有 IInitializeSystem 接口。它有一个 void Initialize(); 方法,该方法应该包含您的游戏初始化逻辑 - 基本上是创建您需要开始玩的所有实体和其他状态。

IInitializeSystem 的对应物是 ITearDownSystem。这个接口有一个 void TearDown(); 方法,在我们关闭游戏/关卡/场景之前(适用于您的用例)执行代码。

组合系统

到目前为止,在本章中我所描述的一切都只是接口,这些接口反映了我们用于拆分行为代码的惯例。我见过一些项目,其中的人们在没有系统的情况下使用了 Entitas。他们实现了自己的命令模式。但是,如果您想要遵循系统方法,您可能需要在某种层次结构中将系统组合在一起。为此,我们提供了一个 Systems 类,它实现了 IInitializeSystem, IExecuteSystem, ICleanupSystem, ITearDownSystem 接口。我们可以将一个系统添加到 Systems 类的实例中。这样,当我们在此实例上调用 Execute()Cleanup()Initialize()TearDown() 方法时,它将在添加的系统上调用这些方法。Systems 类在 组合模式 意义上是一个典型的父节点。

当我们看 MatchOne 示例时,可以看到我们不直接使用 Systems 类:

public class MatchOneSystems : Feature {

    public MatchOneSystems(Contexts contexts) {

        // 输入
        Add(new InputSystems(contexts));

        // 更新
        Add(new GameBoardSystems(contexts));
        Add(new GameStateSystems(contexts));

        // 渲染
        Add(new ViewSystems(contexts));

        // 销毁
        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 中,还有另一种处理数据的方法,您将在下一章中详细了解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值