系统
了解系统如何用于更新场景状态
Decentraland 场景依赖于 系统 随时间更新任何数据,包括存储在每个实体的 组件.

系统 系统正是让场景变得动态的关键;它们是在场景游戏循环的每一帧中周期性执行的函数,会改变将要渲染的内容。
下面的示例展示了一个基本的系统声明:
系统中的函数可以执行你想要的任何操作。通常,它会作用于所有满足特定 查询,并按照特定逻辑更改存储在实体组件中的值。
在上面的示例中,系统 MoveSystem 是一个在游戏循环每一帧运行的函数,会更改场景中每个具有 Transform 的实体的位置。

你可以在场景中使用多个系统来解耦不同行为,使代码更简洁,也更易于扩展和复用。例如,一个系统可以处理物理,另一个系统可以让障碍实体持续来回移动,另一个系统则可以处理角色的 AI。
多个系统可以作用于同一个实体。例如,一个非玩家角色可能会根据 AI 自主移动,但当它不小心走出悬崖时,也会受到重力影响。在这种情况下,物理系统和 AI 系统甚至不需要彼此知晓。它们会在游戏循环的每一帧独立重新评估各自的当前状态,并实现各自独立的逻辑。
系统函数
系统的函数会按周期执行,每一帧游戏循环执行一次。这是自动发生的,你无需在代码中的任何地方显式调用这个函数。
在 Decentraland 场景中,你可以把游戏循环看作场景中所有系统函数的总和。
📔 注意:你不能将同一个系统函数多次添加到引擎中。如果你尝试添加一个已经添加过的系统,引擎会抛出错误: 系统“<name>”已添加到引擎中.
通过引用处理实体
某些组件和系统仅用于场景中的一个实体。例如,存储游戏分数的实体,或者场景中独一无二的主门。要在系统中访问这些实体之一,你只需在系统函数中通过名称引用该实体或其组件。
对于更大的项目,我们建议将系统定义与实体和组件的实例化分开放在不同文件中。
遍历组件查询
很多时候,你的场景会有多个同类型且行为相似的实体。例如,许多可以打开的门,或者许多可以攻击玩家的敌人。将这些相似实体交由一个系统处理是合理的,对列表进行遍历,并对每个实体执行相同的检查。
你不希望系统函数遍历 整个 场景中的实体集合,因为这在处理性能方面代价很高。为避免这种情况,你可以 查询组件,从而只遍历相关的实体。
例如,你的场景可以有一个 PhysicsSystem 用于计算场景中实体所受重力影响的系统。场景中的某些实体,比如树木,本来就不应该移动;因此,避免对它们计算重力影响是明智的。你可以定义一个 HasPhysics 组件,用来标记可能受到重力影响的实体,然后让 PhysicsSystem 只处理此查询返回的实体。
帧与帧之间的时间差
系统中的函数可以选择性地包含一个名为 dt、类型为 数字 (表示 时间差).
时间差 表示自游戏循环上一帧以来经过的时间,单位为秒。
该 dt 该参数是自游戏循环上一帧以来经过的真实时间,以秒为单位测量。SDK 不强制固定的帧率:宿主运行时会传递这个值,它会随场景的帧率而变化。
场景运行得越流畅,帧触发得就越频繁,每次的 dt 值就越小。当一帧处理耗时更长时,在下一帧到来之前会过去更多时间,而 dt 则会相应增大。
场景侧帧率上限为每秒 30 帧,在这种情况下 dt 为 1/30 (0.0333...).

当一帧的处理时间更长时,该帧的绘制会被延迟,而 dt 在下一帧传递的值会反映更长的间隔。引擎不会事后补偿这一延迟; dt 始终报告实际经过的时间,以便你的系统据此调整。

理想情况下,你应避免场景掉帧,因为这会影响玩家体验的质量。由于这取决于玩家设备的处理能力,因此你的场景始终应准备好优雅地处理这种情况。
该 dt 变量在帧处理超过默认时间时很有用。假设当前帧将花费与前一帧相同的时间,这些信息可用于计算需要对渐变变化调整多少,以使变化速率看起来平稳,并与帧间延迟成比例。
参见 实体定位 有关如何使用的示例 dt 以使移动更平滑。
按时间间隔循环
如果你想让系统按固定时间间隔执行某些操作,可以通过结合 dt 参数和计时器来实现。
还有一个快捷函数 setInterval 和 clearInterval 它在后台使用系统。这在你想要每隔 X 时间运行某个函数时,提供了更简单、更简短的写法。
其中第一个参数 callback,是要执行的函数(在本例中为 console.log()),第二个参数 ms(本例中为 10000)是每次函数执行之间等待的毫秒数。
要停止 setInterval 函数, clearInterval 则使用。
其中 intervalId 是对 setInterval 之前定义的返回值的引用。
对于更复杂的用例,如果会动态创建多个延迟和循环,那么可以考虑定义一个自定义组件,为每个实体存储各自的计时器值。参见 自定义组件.
系统执行顺序
在某些情况下,当你运行多个系统时,你可能会关心场景先执行哪个系统。
例如,你可能有一个 物理 系统,它更新场景中实体的位置,而另一个 边界 系统,它确保没有任何实体被放置到场景边界之外。在这种情况下,你需要确保 边界 系统最后执行。否则, 物理 系统可能会将实体移动到场景边界之外,但 边界 系统要到下一帧再次执行时才会发现。
将系统添加到引擎时,设置一个可选的 优先级 字段,以确定该系统相对于其他系统的执行时机。
优先级数值更高的系统会先执行,因此优先级为 5 的系统会在优先级为 1.
未显式指定优先级的系统默认优先级为 100000。由于数值越大越先运行,这些系统会在显式较低优先级的系统之前执行。
如果两个系统具有相同的优先级数值,就无法确定它们谁会先执行。
移除系统
系统实例可以被添加到引擎或从引擎移除,以启用或停用它。
如果定义了某个系统但没有将其添加到引擎,引擎就不会调用它的函数。
要移除系统,你必须先在将其添加到引擎时为它命名,以便之后引用该系统。
你传递给 engine.removeSystem() 的字符串必须与添加系统时为它分配的名称一致。
另一种移除系统的方法是将系统函数本身传递给 engine.removeSystem() 方法。这样在添加系统时就无需为它命名。
engine.removeSystem() 可以接受你在添加系统时指定的名称字符串,或者系统函数本身的引用。
你可以使用下面的方法,让系统在其目的完成后自行终止。
最后更新于