> 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
```

或者使用你在 @dcl/protocol PR 中由 GitHub Bot 生成的命令（这必须是临时的，用于测试 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 在 `@dcl/protocol` PR 中生成的 URL：示例：

```
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();
        }
    }
```

现在你已经添加并让组件正常工作了！

## 确保组件遵循约定

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

* 单元测试 所有组件都必须包含单元测试，覆盖其功能、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 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/contributor/contributor-zh/chang-jing-yun-xing-shi/zu-jian/component-creation.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.
