> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md).

# 系统

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

Decentraland 场景依赖于 *系统* 随时间更新任何数据，包括存储在每个实体的 [组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/entities-components.md).

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-de62a18d51b28bebd55c86e8ef5a8a783467ae62%2Fecs-big-picture.png?alt=media)

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

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

```ts
// 定义系统
function mySystem() {
  console.log("每一帧都会执行。我的系统正在运行")
}
// 将系统添加到引擎
engine.addSystem(mySystem)
```

系统中的函数可以执行你想要的任何操作。通常，它会作用于所有满足特定 [查询](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/querying-components.md)，并按照特定逻辑更改存储在实体组件中的值。

```ts
function moveSystem(dt: number) {
  // 遍历所有具有 Transform 的实体
  for (const [entity] of engine.getEntitiesWith(Transform)) {

  // 获取可变的 Transform 组件
  const transform = Transform.getMutable(entity)

  // 更新位置值
    transform.position.z += 0.01
  }
}

engine.addSystem(moveSystem)
```

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

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-826c0dcfcfc5a6d5719de5a0939214a534078567%2Fecs-system-new.png?alt=media)

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

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

## 系统函数

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

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

{% hint style="warning" %}
**📔 注意**：你不能将同一个系统函数多次添加到引擎中。如果你尝试添加一个已经添加过的系统，引擎会抛出错误： `系统“<name>”已添加到引擎中`.
{% endhint %}

## 通过引用处理实体

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

```ts
// 在模块作用域声明实体引用，以便系统可以访问它
let game: Entity

export function main(){
	// 创建一个新实体
	game = engine.addEntity()

	// 向该实体添加组件
	ScoreComponent.create(game)
}

// 定义系统
export function UpdateScore() {

  // 调用对单个实体的引用
  const points = ScoreComponent.get(game).points
  console.log(points)
}

// 将系统添加到引擎
engine.addSystem(UpdateScore)
```

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

## 遍历组件查询

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

你不希望系统函数遍历 *整个* 场景中的实体集合，因为这在处理性能方面代价很高。为避免这种情况，你可以 [查询组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/querying-components.md)，从而只遍历相关的实体。

例如，你的场景可以有一个 `PhysicsSystem` 用于计算场景中实体所受重力影响的系统。场景中的某些实体，比如树木，本来就不应该移动；因此，避免对它们计算重力影响是明智的。你可以定义一个 `HasPhysics` 组件，用来标记可能受到重力影响的实体，然后让 `PhysicsSystem` 只处理此查询返回的实体。

```ts
// 定义系统
export function PhysicsSystem() {
  // 遍历所有具有 HasPhysics 的实体
  for (const [entity] of engine.getEntitiesWith(HasPhysics)) {

  // 获取可变的 Transform 组件
  const transform = Transform.getMutable(entity)

  // 计算物理效果
  }
}

// 将系统添加到引擎
engine.addSystem(PhysicsSystem)
```

## 帧与帧之间的时间差

系统中的函数可以选择性地包含一个名为 `dt`、类型为 `数字` （表示 *时间差*).

```ts
function MySystem(dt: number) {

  // 更新场景
  console.log("距离上一帧的时间：", dt)
}

engine.addSystem(MySystem)
```

*时间差* 表示自游戏循环上一帧以来经过的时间，单位为秒。

该 `dt` 该参数是自游戏循环上一帧以来经过的真实时间，以秒为单位测量。SDK 不强制固定的帧率：宿主运行时会传递这个值，它会随场景的帧率而变化。

场景运行得越流畅，帧触发得就越频繁，每次的 `dt` 值就越小。当一帧处理耗时更长时，在下一帧到来之前会过去更多时间，而 `dt` 则会相应增大。

场景侧帧率上限为每秒 30 帧，在这种情况下 `dt` 为 *1/30* (0.0333...).

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-9ebcec63913c57ddf96107a817b14797211c1de2%2Fecs-framerate.png?alt=media)

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

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-951edc3e7611eb138145de377ec07dfc8ec4836b%2Fecs-framerate-heavy.png?alt=media)

