> 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/entities-components.md).

# 实体与组件

了解 Decentraland 场景中实体和组件的基础知识

Decentraland 场景围绕 [*实体*, *组件* 和 *系统*](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system)。这是几种游戏引擎架构中常用的一种模式，便于轻松组合和扩展。

![](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)

## 概览

*实体* 是构建 Decentraland 场景中一切内容的基本单元。场景中所有可见和不可见的 3D 对象以及音频播放器都会是一个实体。实体本身只不过是一个可被组件引用的 ID。实体本身没有自己的属性或方法，它只是将多个组件组合在一起。

*组件* 定义实体的特征。例如，一个 `Transform` 组件会存储实体的坐标、旋转和缩放。一个 `MeshRenderer` 组件在场景中渲染时会给实体一个可见的形状（比如立方体或球体），一个 `材质` 组件会给实体一个颜色或纹理。你也可以创建自定义组件来承载场景所需的数据，例如一个自定义的 `生命值` 可以存储实体剩余的生命值，并将其添加到代表游戏中非玩家敌人的实体上。

如果你熟悉 Web 开发，可以将实体视为 *元素* 包裹在一个 *DOM* 树，而将组件视为 *属性* 中这些元素的。

在 [Creator Hub 中的场景编辑器](/creator/content-creator-zh/chang-jing-bian-ji-qi/kai-shi-shi-yong/about-editor.md)，你可以通过选中它来查看属于某个实体的组件。

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

{% hint style="warning" %}
**📔 注意**：在 SDK 的早期版本中，实体是 *对象* ，会被实例化，并且可以扩展以添加函数。从 SDK 7.0 版本开始，实体只是一个 ID。这种结构更符合 [数据导向编程](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/data-oriented-programming.md) 的原则，并且有助于提升场景性能。
{% endhint %}

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

像 `Transform`, `材质` 或任何 *形状* 组件这样的组件与场景的渲染密切相关。如果这些组件中的值发生变化，仅这一点就足以让引擎在下一帧改变场景的渲染方式。

引擎是场景中居于中间位置、管理所有其他部分的部分。它决定渲染哪些实体，以及玩家如何与它们交互。它还协调来自 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md) 的函数在何时执行。

组件旨在存储其所引用实体的数据。它们只能存储这些数据，不能自行修改这些数据。对组件值的所有更改都由 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md)执行。系统与组件和实体本身是完全解耦的。实体和组件并不关心哪些 *系统* 正在作用于它们。

## 实体和组件的语法

下面的示例展示了声明和配置基本实体与组件的一些基本操作。

```ts
export function main() {
	// 创建一个实体
	const door = engine.addEntity()

	// 通过 transform 组件为实体设置位置
	Transform.create(door, {
		position: Vector3.create(5, 1, 5),
	})

	// 通过 GltfContainer 组件为实体赋予可见形状
	GltfContainer.create(door, {
		src: 'assets/models/door.glb',
	})
}
```

{% hint style="warning" %}
**📔 注意**：在 SDK 的早期版本中，需要手动将实体添加到引擎中才能开始渲染。从 SDK 7 版本开始，只要实体被分配了组件，就会被隐式添加到引擎中。
{% endhint %}

当创建组件时，它总是被分配给一个父实体。然后组件的值会影响该实体。

{% hint style="info" %}
**💡 提示**：与其逐个创建实体，不如从一个 [复合体](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/composites.md) 文件。
{% endhint %}

## 移除实体

要从引擎中移除实体，请使用 `engine.removeEntity()`。此函数会返回 `布尔值`: `真` 如果实体被移除， `false` 如果移除被拒绝。

```ts
export function main() {
	// 创建一个实体
	const door = engine.addEntity()

	// 通过 GltfContainer 组件为实体赋予可见形状
	GltfContainer.create(door, {
		src: 'assets/models/door.glb',
	})

	// 移除实体
	const removed = engine.removeEntity(door)
	console.log('Entity removed:', removed) // true
}
```

如果被移除的实体有任何子实体，这些子实体会将其父实体改回默认的 `engine.RootEntity` 实体，它位于场景基准位置，缩放为 *1*.

