> 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 场景，包括渲染和运行你的内容所需的所有资源。
* 在本地 Web 浏览器中构建、测试并预览你场景的内容——完全离线，无需进行任何以太坊交易或拥有 LAND。
* 使用 Decentraland API 编写 TypeScript 代码，为场景添加交互性和动态行为。
* 将你场景的内容上传到内容服务器。
* 将你的 LAND 代币链接到你已上传内容的 URL。

我们的 SDK 包含以下内容：

* **创作者中心**：一个独立应用，除其他功能外，它还让你可以通过简单的拖放界面创建场景。你可以运行预览、调试、编辑代码并发布。 [阅读更多](/creator/content-creator-zh/chang-jing-bian-ji-qi/kai-shi-shi-yong/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 编辑器**：一个基于 Web 的工具，用于创建简单场景并发布它们。

## 要求

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

你必须具备：

* **创作者中心**：一个独立应用，除其他功能外，它还让你可以通过简单的拖放界面创建场景。你可以运行预览、调试、编辑代码并发布。 [阅读更多](/creator/content-creator-zh/chang-jing-bian-ji-qi/kai-shi-shi-yong/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 进行 Vibe Coding](/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) 架构，其中场景中的一切都是一个 *实体*实体 *组件*多个组件

![](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 地图中的玩家获得一致的体验，而在那个“低”层面更容易出错。

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

## 摇树优化

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

作为摇树优化的结果，你希望场景运行的任何代码都需要以某种方式被你代码的入口点引用： `main()` 函数在 `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)，因为它避免打包整个 SDK，而只包含场景使用的部分。

文档中的各处代码片段都会省略每个文件开头的 import 行，以保持整洁，但要让它们工作，你必须把它们添加到场景中。

在使用 VS Studio Code 编写场景时，智能自动补全选项应当会在你编写时为你处理 imports，而无需你了解这一点。

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

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

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

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

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

VS Studio Code 应该能够自行解析到你导入项的正确路径。如果由于某种原因它在这方面有困难，一个技巧是在文件开头粘贴以下空的 import 语句。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 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/ru-men/coding-scenes.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.
