> 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/contributor/contributor-zh/chang-jing-yun-xing-shi/zu-jian/component-creation.md).

# 组件创建

要创建新组件，在开始在渲染器中编写它们之前，我们需要先做几件事。你需要遵循一个分步指南。我会列出所有步骤，然后你可以通过更详细的说明逐步执行。

1. 在以下位置创建 proto 定义： [@dcl/protocol](https://github.com/decentraland/protocol)
2. 在以下位置生成新的 proto TypeScript 代码： [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)
3. 为以下内容创建测试： `js-sdk-toolchain`
4. 在项目中生成新的 proto C# 代码
5. 编写新组件的代码
6. 确保该组件遵循约定

## 在以下位置创建 proto 定义： [@dcl/protocol](https://github.com/decentraland/protocol)

要创建定义，你必须前往这个仓库：<https://github.com/decentraland/protocol>

1. 在这个文件夹中创建 proto 定义：<https://github.com/decentraland/protocol/tree/main/ecs/components>
2. 创建一个包含这些新更改的 PR

> ***注意：*** 创建 PR 后，GitHub Bot 会评论并提供用于测试 PR 的包链接。你可以在后续步骤中使用该链接进行测试

需要考虑的事项

* 我们使用的是 proto 3，因此 proto 的所有定义都必须按照其 [语法](https://developers.google.com/protocol-buffers/docs/proto3/)
* 我们有一些不能被重新创建的公共类型
* proto 应该具备基本定义
* 你必须添加以下代码来枚举该组件

```
import "common/id.proto";
option (ecs_component_id) = 1100;
```

proto 示例

```
syntax = "proto3";

import "common/id.proto";
option (ecs_component_id) = 1020;

message PBAudioSource {
  optional bool playing = 1;
  optional float volume = 2; // default=1.0f
  optional bool loop = 3;
  optional float pitch = 4; // default=1.0f
  string audio_clip_url = 5;
}
```

## 在以下位置生成新的 proto TypeScript 代码： [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)

下载以下仓库：<https://github.com/decentraland/js-sdk-toolchain>

在其中，进入 `packages/dcl/ecs` (`https://github.com/decentraland/js-sdk-toolchain/tree/main/packages/%40dcl/ecs`)

然后，你可以执行

```
npm install @dcl/protocol@next
```

或者使用 GitHub Bot 在你的 @dcl/protocol PR 中生成的命令（这必须是临时的，用于测试 PR）。

然后在以下位置的根目录下运行这些命令： `js-sdk-toolchain` 项目：

```
make install
make build
```

并推送生成的代码。

## 在项目中生成新的 proto C# 代码

要生成 C# 代码，我们需要前往 `protocol-gen` 路径，在以下仓库的根目录中： `@decentraland/unity-renderer` 仓库。并执行以下命令：

```
npm install
npm run build-components
```

要升级到最新版本的 `@dcl/protocol` （main 分支），我们应该使用

```
npm install @dcl/protocol@next
npm run build-components
```

要测试 PR，我们可以使用 GitHub Bot 在以下位置生成的 URL： `@dcl/protocol` PR：示例：

```
npm install "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-3143233696.commit-45f1290.tgz"
npm run build-components
```

在合并 @dcl/protocol PR 之后，我们必须使用 `@dcl/protocol@next` 并生成代码。

## 编写新组件的代码

现在是实现新组件功能的时候了。

组件有五个基本部分。

* ComponentID 组件将使用的 ID。它必须是唯一的，并由 proto 定义生成
* Model 这是组件的模型。这是我们用于处理组件的数据。它是通过 proto 生成自动创建的，名称与 proto 文件相同，只是在前面加上 PB。例如，如果你有一个 `BoxShape.proto` 定义，那么生成的模型类将是 `PBBoxShape`
* Component Handler 组件处理器将管理组件的所有功能。在这个类中，你必须实现 `IECSComponentHandler<ModelClass>` （ModelClass 是模型。它是从 proto 生成的类，名称将是 PB + .proto 文件名）。这个接口有 3 个重要方法需要实现，以便创建一个组件

```
        void OnComponentCreated(IParcelScene scene, IDCLEntity entity);
        void OnComponentRemoved(IParcelScene scene, IDCLEntity entity);
        void OnComponentModelUpdated(IParcelScene scene, IDCLEntity entity, ModelType model);
```

* Serializer 每个组件都负责实现其序列化和反序列化。这个序列化器必须能够将其序列化/反序列化为字节数组
* Register 这将把组件注册到系统中，使其连接到系统。这个注册将把组件注册到工厂和组件写入器中

组件的设计是为了避免继承，因此我们鼓励尽可能使用纯函数

为了创建它们，我们必须遵循以下步骤

1. 创建组件文件夹和程序集。我们所有的组件都位于以下文件夹下 `DCLPlugins/ECS7/ECSComponents`。你需要创建一个文件夹和一个新的程序集来承载该组件
2. 在新的程序集里，你必须引用以下内容 `DCL.ECSComponents.Data`。这将引用你刚刚更新的组件的新模型
3. 你必须创建带有所有逻辑的组件处理器（请查看 `ECSBoxShapeComponentHandler.cs` 作为示例）
4. 你必须创建序列化器类（也许你可以从另一个类复制并适配到你的）
5. 你必须创建注册类

```sh
   public static class AudioSourceSerializer
    {
        public static byte[] Serialize(PBAudioSource model)
        {
            int size = model.CalculateSize();
            byte[] buffer = new byte[size];
            CodedOutputStream output = new CodedOutputStream(buffer);
            model.WriteTo(output);
            return buffer;
        }

        public static PBAudioSource Deserialize(object data)
        {
            return PBAudioSource.Parser.ParseFrom((byte[])data);
        }
    }
```

6. 将新的注册添加到 `ECS7ComponentsComposer` 类中，并使用其对应的 ID

```sh
   public class ECS7ComponentsComposer : IDisposable
    {
        private readonly TransformRegister transformRegister;
        private readonly SphereShapeRegister sphereShapeRegister;
        private readonly BoxShapeRegister boxShapeRegister;
        private readonly PlaneShapeRegister planeShapeRegister;
        private readonly CylinderShapeRegister cylinderShapeRegister;
        private readonly AudioStreamRegister audioStreamRegister;
        private readonly AudioSourceRegister audioSourceRegister;

        public ECS7ComponentsComposer(ECSComponentsFactory componentsFactory, IECSComponentWriter componentsWriter)
        {
            transformRegister = new TransformRegister(ComponentID.TRANSFORM, componentsFactory, componentsWriter);
            sphereShapeRegister = new SphereShapeRegister(ComponentID.SPHERE_SHAPE, componentsFactory, componentsWriter);
            boxShapeRegister = new BoxShapeRegister(ComponentID.BOX_SHAPE, componentsFactory, componentsWriter);
            planeShapeRegister = new PlaneShapeRegister(ComponentID.PLANE_SHAPE, componentsFactory, componentsWriter);
            cylinderShapeRegister = new CylinderShapeRegister(ComponentID.CYLINDER_SHAPE, componentsFactory, componentsWriter);
            audioStreamRegister = new AudioStreamRegister(ComponentID.AUDIO_STREAM, componentsFactory, componentsWriter);
            audioSourceRegister = new AudioSourceRegister(ComponentID.AUDIO_SOURCE, componentsFactory, componentsWriter);
        }

        public void Dispose()
        {
            transformRegister.Dispose();
            sphereShapeRegister.Dispose();
            boxShapeRegister.Dispose();
            planeShapeRegister.Dispose();
            cylinderShapeRegister.Dispose();
            audioStreamRegister.Dispose();
            audioSourceRegister.Dispose();
        }
    }
```

现在你的组件已经添加并可以工作了！

## 确保该组件遵循约定

在开发新组件时，有一些检查清单需要考虑，这部分试图对它们进行总结。

* 单元测试 所有组件都必须包含单元测试，至少覆盖其功能、释放以及序列化/反序列化，以确保组件能够正常工作
* 要考虑当组件不在场景中时会发生什么（查看 `SceneBoundariesChecker` 类以获取更多信息）
* 如果组件在世界中渲染某些内容，它必须将可渲染信息添加到数据存储中，这样我们就能把渲染器的信息添加到场景中，从而计入限制
* 如果组件在世界中渲染某些内容，它必须添加 `MeshesInfo` 到实体中
* 它必须尽可能高效。此代码会被执行很多次，因此我们需要确保一切尽可能流畅地运行
* 它必须与 `热重载` 在预览模式下配合工作。如果你已经正确编写了 `OnComponentRemoved` ，这将开箱即用，但热重载是一种测试一切是否能与组件释放良好配合的方法
* 如果组件使用了资源，你必须使用一个 `AssetPromiseKeeper`来实现资源管理。组件应在资源被使用时以及不再使用时通知 `AssetPromiseKeeper` 它


---

# 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/contributor/contributor-zh/chang-jing-yun-xing-shi/zu-jian/component-creation.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.