### 渲染器保留的实体

某些实体 ID 被渲染器保留给远程玩家头像。你无法移除这些实体。如果你对一个渲染器保留的实体调用 `engine.removeEntity()` ，它会返回 `false` 并保持所有组件不变。

该 [命名的保留实体](#reserved-entities) (`engine.RootEntity`, `engine.PlayerEntity`, `engine.CameraEntity`）是一个特殊情况： `engine.removeEntity()` 仍然返回 `false` ，但它们的组件会被 **中是** 清除。这意味着你可以从中清除自己的组件（例如移除一个 `InputModifier` 来自 `engine.PlayerEntity`），即使实体 ID 本身从未被释放。

```ts
// 尝试移除一个保留实体
const result = engine.removeEntity(engine.PlayerEntity)
console.log(result) // false — 实体 ID 未被释放
// 但 PlayerEntity 上场景自身的组件会被清除
```

你可以在移除实体时检查返回值来处理边缘情况：

```ts
if (!engine.removeEntity(someEntity)) {
	console.log('Entity could not be removed (reserved)')
}
```

### 移除实体及其子实体

要移除一个实体以及它的所有子实体（以及其子实体的子实体，递归地），请使用 `removeEntityWithChildren()` 辅助函数。

```ts
export function main() {
	// 创建父实体
	const door = engine.addEntity()

	// 创建子实体
	const doorKnob = engine.addEntity()

	// 给这些实体赋予可见形状
	GltfContainer.create(door, {
		src: 'models/door.glb',
	})
	GltfContainer.create(doorKnob, {
		src: 'models/doorKnob.glb',
	})

	// 父实体
	Transform.create(doorKnob, {
		parent: door,
	})

	// 同时移除父实体和子实体
	removeEntityWithChildren(engine, door)
}
```

{% hint style="warning" %}
**注意：** 如果树中的任何位置存在一个渲染器保留的实体， `removeEntityWithChildren` 会移除所有其他后代，但保留该保留实体。该保留实体的 `Transform.parent` 会指向一个已移除的实体。只有当你的场景把一个保留实体作为场景实体的子实体时才会发生这种情况，这并不常见。
{% endhint %}

{% hint style="info" %}
**💡 提示**：与其从引擎中移除实体，在某些情况下把它设为不可见可能更好，因为这样你之后可以无延迟地再次加载它。请参见 [设为不可见](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/shape-components.md#make-invisible)
{% endhint %}

### 在幕后移除实体

实体只是一个由其组件引用的 ID。因此，当你移除一个实体时，实际上是在移除引用该实体的每个组件。如果你手动移除实体的所有组件，对玩家来说看起来和执行 `engine.removeEntity()`一样。不过， `engine.removeEntity()` 还会执行一些额外的内部记录，将实体 ID 标记为不再使用，因此它始终是移除实体的推荐方式。

## 嵌套实体

一个实体可以将其他实体作为子实体。借助这一点，我们可以像网页的 HTML 一样，将实体组织成树状结构。

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

要将一个实体设为另一个实体的父实体，子实体必须具有一个 `Transform` 组件。然后你可以将 `parent` 字段设置为对父实体的引用。

```ts
export function main() {
	// 创建实体
	const parentEntity = engine.addEntity()

	const childEntity = engine.addEntity()

	// 设置父实体
	Transform.create(childEntity, {
		parent: parentEntity,
	})
}
```

一旦分配了父实体，就可以从子实体的 `parent` 字段中读取它的 `Transform` 组件。

```ts
// 从实体获取父实体
const parent = Transform.get(childEntity).parent
```

如果父实体具有一个 `Transform` 组件，该组件会影响其位置、缩放或旋转，那么它的子实体也会受到影响。任何位置或旋转值都会相加，任何缩放值都会相乘。

如果父实体或子实体没有 `Transform` 组件，则会使用以下默认值。

* 对于 **位置**，父实体的中心是 *0, 0, 0*
* 对于 **旋转** 父实体的旋转四元数是 *0, 0, 0, 1* （等同于欧拉角 *0, 0, 0*)
* 对于 **缩放**，则认为父实体的大小为 *1*。父实体的任何缩放都会按比例影响缩放和位置。

没有形状组件的实体在场景中是不可见的。它们可作为包装器，用于将多个实体作为一个组进行处理和定位。

要将子实体与父实体分离，可以将实体的父实体设为 `engine.RootEntity`.

```ts
const mutableChildTransform = Transform.getMutable(childEntity)
mutableChildTransform.parent = engine.RootEntity
```

{% hint style="warning" %}
**📔 注意**：在处理与其他玩家同步的嵌套实体时，请使用 Transform 中的 `parentEntity()` 函数，而不是 `parent` 实体。参见 [父子实体](/creator/content-creator-zh/chang-jing-sdk7/wang-luo/serverless-multiplayer.md#parented-entities)
{% endhint %}

在场景编辑器中，你可以在左侧面板看到场景中所有嵌套实体的完整层级结构。

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-60929069a225f48ce0d6c94d9dce285ff875af10%2Fentity-tree-example.png?alt=media)

## 按 ID 获取实体

场景中的每个实体都有一个唯一编号 *id*。你可以根据此 ID 从引擎中检索引用特定实体的组件。

```typescript
// 获取 Transform 组件
Transform.get(1000 as Entity)
```

{% hint style="warning" %}
**📔 注意**：介于 *0* 和 *511* 之间的实体 ID 被引擎保留给固定实体，例如玩家头像、基础场景等。
{% endhint %}

例如，如果玩家的点击或一个 [射线投射](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/raycasting.md) 击中了一个实体，这将返回被击中的实体的 ID，你可以使用上面的命令获取与该 ID 匹配的实体的 Transform 组件。你也可以用同样的方式获取该实体的任何其他组件。

## 按名称获取实体

在场景编辑器中通过拖放添加实体时，每个实体都有一个唯一名称。使用 `engine.getEntityOrNullByName()` 函数从你的代码中引用这些实体之一。将实体名称作为字符串传入，名称应与场景编辑器 UI 中左侧树视图里显示的一致。

```ts
function main() {
	const door = engine.getEntityOrNullByName('door3')
}
```

{% hint style="warning" %}
**📔 注意**：请确保你只在 `engine.getEntityOrNullByName()` 在 `main()` 函数内、在 `main()`，或者在系统中使用。如果在这些上下文之外使用，场景编辑器 UI 中创建的实体可能还尚未实例化。
{% endhint %}

你可以对通过此方法获取的实体执行任何操作，例如添加或移除组件、修改现有组件的值，或者将实体从引擎中移除。

```ts
function main() {
	// 获取实体
	const door = engine.getEntityOrNullByName('door-3')
	// 验证实体是否存在
	if (door) {
		// 添加指针事件回调
		pointerEventsSystem.onPointerDown(
			{
				entity: door,
				opts: { button: InputAction.IA_PRIMARY, hoverText: 'Open' },
			},
			function () {
				// 打开门
			}
		)
	}
}
```

通过场景编辑器 UI 添加的所有实体都有一个 `名称` 组件，你可以像这样遍历它们：

```ts
function main() {
	for (const [entity, name] of engine.getEntitiesWith(Name)) {
		console.log({ entity, name })
	}
}
```

## 添加或替换组件

每个实体对于某一给定类型的组件只能有一个。例如，如果你尝试给一个已经有 Transform 的实体再赋予一个 Transform，就会导致错误。

为了避免这个错误，你可以使用 `.createOrReplace` 而不是 `.create`。如果同类组件已存在，此命令会覆盖它；否则，它会像 `.create`.

```ts
Transform.createOrReplace(door, {
	position: Vector3.create(5, 1, 5),
})
```

{% hint style="warning" %}
**📔 注意**：由于 `.createOrReplace` 在创建组件之前会执行额外检查，因此使用 `.create`总是更高效。如果你确定实体还没有你要添加的那种组件，请使用 `.create`.
{% endhint %}

## 从实体访问组件

你可以使用实体的 `.get()` 或 `getMutable()` 函数来创建颜色。

```ts
export function main() {
	// 创建实体
	const box = engine.addEntity()

	// 创建并向该实体添加组件
	Transform.create(box)

	// 获取组件的只读版本
	let transform = Transform.get(box)

	// 获取组件的可变版本
	let transform = Transform.getMutable(box)
}
```

该 `get()` 函数获取组件的只读引用。你不能通过这个引用更改组件中的任何值。

如果你希望更改组件的值，请改用 `getMutable()` 函数。如果你更改组件可变版本中的值，就会直接影响该组件所属的实体。

参见 [可变数据](/creator/content-creator-zh/chang-jing-sdk7/bian-cheng-mo-shi/mutable-data.md) 了解更多详情。

{% hint style="warning" %}
**📔 注意**：只有在 `getMutable()` 你确实要更改组件值时才使用。否则，总是使用 `get()`。这种做法遵循 [数据导向编程](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/data-oriented-programming.md)的原则，并能显著提升场景性能。
{% endhint %}

```ts
// 获取组件的可变版本
let transform = Transform.getMutable(box)

// 更改组件的一个值
transform.scale.x = 5
```

上面的示例直接修改了 *x* Transform 组件上的 scale 值。

如果你不完全确定实体是否具有你要检索的组件，请使用 `getOrNull()` 或 `getMutableOrNull()`.

{% hint style="warning" %}
**📔 注意**：尽量避免使用 `getOrNull()` 或 `getMutableOrNull()` 在可能的情况下，因为这些函数涉及额外检查，因此效率低于 `.get()` 和 `getMutable()`.
{% endhint %}

```ts
//  getOrNull
const transformOrNull = Transform.getOrNull(myEntity)

//  getMutableOrNull
const mutableTransformOrNull = Transform.getMutableOrNull(myEntity)
```

如果你尝试检索的组件在实体中不存在：

* `get()` 和 `getMutable()` 会返回错误。
* `getOrNull()` 和 `getMutableOrNull()` 返回 `空值`.

## 从实体移除组件

要从实体中移除一个组件，请使用实体的 `deleteFrom()` 组件类型的方法。

```ts
Transform.deleteFrom(myEntity)
```

如果你尝试移除实体中不存在的组件，此操作不会抛出任何错误。

{% hint style="warning" %}
**📔 注意**：要一次移除实体的所有组件，请参见 [此部分](#remove-entities)
{% endhint %}

## 检查组件是否存在

你可以使用 `has()` 函数来检查实体是否拥有某个组件的实例。该函数返回 *真* 如果组件存在，返回 *false* 如果不存在。这在场景中的条件逻辑里非常有用。

```ts
const hasTransform = Transform.has(myEntity)
```

{% hint style="info" %}
**💡 提示**：你也可以 [查询组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/querying-components.md) 来获取包含某个特定组件，或一组特定组件的完整列表。不要手动遍历场景中的所有实体并逐个使用 `has()`进行检查，那种方法效率低得多。
{% endhint %}

## 检查组件的变化

使用 `onChange` 函数，当给定实体的组件值发生变化时运行一个回调函数。它适用于任何组件，是帮助保持代码可读性的绝佳捷径。

回调函数可以包含一个输入参数，用于传入组件的新状态。

```ts
Transform.onChange(cubeEntity, (newTransform) => {
	if (!newTransform) return
	console.log(
		'立方体位置已更改：',
		newTransform.position,
		newTransform.rotation
	)
})

VisibilityComponent.onChange(cubeEntity, (newVisibilityComponent) => {
	if (!newVisibilityComponent) return
	console.log('Cube visibility changed: ', newVisibilityComponent.visible)
})
```

如果组件从实体中被移除，则该函数会以 `undefined`.

{% hint style="info" %}
**💡 提示**： `.onChange()` 函数既适用于 SDK 的原生组件，也适用于 [自定义组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/custom-components.md) ，由创建者定义。
{% endhint %}

## 获取子实体

要访问父实体的所有直接子实体，请使用 `getEntitiesWithParent`。它将 `引擎` 和 `parent` 实体作为参数，并返回所有以该特定实体为父实体的实体列表。请注意，它只返回直接子实体，不包括子实体的子实体。

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

const children = getEntitiesWithParent(engine, myEntity)
for (const child of children) {
   // 处理每个子实体
}
```

如果想访问某个实体的所有后代，而不管它们嵌套得有多深，请使用函数 `getComponentEntityTree()`。该函数无需逐级手动遍历层级，而是返回一个易于迭代的所有后代的扁平列表。它还会筛选出仅包含具有给定组件或组件列表的实体。

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

export function main() {
	// 创建一个带有嵌套子实体的父实体
	const parentEntity = engine.addEntity()
	Transform.create(parentEntity, {
		position: Vector3.create(8, 0, 8),
	})

	// ... 假设父实体有多个子实体和孙实体

	// 遍历父实体的所有后代
	for (const descendantEntity of getComponentEntityTree(
		engine,
		parentEntity,
		Transform
	)) {
		// 访问每个后代实体
		const transform = Transform.get(descendantEntity)
		console.log('后代位置：', transform.position)
	}
}
```

该 `getComponentEntityTree` 函数接受三个参数：

* `引擎`：运行这些实体的引擎实例
* `实体`：作为起点的根实体
* `组件`：用于筛选的组件（通常 `Transform` 用于空间层级）

该函数返回一个生成器，它会在树结构中产生每个后代实体。只有具有指定组件的实体才会包含在结果中。

你可以将它与其他组件检查结合使用，以在层级中找到特定实体：

```ts
// 查找具有特定名称的所有后代
for (const descendantEntity of getComponentEntityTree(
	engine,
	parentEntity,
	Transform
)) {
	const name = Name.getOrNull(descendantEntity)
	if (name && name.value === 'targetEntity') {
		console.log('Found target entity:', descendantEntity)
	}
}
```

## 保留实体

某些实体 ID 被保留给每个场景中都存在的特殊实体。可通过以下别名访问：

* `engine.RootEntity`
* `engine.PlayerEntity`
* `engine.CameraEntity`

{% hint style="warning" %}
**📔 注意**：避免在这些实体初始化之前引用它们。为避免此问题，请在 `main()` 函数中，或在系统中引用这些实体。
{% endhint %}

## 根实体

场景中的所有实体都是 `engine.RootEntity`的子实体，直接或间接如此。

这个实体没有 Transform 组件，但它用于处理一些更全局的设置，例如 [天空盒控制](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/skybox-control.md), [光标位置](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/user-data.md#check-the-players-cursor-position)，或 [屏幕尺寸](/creator/content-creator-zh/chang-jing-sdk7/2d-ui/ui-positioning.md#responsive-ui-size).

## 玩家实体

该 `engine.PlayerEntity` 实体代表玩家的头像。

获取玩家的 `Transform` 组件以获取玩家当前的位置和旋转，参见 [用户数据](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/user-data.md)。玩家的 Transform 是只读的，如需修改请使用 `movePlayerTo()` 函数， [了解更多](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/player-avatar.md#move-player).

你也可以通过将对象设为此实体的子实体来附加到玩家上，不过 [附加到玩家](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/entity-positioning.md#attach-an-entity-to-an-avatar) 通常是更好的选择。

## 摄像机实体

该 `engine.CameraEntity` 实体代表玩家的摄像机。

获取摄像机的 `Transform` 组件以获取摄像机的位置和旋转。此实体的 Transform 也是只读的。要修改摄像机角度或位置，请使用一个 [虚拟摄像机](/creator/content-creator-zh/chang-jing-sdk7/3d-nei-rong-ji-chu/camera.md#using-virtual-cameras).

你也可以获取摄像机的 `CameraMode` 组件，以了解玩家是否正在使用第一人称或第三人称摄像机模式，参见 [摄像机模式](/creator/content-creator-zh/chang-jing-sdk7/jiao-hu-xing/user-data.md#check-the-players-camera-mode).


---

# 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/entities-components.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.
