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

实体与组件

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

Decentraland 场景围绕……构建 实体, 组件 和 系统。这是多个游戏引擎架构中常用的一种模式,可实现良好的可组合性和可扩展性。



概述

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

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

如果你熟悉网页开发,可以把实体看作等同于 元素 中的 DOM 树中的元素,而组件则相当于 属性 。

在 Creator Hub 中的场景编辑器中,你可以通过选择某个实体来查看其所属的组件。




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

引擎是场景中处于中心位置并管理其他所有部分的组件。它决定哪些实体会被渲染,以及玩家如何与它们交互。它还会协调来自 系统 系统的哪些函数会在何时执行。

组件用于存储其所引用实体的数据。它们只能存储这些数据,不能自行修改这些数据。组件值的所有更改都由 系统完成。系统与组件和实体本身完全解耦。实体和组件并不关心 系统 是哪些系统在作用于它们。

实体和组件的语法

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

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

💡 提示:你不必一个个创建实体,可以一次性从一个 复合 文件中生成一整棵由实体和组件组成的树。

移除实体

要从引擎中移除一个实体,请使用 engine.removeEntity()。此函数返回一个 布尔值: true ,如果实体已被移除, false 如果移除被拒绝,则返回 false。

如果一个被移除的实体还有任何子实体,这些子实体的父级会改回默认的 engine.RootEntity 实体,它位于场景基准位置,缩放为 1.

由渲染器保留的实体

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

该 命名保留实体 (engine.RootEntity, engine.PlayerEntity, engine.CameraEntity)是一个特殊情况: engine.removeEntity() 仍然返回 false 这些对象(它们的 ID 永远不会释放),但它们的组件 会 被清除。这意味着你可以从中清除你自己的组件(例如,移除一个 InputModifier 从 engine.PlayerEntity),尽管实体 ID 本身永远不会被释放。

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

移除实体及其子实体

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

💡 提示:有时与其把实体从引擎中移除,不如将其设为不可见,这样如果你想再次加载它就不会有任何延迟。参见 设为不可见

在后台移除实体

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

嵌套实体

一个实体可以将其他实体作为子实体。借此,我们可以像网页的 HTML 一样,把实体组织成树结构。



要将一个实体设为另一个实体的父级,子实体必须拥有一个 Transform 组件。然后你可以设置 parent 字段,填入对父实体的引用。

一旦分配了父级,就可以从子实体的 parent 其上的 Transform 组件找到。

如果父实体拥有一个 Transform 会影响其位置、缩放或旋转的组件,它的子实体也会受到影响。位置和旋转值会相加,缩放值会相乘。

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

  • 对于 位置,父实体的中心是 0, 0, 0

  • 对于 旋转 ,父实体的旋转是四元数 0, 0, 0, 1 (等同于欧拉角 0, 0, 0)

  • 对于 scale,父实体被视为大小为 1。父实体的任何缩放都会按比例影响子实体的缩放和位置。

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

要将子实体与其父实体分离,你可以把该实体的父级设为 engine.RootEntity.

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



按 ID 获取实体

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

例如,如果玩家的点击或一个 raycast 命中一个实体,那么这里会返回被命中实体的 ID,你可以使用上面的命令来获取与该 ID 对应实体的 Transform 组件。你也可以用同样的方式获取该实体的其他任何组件。

按名称获取实体

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

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

所有通过场景编辑器 UI 添加的实体都有一个 Name 组件,你可以像这样遍历所有这些实体:

添加或替换组件

每个实体对于同一种组件只能拥有一个。例如,如果你尝试给一个已经拥有 Transform 的实体再分配一个 Transform,就会报错。

为避免此错误,你可以使用 .createOrReplace 而不是 .create。如果同类组件已存在,此命令会覆盖它们;否则就像 .create.

从实体访问组件

你可以通过实体的 .get() 或 getMutable() 函数。

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

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

参见 可变数据 了解更多细节。

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

如果你不完全确定该实体是否拥有你要获取的组件,请使用 getOrNull() 或 getMutableOrNull().

如果你要检索的组件在该实体中不存在:

  • get() 和 getMutable() 会返回错误。

  • getOrNull() 和 getMutableOrNull() 返回 空值.

从实体中移除一个组件

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

如果你尝试移除一个在实体中不存在的组件,这个操作不会引发任何错误。

检查组件是否存在

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

💡 提示:你也可以 查询组件 来获取拥有某个特定组件,或某组特定组件的完整组件列表。不要手动遍历场景中的所有实体,并逐个使用 has()进行检查,那种做法效率要低得多。

检查组件的变化

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

回调函数可以包含一个输入参数,其中保存组件的新状态。

如果该组件从实体中移除,那么函数会以 undefined.

💡 提示:该 .onChange() 函数既适用于 SDK 的原生组件,也适用于 自定义组件 (由创作者定义)。

获取子实体

要访问某个父实体的所有直接子实体,请使用 getEntitiesWithParent。它将 engine 和 parent 实体作为参数,并返回所有以该特定实体为父级的实体列表。请注意,它只返回直接子实体,不返回子实体的子实体。

如果想改为访问某个实体的所有后代,无论它们嵌套有多深,请使用函数 getComponentEntityTree()。它不会让你按层级逐级手动遍历,而是返回一个易于迭代的后代扁平列表。它还会进行过滤,只保留具有给定组件或组件列表的实体。

该 getComponentEntityTree 该函数接受三个参数:

  • engine:运行这些实体的引擎实例

  • 实体:作为起点的根实体

  • 组件:用于筛选的组件(通常 Transform 用于空间层级)

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

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

保留实体

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

  • engine.RootEntity

  • engine.PlayerEntity

  • engine.CameraEntity

根实体

场景中的所有实体都是 engine.RootEntity的子实体,直接或间接皆是。

这个实体缺少 Transform 组件,但它用于处理几个表示更全局设置的组件,例如 天空盒控制, 光标位置,或 屏幕尺寸.

玩家实体

该 engine.PlayerEntity 实体代表玩家的化身。

获取玩家的 Transform 组件,以获取玩家当前的位置和旋转,参见 用户数据。玩家的 Transform 仅可读,如需修改,请使用 movePlayerTo() 函数, 了解更多.

你也可以通过将对象设为此实体的子对象来把它们附加到玩家身上,不过 附加到玩家 通常是更好的选择。

摄像机实体

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

获取摄像机的 Transform 组件,以获取摄像机的位置和旋转。该实体的 Transform 也仅可读。如需修改摄像机角度或位置,请使用 虚拟摄像机.

你也可以获取摄像机的 CameraMode 组件,以了解玩家是否正在使用第一人称或第三人称摄像机模式,参见 摄像机模式.

最后更新于