> 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/ru-men/coding-scenes.md).

# 编码要点

这组内容将帮助你理解 Decentraland 客户端和 SDK 中的工作方式。

## 开发工具

从非常高的层面来看，Decentraland **软件开发工具包** （SDK）允许你执行以下操作：

* 生成一个默认的 *项目* 其中包含一个 Decentraland 场景，以及渲染和运行你的内容所需的所有资源。
* 在本地浏览器中构建、测试并预览你的场景内容——完全离线，无需进行任何以太坊交易，也无需拥有 LAND。
* 使用 Decentraland API 编写 TypeScript 代码，为场景添加交互式和动态行为。
* 将你的场景内容上传到内容服务器。
* 将你的 LAND 代币与已上传内容的 URL 关联起来。

我们的 SDK 包含以下内容：

* **创作者中心**：一个独立应用程序，除了其他功能外，还能让你通过简单的拖放界面创建场景。你可以运行预览、调试、编辑代码并发布。 [了解更多](/creator/content-creator-zh/chang-jing-bian-ji-qi/ru-men/about-editor.md)
* **Decentraland ECS**：一个包含辅助方法框架的 TypeScript 包，可让你创建交互式体验。可用它在场景中创建和操作对象，并促进玩家或其他应用之间的世界内交易。（ [最新 ECS 参考](https://github.com/decentraland/ecs-reference/blob/master/docs-latest/decentraland-ecs.md))
* **场景示例**：从以下内容中获取灵感和编码最佳实践： [场景示例](https://studios.decentraland.org/resources?sdk_version=SDK7).

其他旧版工具：

* **Web 编辑器**：一个基于网页的工具，用于创建简单场景并发布它们。

## 要求

要在本地开发场景，你不需要拥有 LAND 代币。开发和测试场景可以完全离线完成，无需将场景部署到以太坊网络（Decentraland 用来确立 LAND 以及 Decentraland 名称所有权的系统）或内容服务器。

你必须具备：

* **创作者中心**：一个独立应用程序，除了其他功能外，还能让你通过简单的拖放界面创建场景。你可以运行预览、调试、编辑代码并发布。 [了解更多](/creator/content-creator-zh/chang-jing-bian-ji-qi/ru-men/about-editor.md).

如果你计划编辑场景代码，还需要安装以下之一：

* <img src="https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-72733d889cecbb74400d2c9292b43a212b253969%2Fvscode.png?alt=media" alt="VS Code" data-size="line"> **Visual Studio Code**：下载它 [这里](https://code.visualstudio.com/)。它能帮助你更快地编写代码，并减少错误。源代码编辑器会标记语法错误，在你编写时自动补全，甚至会根据上下文显示智能建议。你还可以点击代码中的对象，查看其类的完整定义以及它支持哪些属性。
* <img src="https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-2c1650edc9dbc881f65cd788d7c8ccfa2c27575c%2Fcursor-icon.png?alt=media" alt="Cursor" data-size="line"> **Cursor AI**：下载它 [这里](https://www.cursor.com/)。一个集成了 AI 的强大代码编辑器。它让你可以选择不同的 AI 模型来帮助你编写代码，而且全部免费。AI 助手不仅会在你编写时自动补全，你还可以提示它重构大型代码库、编写文档等。

{% hint style="info" %}
**💡 提示**：你可以使用 Cursor、OpenDCL 或 Claude Code 等 AI 助手，仅凭自然语言描述构建完整场景——无需 TypeScript 经验。参见 [借助 AI 进行氛围编程](/creator/content-creator-zh/chang-jing-sdk7/ru-men/vibe-coding.md) 以开始使用。
{% endhint %}

## 支持的语言和语法

Decentraland 使用 [TypeScript (.ts)](https://www.typescriptlang.org/docs/handbook/jsx.html) 作为编写场景的默认语言。

TypeScript 是 JavaScript 的超集，所以如果你熟悉 JavaScript，会发现它们几乎一样，但 TypeScript 包含类型声明。得益于类型声明，可以拥有自动补全、更好的调试提示等功能，这些功能能加快开发速度，并有助于创建更稳固的代码库。这些功能都是良好开发体验的关键组成部分。

当场景构建时，你编写的 TypeScript 代码会被编译成压缩后的 JavaScript，使其更轻量。TypeScript 的原始源代码不会上传到服务器，只会上传编译后的 JavaScript 版本。

### 其他语言

你可以使用 TypeScript 之外的其他工具或语言，并将其编译为 JavaScript，只要你的编译脚本包含在一个单独的 JavaScript 文件中，并且该文件路径与场景 `main` 字段中设置的路径匹配，位于你的场景的 `scene.json` 文件（默认情况下是 *bin/index.js*）。所有提供的类型声明都是用 TypeScript 编写的，其他语言和转译器并未获得官方支持。

## 场景

你部署到 LAND 上的内容称为 **场景**。场景是一个渲染 3D 内容的交互式程序，它可以是游戏、交互体验、艺术画廊，随你想要什么都行！

场景会部署到 Decentraland 中的虚拟 LAND 上。LAND 是一种稀缺且不可替代的资产，由以太坊智能合约维护。部署到单个 **地块**，即一个 16 米 × 16 米 的 LAND 地块，或多个相邻地块。

当玩家访问 Decentraland 时，他们会在沿着地图行走的过程中下载并渲染每个场景的内容。当他们走远时，系统会卸载这些场景。

你也可以通过在 CLI 中运行预览，在自己的机器上本地运行场景。

## 实体与组件

Decentraland 中的三维场景基于一种 [实体-组件-系统](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system) 架构，场景中的一切都是一个 *实体*实体 *组件*，每个组件都会为其所属的实体赋予特定属性。一个门实体很可能至少拥有一个 Transform 组件（用于设置位置、旋转和缩放），以及另一个用于提供其形状的组件。组件只是存储数据的地方，本身不会执行任何操作。

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

```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',
	})
}
```

实体可以嵌套在其他实体内部，从而形成树状结构。如果你熟悉 Web 开发，你可能会觉得把实体看作 DOM 树中的元素、把组件看作这些元素的属性会很有帮助。

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

实体是一个抽象概念。实体其实只是一个 id，用作将不同组件分组的引用。

参见 [实体和组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/entities-components.md) 如需深入了解这两个概念以及它们在 Decentraland 场景中的用法，请参阅

### 自定义组件

默认的组件集合（例如 `Transform`, `GltfContainer`, `材质`等）会被引擎解释，并直接影响实体的外观、位置、是否发出声音等。

你还可以定义 *自定义组件* 用于存储可能对场景机制有用的数据。引擎不会知道这些组件上的值意味着什么，它们不会对场景如何渲染产生任何直接影响。不过，你可以在场景代码中编写逻辑来监视这些值并作出响应。例如，你可以定义一个自定义的 "doorState" 组件来跟踪门的开/关状态。在这种情况下，该组件不过是一个用于存储值、用来记录此状态的地方。要在场景中看到门开合，你还需要单独实现使用这些值来影响门的旋转的逻辑，而这个值来自引擎能够理解如何解释的 `Transform` 组件。

参见 [自定义组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/custom-components.md) 以获取更多信息。

### 按名称获取实体

在 Creator Hub 的 Scene Editor 中通过拖放添加的实体，也可以通过代码访问，以便进一步编辑并添加行为。

使用 `engine.getEntityOrNullByName()` 来获取一个实体，传入在 Scene Editor 界面中分配给该实体的名称。每个实体都应有唯一的名称。

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

然后你就可以对该实体做任何想做的事，比如添加新组件、修改已有组件、复制它或删除它。

参见 [按名称获取实体](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/entities-components.md#get-an-entity-by-name) 以获取更多信息。

如果该实体是一个 [智能物品](/creator/content-creator-zh/chang-jing-bian-ji-qi/jiao-hu-xing/smart-items.md)，你也可以调用它的 **操作** 或通过代码订阅其 **触发器** 。参见 [参考项](/creator/content-creator-zh/chang-jing-bian-ji-qi/shi-yong-dai-ma-kuo-zhan/reference-items.md).

## 系统

实体和组件是用于存储场景中对象信息的地方。 *系统* 保存会随时间改变组件中所存储信息的函数。

系统是我们实现游戏逻辑的地方，它们会执行需要在游戏循环的每个 tick 中定期更新或检查的动作。

系统是一个纯粹而简单的函数，它会按照 [*更新模式*](http://gameprogrammingpatterns.com/update-method.html).

```ts
// 基础系统
function mySystem() {
	console.log('我的系统正在运行')
}

engine.addSystem(mySystem)

// 带 dt 的系统
function mySystemDT(dt: number) {
	console.log('距离上一帧的时间：', dt)
}

engine.addSystem(mySystemDT)
```

单个场景可以同时运行 0 个或多个系统。系统可以在场景运行期间的不同时间被开启或关闭。通常，最好将彼此独立的行为放在单独的系统中。

参见 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md) 以了解系统在场景中的更多用法。

### 游戏循环

该 [游戏循环](http://gameprogrammingpatterns.com/game-loop.html) 是 Decentraland 场景代码的支柱。它会按固定时间间隔循环执行部分代码，并执行以下操作：

* 监听玩家输入
* 更新场景
* 重新渲染场景

在大多数传统软件程序中，所有事件都会由玩家操作直接触发。程序状态在玩家点击按钮、打开菜单等之前不会发生变化。

但交互式环境和游戏不同。场景中的并非所有变化都一定由玩家的操作引起。你的场景可能有会自行移动的动画对象，甚至有拥有自身 AI 的非玩家角色。某些玩家操作也可能需要多个 tick 才能完成，例如如果打开门需要整整一秒，那么门在移动过程中，其旋转必须逐步更新约 30 次。

我们把循环中的每一次迭代称为 *tick*。Decentraland 场景在可行时会以每秒 30 个 tick 的频率渲染。如果机器难以渲染每个 tick，更新频率可能会降低。

在每个 tick 中，场景都会更新；然后根据更新后的值重新渲染场景。

在 Decentraland 场景中，并没有显式声明的游戏循环，而是场景的 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md) 构成了游戏循环。

场景的编译和渲染是在后端完成的，在开发场景时你不需要处理这些。

## 查询组件

你可以 [查询组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/querying-components.md) 使用方法 `engine.getEntitiesWith(...components)` 以跟踪场景中具有某些组件的所有实体。

通常在一个 [系统](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/systems.md)中查询组件是有意义的，然后遍历返回的每个实体，并对每个实体执行同一组操作。

如果你尝试在游戏循环的每个 tick 中遍历场景中的所有实体，性能成本可能会很高。只引用查询返回的实体，就能确保你只处理相关的实体。

```ts
// 定义一个系统
function boxHeightSystem(dt: number) {
	// 查询同时包含 MeshRenderer 和 Transform 组件的实体
	for (const [entity] of engine.getEntitiesWith(MeshRenderer, Transform)) {
		const transform = Transform.get(entity)
		console.log('方块的高度：', transform.position.y)
	}
}

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

## 场景生命周期

如果你开始直接在 `index.ts`中编写零散的代码行，你的代码可能会缺少一些重要上下文。例如，你会缺少关于玩家实体的信息，或者关于通过拖放在 Creator Hub 中添加的实体的信息。在这些代码行被读取时，这些内容还没有加载。

为避免这种情况，始终建议使用 `main()` 函数（在 `index.ts` 文件中）作为入口点来编写场景的初始加载代码。只有在场景的所有初始上下文都已加载后，此函数才会运行，其中包括通过 Scene Editor 界面添加的任何内容。

你可以在 `main()` 函数之外编写代码，只要：

* 代码会被 `main()`
* 间接调用
* 代码定义了一个系统，或者向引擎添加了一个系统 [异步函数](/creator/content-creator-zh/chang-jing-sdk7/bian-cheng-mo-shi/async-functions.md)

{% hint style="warning" %}
**📔 注意**代码位于一个

[自定义组件](/creator/content-creator-zh/chang-jing-sdk7/jia-gou/custom-components.md) 定义是一个例外，这些定义必须始终写在 `main()` 函数之外，放在单独的文件中。它们需要在 `main()` 被执行之前被解释。
{% endhint %}

## 可变性

你可以选择使用组件的可变版本或不可变（只读）版本。组件中的 `.get()` 函数会返回该组件的不可变版本。你只能读取其值，但不能更改其上的任何属性。

该 `.getMutable()` 函数会返回该组件的可变表示，允许你修改其值。仅在计划对组件进行更改时才使用可变版本。使用组件的不可变版本会带来巨大的性能提升。

```ts
// 获取不可变版本（只读）
const immutableTransform = Transform.get(myEntity)

// 以下代码不起作用：
// 	immutableTransform.position.y = 2

const mutableTransform = Transform.getMutable(myEntity)

// 以下代码确实会改变实体的位置
mutableTransform.position.y = 2
```

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

## 将以上内容整合起来

该 *引擎* 位于 *实体*、以及 *组件* 一方面和 *系统* 另一方面之间。

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

场景中组件里存储的所有值都代表该时刻场景的状态。随着游戏循环的每个 tick，引擎会运行各个系统的函数来更新组件中存储的值。

在所有系统运行完毕后，每个实体上的组件都会有新的值。当引擎渲染场景时，它会使用这些新的更新值，玩家就会看到实体变化以匹配其新状态。

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

	// 通过 Transform 组件为实体赋予位置
	Transform.create(cube, {
		position: Vector3.create(5, 1, 5),
	})

	// 通过 MeshRenderer 组件为实体赋予一个可见形状
	MeshRenderer.setBox(cube)
}

// 定义一个系统
function rotationSystem(dt: number) {
	// 查询同时包含 MeshRenderer 和 Transform 组件的实体
	for (const [entity] of engine.getEntitiesWith(MeshRenderer, Transform)) {
		const transform = Transform.getMutable(entity)
		transform.rotation = Quaternion.multiply(
			transform.rotation,
			Quaternion.fromAngleAxis(dt * 10, Vector3.Up())
		)
	}
}

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

在上面的示例中，一个 `立方体` 实体以及一个 `rotationSystem` 系统被添加到引擎中。该 `立方体` 实体有一个 `Transform`，以及一个 `MeshRenderer` 组件。在游戏循环的每个 tick 中， `rotationSystem` 系统都会被调用，并更改 `Transform` 的 `立方体` 实体，从玩家摄像机位置向前追踪一条射线。

请注意，上面的代码大部分只会在加载场景时执行一次。例外是 `rotationSystem` 系统，它会在游戏循环的每个 tick 中被调用。

## 场景解耦

你的场景运行时所处的上下文与引擎（也就是主线程）并不相同。我们创建 SDK 的方式与渲染引擎完全解耦。我们这样设计是出于安全和性能两方面的考虑。

由于这种解耦，你的场景代码无法访问 DOM 或 `window` 对象，因此你无法访问玩家的浏览器或地理位置等数据。

这种解耦是通过 RPC 协议实现的，该协议将客户端的一小部分专门用于渲染场景和处理事件。

我们还对通信协议进行了抽象，这使我们能够在 WebWorker 中本地运行场景。

我们不希望开发者干预引擎内部，甚至不需要知道引擎内部是什么。我们需要确保玩家在整个 Decentraland 地图中获得一致的体验，而在那种“低”层级更容易出错。

这种解耦对于防止邻近场景在玩家处于别人的场景中时干扰其体验也很重要。玩家可能同时加载了多个附近场景，每个场景都在运行自己的代码。有些操作（如打开外部链接或移动玩家）只有在玩家站在该特定场景上时才被允许；如果场景已加载但玩家不在其上，则不允许。

## Tree Shaking

当将 TypeScript 源代码转换为压缩后的 JavaScript 代码时，该过程会执行 [tree shaking（树摇）](https://en.wikipedia.org/wiki/Tree_shaking) 以确保只有实际正在使用的代码部分会被转换。这有助于让场景的最终代码尽可能轻量。它在使用外部库时尤其有用，因为这些库通常包含许多未被使用的功能，否则会让场景变得臃肿。

作为 tree shaking 的结果，你希望场景运行的任何代码，都需要以某种方式被代码入口点引用： `main()` 函数 on `index.ts`。系统也可以选择在 `index.ts` 文件中添加，而无需引用 `main()`。任何没有被这些文件显式或间接引用的代码，都不会被纳入场景。

例如，假设你有一个名为 `extraContent.ts` 的文件，包含以下内容，那么实体不会被渲染，系统也不会开始运行：

```ts
// extraContent.ts

const myEntity = engine.addEntity()
Transform.create(myEntity, {
	position: { x: 8, y: 0, z: 8 },
})
MeshRenderer.setBox(myEntity)

function mySystem(dt: number) {
	console.log('系统正在运行')
}

engine.addSystem(mySystem)
```

要让它作为场景的一部分运行，你可以通过以下方式从 `index.ts` 引用它：

```ts
// 在 extraContent.ts 中

export function addEntities() {
	const myEntity = engine.addEntity()
	Transform.create(myEntity, {
		position: { x: 8, y: 0, z: 8 },
	})
	MeshRenderer.setBox(myEntity)
}

export function mySystem(dt: number) {
	console.log('系统正在运行')
}

/////////////////////////////

// 在 index.ts 中

import { addEntities, mySystem } from './extraContent'

export function main() {
	addEntities()
}

engine.addSystem(mySystem)
```

此规则的例外是自定义组件的定义。这些定义不能通过 `main()` 函数入口点访问，因为它们需要在其他所有内容之前被解释。

## 导入

场景中使用的所有函数、对象、组件和其他元素都必须导入到每个使用它们的文件中。这是 [tree-shaking](#tree-shaking)的结果，因为它避免了打包整个 SDK，而只包含场景使用的部分。

文档中的各个代码片段为了保持简洁，会省略每个文件开头的导入行，但要让它们正常工作，你必须将这些导入添加到场景中。

如果你使用 VS Studio Code 编写场景，智能自动补全选项应该会在你编写时自动处理导入，而无需你特意关注这一点。

不过，当你把代码片段粘贴到场景中时，可能会看到某些元素标红，因为它们没有导入到该文件中。要修复这一点：

* 点击每个带下划线的词
* 点击该行左侧的灯泡图标
* 选择 **添加导入自**
* 文件开头会出现一行导入语句。

![](https://2460066822-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FoPnXBby9S6MrsW83Y9qZ%2Fuploads%2Fgit-blob-7a9b5a49dd3ff062e5609e6205f2a41c55d9ea55%2Fimports.png?alt=media\&token=7d6f1e13-e683-422f-82ad-dffcf105f362)

如果需要导入很多不同内容，你也可以选择 **添加所有缺失的导入** ，从同一个下拉菜单中进行。

请注意，元素被使用到的每个文件都必须添加导入。

VS Studio Code 应该能够自动解析导入的正确路径。如果由于某种原因它在这方面遇到问题，一个技巧是在文件开头粘贴以下空导入语句。VS Studio 应该可以从那里继续处理。

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

## SDK 版本

在开发新的场景时，你默认使用 `@latest` 稳定版 SDK 发布版本。

如果你想利用或预览尚未进入最新稳定版的即将推出的功能，你可以安装 `@next` SDK 发布版本。

为此，请打开你场景的 `package.json` 文件，并更改以下行：

```json
  "devDependencies": {
    "@dcl/js-runtime": "next",
    "@dcl/sdk": "next"
  },
```

然后在你场景项目的文件夹上运行以下命令：

```
npm i
```

参见 [管理依赖](/creator/content-creator-zh/chang-jing-sdk7/ku/manage-dependencies.md) 了解更多详情。

{% hint style="warning" %}
**📔 注意**：请记住，@next 版本有时可能会出现问题。新功能的语法和名称在发布稳定版之前可能会发生变化。
{% endhint %}


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/creator/content-creator-zh/chang-jing-sdk7/ru-men/coding-scenes.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
