Component

组件为实体提供数据和属性

  • 以带有 IComponent 标记接口的用户自定义结构体表示
  • 使用 struct 纯粹出于性能考虑(SoA 存储)
  • 支持生命周期钩子:OnAddOnDeleteCopyToWriteRead
  • 可以在不删除数据的情况下启用/禁用

示例:

public struct Position : IComponent {
    public Vector3 Value;
}

public struct Velocity : IComponent {
    public float Value;
}

public struct Name : IComponent {
    public string Val;
}

需要在世界创建和初始化之间进行注册

W.Create(WorldConfig.Default());
//...
// 不带配置的简单注册(适用于大多数情况)
W.Types()
    .Component<Position>()
    .Component<Velocity>()
    .Component<Name>();

// 带配置的注册
W.Types()
    .Component<Scale>(new ComponentTypeConfig<Scale>(
        guid: new Guid("..."),                           // 序列化的稳定标识符
        version: 1,                                      // 数据模式版本,用于迁移(默认 — 0)
        noDataLifecycle: false,                          // 禁用框架数据管理(默认 — false)
        readWriteStrategy: null,                         // 二进制序列化策略(默认 — StructPackArrayStrategy<T>)
        defaultValue: new Scale { Value = Vector3.One }, // 初始化和删除时的默认值(默认 — default(T))
        trackAdded: true,                                // 启用添加追踪(默认 — false),参见变更追踪
        trackDeleted: true                               // 启用删除追踪(默认 — false),参见变更追踪
    ));
//...
W.Initialize();

noDataLifecycle 参数控制框架是否管理组件数据生命周期。默认情况下(noDataLifecycle: false),框架会用 defaultValue 预初始化新存储,并在删除时将数据重置为 defaultValue — 因此 entity.Add<T>() 返回配置的默认值。设置 noDataLifecycle: true 时不执行初始化或清理 — 对于高频 unmanaged 类型很有用。如果定义了 OnDelete,无论此标志如何,钩子都负责清理。

无需手动传递配置,您可以直接在组件结构体内声明一个 ComponentTypeConfig<T> 类型的静态字段或属性。RegisterAll() 会按类型自动发现它(优先选择名为 Config 的成员):

public struct Health : IComponent {
    public float Value;
    public static readonly ComponentTypeConfig<Health> Config = new(
        defaultValue: new Health { Value = 100f }
    );
}
// RegisterAll() 会自动使用 Health.Config

创建带组件的实体:

// 创建空实体(无组件和标签)
W.Entity entity = W.NewEntity<Default>();

// 创建指定类型和集群的实体
W.Entity entity = W.NewEntity<Default>(clusterId: 0);

// 创建带组件的实体 — Set 返回 Entity(1 到 8 个组件的重载)
W.Entity entity = W.NewEntity<Default>().Set(new Position { Value = Vector3.One });
W.Entity entity = W.NewEntity<Default>().Set(
    new Position { Value = Vector3.One },
    new Velocity { Value = 1f }
);

添加组件:

// 不带值的 Add:如果组件已存在 → 返回现有数据的 ref,钩子不会被调用
// 如果不存在 → 用默认值初始化,调用 OnAdd
ref var position = ref entity.Add<Position>();

// 带 isNew 标志:isNew=true 表示组件是首次添加的
ref var position = ref entity.Add<Position>(out bool isNew);

// 一次添加多个组件(2 到 5 个的重载)
entity.Add<Position, Velocity>();

// 带值的 Set:始终覆盖数据
// 如果组件已存在 → OnDelete(旧值) → 替换 → OnAdd(新值)
// 如果不存在 → 设置值 → OnAdd
ref var position = ref entity.Set(new Position { Value = Vector3.One });

// 带值设置多个组件(2 到 12 个的重载)
entity.Set(new Position { Value = Vector3.One }, new Velocity { Value = 1f });

不带值的 Add<T>() 和带值的 Set<T>(T value) 具有不同的钩子语义。 不带值:如果组件已存在,钩子不会被调用,返回当前数据的 ref。 带值:数据始终被覆盖,执行完整的 OnDelete → 替换 → OnAdd 周期。


数据访问:

// 获取组件的可变 ref 引用(读写)
// 不标记为 Changed — 使用 Mut<T>() 进行追踪访问
ref var velocity = ref entity.Ref<Velocity>();
velocity.Value += 10f;

// 获取组件的只读 ref 引用 — 不标记为 Changed
ref readonly var pos = ref entity.Read<Position>();
var x = pos.Value.x; // 读取 OK,无 Changed 标记

// 获取带追踪的可变 ref 引用 — 当 trackChanged 启用时标记为 Changed
ref var pos = ref entity.Mut<Position>();
pos.Value += delta; // 数据已修改且标记为 Changed

Ref<T>() 不标记 Changed。需要变更追踪以配合 AllChanged<T> 过滤器时,请使用 Mut<T>()。在查询委托(For)中,ref 参数自动使用 Mut 语义。


基本操作:

// 获取实体上的组件数量
int count = entity.ComponentsCount();

// 检查组件是否存在(1 到 3 个的重载 — 检查是否全部存在)
// 无论 enabled/disabled 状态都会检查
bool has = entity.Has<Position>();
bool hasBoth = entity.Has<Position, Velocity>();
bool hasAll = entity.Has<Position, Velocity, Name>();

// 检查是否至少存在一个指定的组件(2 到 3 个的重载)
bool hasAny = entity.HasAny<Position, Velocity>();
bool hasAny3 = entity.HasAny<Position, Velocity, Name>();