理想情况下，你应避免场景掉帧，因为这会影响玩家体验的质量。由于这取决于玩家设备的处理能力，因此你的场景始终应准备好优雅地处理这种情况。

该 `dt` 变量在帧处理超过默认时间时很有用。假设当前帧将花费与前一帧相同的时间，这些信息可用于计算需要对渐变变化调整多少，以使变化速率看起来平稳，并与帧间延迟成比例。

参见 [实体定位](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/entity-positioning.md) 有关如何使用的示例 `dt` 以使移动更平滑。

## 按时间间隔循环

如果你想让系统按固定时间间隔执行某些操作，可以通过结合 `dt` 参数和计时器来实现。

```ts
let timer: number = 10

function LoopSystem(dt: number) {
  timer -= dt
  if (timer <= 0) {
      timer = 10
      // 执行某些操作
    }
}

engine.addSystem(LoopSystem)
```

还有一个快捷函数 `setInterval` 和 `clearInterval` 它在后台使用系统。这在你想要每隔 X 时间运行某个函数时，提供了更简单、更简短的写法。

```ts
import { timers } from '@dcl/sdk/ecs'

const intervalId = timers.setInterval(() => {
    console.log('每 10 秒打印一次这条消息')
}, 10000)
```

其中第一个参数 `callback`，是要执行的函数（在本例中为 `console.log()`），第二个参数 `ms`（本例中为 10000）是每次函数执行之间等待的毫秒数。

要停止 `setInterval` 函数， `clearInterval` 则使用。

```
timers.clearInterval(intervalId)
```

其中 `intervalId` 是对 `setInterval` 之前定义的返回值的引用。

对于更复杂的用例，如果会动态创建多个延迟和循环，那么可以考虑定义一个自定义组件，为每个实体存储各自的计时器值。参见 [自定义组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/custom-components.md).

## 系统执行顺序

在某些情况下，当你运行多个系统时，你可能会关心场景先执行哪个系统。

例如，你可能有一个 *物理* 系统，它更新场景中实体的位置，而另一个 *边界* 系统，它确保没有任何实体被放置到场景边界之外。在这种情况下，你需要确保 *边界* 系统最后执行。否则， *物理* 系统可能会将实体移动到场景边界之外，但 *边界* 系统要到下一帧再次执行时才会发现。

将系统添加到引擎时，设置一个可选的 `优先级` 字段，以确定该系统相对于其他系统的执行时机。

```ts
engine.addSystem(PhysicsSystem, 5)
engine.addSystem(BoundariesSystem, 1)
```

优先级数值更高的系统会先执行，因此优先级为 *5* 的系统会在优先级为 *1*.

未显式指定优先级的系统默认优先级为 *100000*。由于数值越大越先运行，这些系统会在显式较低优先级的系统之前执行。

如果两个系统具有相同的优先级数值，就无法确定它们谁会先执行。

## 移除系统

系统实例可以被添加到引擎或从引擎移除，以启用或停用它。

如果定义了某个系统但没有将其添加到引擎，引擎就不会调用它的函数。

要移除系统，你必须先在将其添加到引擎时为它命名，以便之后引用该系统。

```ts
// 声明系统
function mySystem(dt: number){
  console.log("距离上一帧的延迟：", dt)
}

// 添加系统（为其指定优先级和名称）
engine.addSystem(mySystem, 1, "DelaySystem")

// 移除系统
engine.removeSystem("DelaySystem")
```

你传递给 `engine.removeSystem()` 的字符串必须与添加系统时为它分配的名称一致。

另一种移除系统的方法是将系统函数本身传递给 `engine.removeSystem()` 方法。这样在添加系统时就无需为它命名。

```ts
// 声明系统
function mySystem(dt: number){
  console.log("距离上一帧的延迟：", dt)
}

// 添加系统
engine.addSystem(mySystem)

// 通过传递其函数来移除系统
engine.removeSystem(mySystem)
```

`engine.removeSystem()` 可以接受你在添加系统时指定的名称字符串，或者系统函数本身的引用。

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

```ts
   const mySystem = function(dt: number){
        time += dt
        if(time > 3){
		engine.removeSystem(mySystem)
        }
    }
    engine.addSystem(mySystem)
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
