For the complete documentation index, see llms.txt. This page is also available as Markdown.

系统

了解系统如何用于更新场景状态

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



系统 系统正是让场景变得动态的关键;它们是在场景游戏循环的每一帧中周期性执行的函数,会改变将要渲染的内容。

下面的示例展示了一个基本的系统声明:

系统中的函数可以执行你想要的任何操作。通常,它会作用于所有满足特定 查询,并按照特定逻辑更改存储在实体组件中的值。

在上面的示例中,系统 MoveSystem 是一个在游戏循环每一帧运行的函数,会更改场景中每个具有 Transform 的实体的位置。



你可以在场景中使用多个系统来解耦不同行为,使代码更简洁,也更易于扩展和复用。例如,一个系统可以处理物理,另一个系统可以让障碍实体持续来回移动,另一个系统则可以处理角色的 AI。

多个系统可以作用于同一个实体。例如,一个非玩家角色可能会根据 AI 自主移动,但当它不小心走出悬崖时,也会受到重力影响。在这种情况下,物理系统和 AI 系统甚至不需要彼此知晓。它们会在游戏循环的每一帧独立重新评估各自的当前状态,并实现各自独立的逻辑。

系统函数

系统的函数会按周期执行,每一帧游戏循环执行一次。这是自动发生的,你无需在代码中的任何地方显式调用这个函数。

在 Decentraland 场景中,你可以把游戏循环看作场景中所有系统函数的总和。

通过引用处理实体

某些组件和系统仅用于场景中的一个实体。例如,存储游戏分数的实体,或者场景中独一无二的主门。要在系统中访问这些实体之一,你只需在系统函数中通过名称引用该实体或其组件。

对于更大的项目,我们建议将系统定义与实体和组件的实例化分开放在不同文件中。

遍历组件查询

很多时候,你的场景会有多个同类型且行为相似的实体。例如,许多可以打开的门,或者许多可以攻击玩家的敌人。将这些相似实体交由一个系统处理是合理的,对列表进行遍历,并对每个实体执行相同的检查。

你不希望系统函数遍历 整个 场景中的实体集合,因为这在处理性能方面代价很高。为避免这种情况,你可以 查询组件,从而只遍历相关的实体。

例如,你的场景可以有一个 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() 可以接受你在添加系统时指定的名称字符串,或者系统函数本身的引用。

你可以使用下面的方法,让系统在其目的完成后自行终止。

最后更新于