> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/docs-zh/jiao-ben-bian-xie/entities-and-components.md).

# 实体与组件

All Out 游戏围绕实体（世界中的事物）和组件（附加到实体上的行为/数据）构建。

如果你以前用过 Unity：可以把它理解为“GameObject + Components”。如果你以前用过 Roblox：可以把它理解为“Instance + Scripts/Components”。核心思想是一样的： **你通过给实体添加组件来组合玩法。**

## 实体

实体通常有两种存在方式：

* **在编辑器中放置**：它们在启动时就已经在场景中了。
* **在运行时创建**：你通过脚本生成它们。

### 创建和销毁实体

```go
entity := Scene.create_entity();
entity.set_local_position({10, 20});
entity.set_local_scale({2.5, 2.5});
entity.set_local_rotation(0);

// 结束后：
entity.destroy();
```

{% hint style="warning" %}
销毁实体时也会销毁它的组件。在销毁实体后，不要再持有这些组件的引用。
{% endhint %}

### 遍历实体

```go
for e: entity_iterator() {
    // ...
}
```

## 组件

组件是行为的基本单元。组件“附着”在某个实体上，并且可以读取/修改该实体。

### 获取和添加组件

```go
sprite := entity.get_component(Sprite_Renderer);

player := entity.get_component(Player);

my_comp := entity.add_component(My_Component);
```

### 遍历组件

```go
for enemy: component_iterator(Enemy) {
    enemy.tick_ai();
}
```

## 你会经常用到的内置组件

### `Sprite_Renderer`

```go
texture := get_asset(Texture_Asset, "ui/button.png");

sprite := entity.get_component(Sprite_Renderer);
sprite.set_texture(texture);
sprite.color = {1, 1, 1, 1}; // RGBA
sprite.depth_offset = 0.5;
sprite.layer = 10;

// 可选：使用自定义材质。如果渲染器应该拥有它，请传入 true。
// sprite.set_material(material, true);
```

### 预制体（`Prefab_Asset`)

预制体必须在编辑器中创建。在脚本中，你可以实例化它们。

```go
prefab := get_asset(Prefab_Asset, "MyPrefab.prefab");
spawned := Scene.instantiate(prefab);
spawned.set_local_position({1, 3});
```

### Spine（`Spine_Animator`)

如果你在使用 2D 动画角色，你通常会经常接触 `Spine_Animator`。参见 [Spine](/all-out-docs/docs-zh/he-xin-yin-qing-gai-nian/spine.md).

## 编写自定义组件

在专用文件中创建新组件（例如 `orbiter.csl`），并以以下方式之一将它们附加到实体：

* **在编辑器中手动添加**，或
* **在运行时** 使用 `entity.add_component(...)`

组件可以实现生命周期回调：

* `ao_start()`
* `ao_update(dt)`
* `ao_late_update(dt)` （在所有更新之后）
* `ao_draw(dt)` （仅用于视觉效果；可渲染帧）
* `ao_end()` （被销毁时）

`ao_draw` 会跳过重模拟。玩法和交互式 UI 必须使用 `ao_update`/`ao_late_update`，而不是 `ao_draw`.

示例：

```go
Orbiter :: class : Component {
    center: v2;
    radius: float;
    speed: float;
    angle: float;

    ao_start :: method() {
        center = entity.local_position;
        radius = 2;
        speed = 1;
        angle = 0;
    }

    ao_update :: method(dt: float) {
        angle += speed * dt;

        offset_x := cos(angle) * radius;
        offset_y := sin(angle) * radius;

        entity.set_local_position({center.x + offset_x, center.y + offset_y});
    }
}
```

{% hint style="info" %}
全局脚本的生命周期方法（`ao_start`, `ao_update`，等等）在 [游戏/帧生命周期](/all-out-docs/docs-zh/jiao-ben-bian-xie/game-frame-lifecycle.md).
{% endhint %}

## 序列化字段（`@ao_serialize`)

使用 `@ao_serialize` 用于在检查器中公开一个字段，并将其包含到场景和 JSON 序列化中。

```go
Chest :: class : Component {
    capacity: int @ao_serialize;
}
```

## 触发器和邻近查询

触发碰撞体会提供 `on_trigger_start`, `on_trigger_stay`，和 `on_trigger_end` 回调。参见 [导航网格和碰撞](/all-out-docs/docs-zh/he-xin-yin-qing-gai-nian/navmesh-and-collision.md) 以了解设置和回调签名。

当你需要获取半径内的所有组件，或者只需要最近的那个时，请使用邻近查询。

有用的辅助函数：

```go
Scene.get_all_components_in_range     :: proc(position: v2, range: float, results: ref [..]$T)
Scene.get_closest_component_in_range  :: proc(position: v2, range: float, $T: typeid) -> (T, bool)
```

示例：

```go
nearby: [..]Enemy;
Scene.get_all_components_in_range(player.entity.world_position, 5.0, ref nearby);

for e: nearby {
    // ...
}

closest_pickup, found := Scene.get_closest_component_in_range(player.entity.world_position, 2.0, Pickup);
if found {
    // ...
}
```

## 最佳实践

* **每个玩家的状态应该放在 `Player`.** 上。避免使用会在有多个玩家时出问题的全局变量。
* **玩法行为优先使用组件。** 这样你会得到可复用的模块，并且可以把它们附加到不同的实体上。
* **从 `Player.ao_late_update`.** 中绘制玩家 UI。 `is_local_or_server()`.
* **使用 `is_local()` 仅用于玩家专属的视觉覆盖。** 不要在其中更改玩法或 UI 状态。