// 删除组件(1 到 5 个的重载)
// 如果组件存在则调用 OnDelete;返回 true 表示已删除,false 表示不存在
bool deleted = entity.Delete<Position>();
entity.Delete<Position, Velocity>();
entity.Delete<Position, Velocity, Name>();

启用/禁用:

// 禁用组件 — 数据保留,但实体从标准查询中排除
// 返回 ToggleResult 枚举:MissingComponent、Unchanged、Changed
ToggleResult disabled = entity.Disable<Position>();
entity.Disable<Position, Velocity>();
entity.Disable<Position, Velocity, Name>();

// 重新启用组件
// 返回 ToggleResult 枚举:MissingComponent、Unchanged、Changed
ToggleResult enabled = entity.Enable<Position>();
entity.Enable<Position, Velocity>();
entity.Enable<Position, Velocity, Name>();

// 检查所有指定的组件是否已启用(1 到 3 个的重载)
bool posEnabled = entity.HasEnabled<Position>();
bool bothEnabled = entity.HasEnabled<Position, Velocity>();

// 检查是否至少有一个已启用(2 到 3 个的重载)
bool anyEnabled = entity.HasEnabledAny<Position, Velocity>();

// 检查所有指定的组件是否已禁用(1 到 3 个的重载)
bool posDisabled = entity.HasDisabled<Position>();
bool bothDisabled = entity.HasDisabled<Position, Velocity>();

// 检查是否至少有一个已禁用(2 到 3 个的重载)
bool anyDisabled = entity.HasDisabledAny<Position, Velocity>();

禁用的组件不会出现在标准查询过滤器(AllNoneAny)中,但数据保留在内存中。使用 WithDisabled/OnlyDisabled 过滤器变体来处理禁用的组件。


复制和移动:

var source = W.NewEntity<Default>().Set(new Position(), new Velocity());
var target = W.NewEntity<Default>();

// 将指定组件复制到另一个实体(1 到 5 个的重载)
// 源实体保留其组件
// 如果 CopyTo 钩子已重写 — 使用自定义复制逻辑
// 如果 CopyTo 钩子未重写 — 通过 Add 进行按位复制 + 保留 disabled 状态
// 对于单个组件,如果源实体拥有该组件则返回 true
bool copied = source.CopyTo<Position>(target);
source.CopyTo<Position, Velocity>(target);

// 将指定组件移动到另一个实体(1 到 5 个的重载)
// 先 Copy 到目标实体,再从源实体 Delete(在源实体上触发 OnDelete)
bool moved = source.MoveTo<Position>(target);
source.MoveTo<Position, Velocity>(target);

查询过滤器:

组件过滤器请参阅查询 — 组件章节。


生命周期钩子:

IComponent 接口提供了带有空默认实现的钩子 — 只需重写你需要的。

不要留下空的钩子实现。如果不需要钩子 — 不要实现它。未实现的钩子不会被调用,不会产生开销。

public struct Cooldown : IComponent {
    public float Duration;
    public float Elapsed;

    // 在组件添加后或通过 Set(value) 覆盖值后调用
    public void OnAdd<TWorld>(World<TWorld>.Entity self) where TWorld : struct, IWorldType {
        Elapsed = 0f; // 每次应用时重置计时器
    }

    // 在组件被删除(Delete)前、被覆盖(带值的 Set)前调用,
    // 以及在实体销毁时为每个组件调用
    //
    // 参数 `reason` 指示删除原因:
    // HookReason.Default      — 显式删除或实体销毁
    // HookReason.UnloadEntity — 实体/区块卸载
    // HookReason.WorldDestroy — 世界重置/销毁
    public void OnDelete<TWorld>(World<TWorld>.Entity self, HookReason reason) where TWorld : struct, IWorldType { }

    // CopyTo / MoveTo / Clone 的自定义复制逻辑
    // 如果未重写 — 按位复制 + 保留 disabled 状态
    // 如果已重写 — 完全替代默认复制逻辑
    public void CopyTo<TWorld>(World<TWorld>.Entity self, World<TWorld>.Entity other, bool disabled)
        where TWorld : struct, IWorldType {
        ref var copy = ref other.Add<Cooldown>();
        copy.Duration = Duration;
        copy.Elapsed = 0f; // 克隆从零开始
    }

    // 序列化 — 将组件写入二进制流
    // EntitiesSnapshot 必须实现(所有类型),non-unmanaged 类型在任何快照中也必须实现
    public void Write<TWorld>(ref BinaryPackWriter writer, World<TWorld>.Entity self)
        where TWorld : struct, IWorldType {
        writer.WriteFloat(Duration);
        writer.WriteFloat(Elapsed);
    }

    // 反序列化 — 从二进制流读取组件
    // version 参数支持在不同数据版本之间进行迁移
    public void Read<TWorld>(ref BinaryPackReader reader, World<TWorld>.Entity self, byte version, bool disabled)
        where TWorld : struct, IWorldType {
        Duration = reader.ReadFloat();
        Elapsed = reader.ReadFloat();
    }
}

Set(value) 对已存在组件的钩子调用顺序:OnDelete(旧值) → 数据替换 → OnAdd(新值)。对于 Delete 或实体销毁,只调用 OnDelete


调试:

// 将实体的所有组件收集到列表中(用于检查器/调试)
// 列表会在填充前被清除
var components = new List<IComponent>();
entity.GetAllComponents(components);

This site uses Just the Docs, a documentation theme for Jekyll.