mcpField
U
unity-ecs-dots

unity-ecs-dots

github.com/nice-wolf-studio开发4068 次安装6 周前更新
代码仓库

关于 unity-ecs-dots

面向 Unity 6.3 LTS 开发者的 ECS/DOTS 技能资料,涵盖实体、组件、系统、作业与 Burst 编译。重点提供烘焙、并行处理和常见架构问题的实践指导。

主要功能

  1. 01ECS 核心概念与组件类型
  2. 02ISystem 与 SystemAPI 用法
  3. 03烘焙和 SubScene 工作流
  4. 04作业调度与并行处理
  5. 05Burst 编译及限制
  6. 06实体命令缓冲区模式

适用场景

  1. 01将 GameObject 逻辑改写为 ECS 系统
  2. 02在 SubScene 中配置实体烘焙
  3. 03通过 Jobs 并行处理实体
  4. 04排查结构变更与 Burst 限制

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-ecs-dots

安装后在 Claude Code 中输入/unity-ecs-dots确认已加载。

附带文件references/ecs-api.mdreferences/jobs-burst.md

详细介绍

Unity ECS / DOTS(面向数据的技术栈)

基于 Unity 6.3 LTS(6000.3)——Entities 1.3、Burst 1.8、Jobs 包

核心概念

ECS(实体组件系统)是 Unity 的面向数据框架。它用一种缓存友好、高性能的架构取代了 GameObject/MonoBehaviour 模型。

ECS 与 MonoBehaviour 对比
方面MonoBehaviourECS
标识GameObject(重量级、受托管)Entity(轻量级 int ID)
数据组件上的类字段非托管结构体(IComponentData)
逻辑MonoBehaviour 上的方法System(ISystem / SystemBase)
内存布局分散在堆上按 Archetype 存储在连续的块中
线程处理仅限主线程使用 Jobs + Burst 并行处理
实体

实体是轻量级标识符(一个索引 + 版本整数)。它们没有行为,也没有数据——只是用于关联组件的句柄。

组件(IComponentData)

组件是没有方法的非托管结构体,只保存数据。

using Unity.Entities;

public struct Speed : IComponentData
{
    public float Value;
}

public struct Health : IComponentData
{
    public float Current;
    public float Max;
}
Archetype 和 Chunk

Archetype 是组件类型的一种唯一组合。拥有相同组件集合的所有实体共享一个 Archetype。实体按 Archetype 分组,存储在 Chunk(16 KB 块)中,从而实现高效利用缓存的迭代。

World 和 EntityManager

World 包含一个 EntityManager 和一组系统。默认 World 会自动创建。EntityManager 是用于创建/销毁实体以及添加/移除组件的主要 API。

Baking 和 SubScene 工作流

Baking 会将创作用 GameObject 转换为运行时实体。这是一种单向转换,会在构建时或在 Editor 中加载 SubScene 时执行。

Baker
using Unity.Entities;

// Authoring component (MonoBehaviour on GameObject)
public class SpeedAuthoring : MonoBehaviour
{
    public float speed;
}

// Baker converts authoring data to ECS components
public class SpeedBaker : Baker<SpeedAuthoring>
{
    public override void Bake(SpeedAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.Dynamic);
        AddComponent(entity, new Speed { Value = authoring.speed });
    }
}
SubScene
  • SubScene 包含会在构建时烘焙为实体的 GameObject
  • 在运行时,实体数据会高效地流入(没有 GameObject 的开销)
  • 在 Editor 中,可以打开 SubScene 进行编辑(显示 GameObject),也可以关闭(显示烘焙后的实体)
  • 始终将由 ECS 管理的对象放在 SubScene 中
TransformUsageFlags
标志用途
Dynamic实体会在运行时移动(获得 LocalTransform、LocalToWorld)
Renderable实体会被渲染,但不会由代码移动
WorldSpace实体仅使用世界空间变换
None不添加变换组件

系统

系统包含所有逻辑。它们会遍历与组件查询匹配的实体。

ISystem(推荐)

ISystem 是现代的非托管系统类型。它兼容 Burst,相较于 SystemBase 应优先使用。

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

[BurstCompile]
public partial struct MovementSystem : ISystem
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<Speed>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        float deltaTime = SystemAPI.Time.DeltaTime;

        foreach (var (transform, speed) in
            SystemAPI.Query<RefRW<LocalTransform>, RefRO<Speed>>())
        {
            transform.ValueRW.Position +=
                new float3(0, 0, speed.ValueRO.Value * deltaTime);
        }
    }

    [BurstCompile]
    public void OnDestroy(ref SystemState state) { }
}

要点:

  • 必须是 partial struct
  • 在结构体和每个方法上使用 [BurstCompile]
  • OnCreate、OnUpdate、OnDestroy 接收 ref SystemState
  • state.RequireForUpdate<T>() —— 仅当 T 存在时系统才会运行
  • SystemAPI.Query<T>() —— 类型安全的 foreach 迭代
SystemBase(受托管,旧版)

SystemBase 是基于类的受托管系统。它支持受托管代码,但无法在系统级别进行 Burst 编译。所有新代码都应使用 ISystem。SystemBase 使用 Entities.ForEach() lambda 语法,而不是 SystemAPI.Query。

SystemGroup 和更新顺序

系统会按定义的顺序组织到组中并进行更新:

InitializationSystemGroup
  -> BeginInitializationEntityCommandBufferSystem
SimulationSystemGroup (default group)
  -> BeginSimulationEntityCommandBufferSystem
  -> [Your systems go here by default]
  -> EndSimulationEntityCommandBufferSystem
PresentationSystemGroup
  -> BeginPresentationEntityCommandBufferSystem

使用属性控制顺序:

[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(OtherSystem))]
[UpdateAfter(typeof(AnotherSystem))]
public partial struct MySystem : ISystem { }
SystemAPI

SystemAPI 提供用于安全、兼容 Burst 的访问方式的静态方法:

方法用途
SystemAPI.Query<T1, T2>()遍历匹配的实体
SystemAPI.Time访问 TimeData(DeltaTime、ElapsedTime)
SystemAPI.GetSingleton<T>()获取单例组件值
SystemAPI.SetSingleton<T>(value)设置单例组件值
SystemAPI.GetComponent<T>(entity)读取实体上的组件
SystemAPI.SetComponent<T>(entity, value)写入实体上的组件
SystemAPI.HasComponent<T>(entity)检查实体是否具有组件
SystemAPI.GetComponentLookup<T>()获取随机访问查找器
SystemAPI.GetBuffer<T>(entity)获取 DynamicBuffer
SystemAPI.GetAspect<T>(entity)获取实体的 aspect

深入了解组件

IComponentData(非托管结构体)

基础组件类型。必须是非托管结构体(不含引用类型或托管数组)。

ISharedComponentData

在实体之间共享——具有相同共享组件值的实体会被归入同一个 chunk。更改共享组件值属于结构性更改。

public struct TeamId : ISharedComponentData
{
    public int Value;
}
IBufferElementData(DynamicBuffer)

附加到实体的可变长度数组。

[InternalBufferCapacity(8)]
public struct DamageEvent : IBufferElementData
{
    public float Value;
    public Entity Source;
}

// Usage in a system
foreach (var (buffer, entity) in SystemAPI.Query<DynamicBuffer<DamageEvent>>()
    .WithEntityAccess())
{
    for (int i = 0; i < buffer.Length; i++)
        totalDamage += buffer[i].Value;
    buffer.Clear();
}
ICleanupComponentData

在实体销毁后仍会保留。用于清理逻辑(例如,释放原生资源)。移除所有清理组件后,实体才会被完全销毁。

IEnableableComponent

可在不进行结构性更改(不移动 chunk)的情况下启用或禁用的组件。

public struct Stunned : IComponentData, IEnableableComponent { }

// Toggle in system
SystemAPI.SetComponentEnabled<Stunned>(entity, true);
bool isStunned = SystemAPI.IsComponentEnabled<Stunned>(entity);
标记组件

纯粹用于筛选查询的零大小结构体。不含字段——每个实体的内存开销为零。

public struct IsPlayer : IComponentData { }

常见模式

创建和销毁实体
// Direct creation (structural change -- main thread only)
Entity e = entityManager.CreateEntity(typeof(Speed), typeof(Health));

// Preferred: use EntityCommandBuffer for deferred structural changes
var ecb = new EntityCommandBuffer(Allocator.TempJob);
Entity e2 = ecb.CreateEntity();
ecb.AddComponent(e2, new Speed { Value = 5f });
ecb.Playback(entityManager);
ecb.Dispose();
EntityCommandBuffer(ECB)

结构性更改(创建/销毁实体、添加/移除组件)不能在迭代过程中进行。使用 ECB 延迟执行这些操作。有关完整的 ECB API,请参阅 references/ecs-api.md。

系统管理的 ECB(推荐)
[BurstCompile]
public partial struct SpawnSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var ecbSingleton = SystemAPI.GetSingleton<
            EndSimulationEntityCommandBufferSystem.Singleton>();
        var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged);
        // ecb is played back automatically -- no Playback/Dispose needed
        foreach (var spawner in SystemAPI.Query<RefRW<Spawner>>())
        {
            if (spawner.ValueRO.Timer <= 0)
            {
                ecb.Instantiate(spawner.ValueRO.Prefab);
                spawner.ValueRW.Timer = spawner.ValueRO.Interval;
            }
        }
    }
}
单例组件

当某个组件恰好由一个实体持有时使用。通过 SystemAPI.GetSingleton<T>() / SystemAPI.SetSingleton<T>(value) / SystemAPI.GetSingletonRW<T>() 访问。适用于全局配置、游戏状态及类似的唯一数据。

Aspects(IAspect)

Aspects 将相关组件组合到一个访问包装器中,并可包含可选方法。

public readonly partial struct CharacterAspect : IAspect
{
    public readonly RefRW<LocalTransform> Transform;
    public readonly RefRO<Speed> Speed;
    public readonly RefRW<Health> Health;

    public void Move(float3 direction, float deltaTime)
    {
        Transform.ValueRW.Position +=
            direction * Speed.ValueRO.Value * deltaTime;
    }
}

// Use in system
foreach (var character in SystemAPI.Query<CharacterAspect>())
{
    character.Move(new float3(1, 0, 0), deltaTime);
}

作业系统

IJobEntity(推荐)

根据 Execute 参数类型自动生成 EntityQuery。

[BurstCompile]
public partial struct MoveJob : IJobEntity
{
    public float DeltaTime;

    void Execute(ref LocalTransform transform, in Speed speed)
    {
        transform.Position += new float3(0, 0, speed.Value * DeltaTime);
    }
}

// Schedule from system
[BurstCompile]
public partial struct MoveSystem : ISystem
{
    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        new MoveJob
        {
            DeltaTime = SystemAPI.Time.DeltaTime
        }.ScheduleParallel();
    }
}
IJobChunk(底层)

手动迭代 chunk,以获得最大控制力。完整示例请参阅 references/jobs-burst.md。

作业调度
方法行为
Run()在主线程上执行(不调度作业)
Schedule()在工作线程上执行单线程作业
ScheduleParallel()跨 chunk 执行多线程作业
并行作业中的 ECB
[BurstCompile]
public partial struct DestroyDeadJob : IJobEntity
{
    public EntityCommandBuffer.ParallelWriter ECB;

    void Execute([ChunkIndexInQuery] int sortKey, in Health health, Entity entity)
    {
        if (health.Current <= 0)
            ECB.DestroyEntity(sortKey, entity);
    }
}

Burst 编译器

Burst 编译器使用 LLVM 将 IL/.NET 字节码转换为高度优化的原生代码。

用法

在 ISystem 结构体(及其每个方法)和 IJobEntity 结构体上添加 [BurstCompile]。Burst 会通过 LLVM 将它们编译为优化的原生代码。

限制
  • 不支持托管类型(不支持 class、string、List<T>、托管数组)
  • 不支持分配内存(不支持对引用类型使用 new)
  • 不支持 try/catch/finally
  • 不支持虚方法或接口(作业接口除外)
  • 不支持静态可变字段(请改用 SharedStatic<T>)
  • 集合必须使用 NativeContainer 类型
Unity.Mathematics

经过 Burst 优化的数学库,可替代 UnityEngine.Mathf 和 Vector3:

using Unity.Mathematics;

float3 position = new float3(1, 2, 3);
quaternion rot = quaternion.Euler(0, math.PI, 0);
float dist = math.distance(a, b);
float3 dir = math.normalize(b - a);
float val = math.lerp(0f, 1f, 0.5f);
UnityEngineUnity.Mathematics
Vector3float3
Vector2float2
Quaternionquaternion
Mathf.Lerpmath.lerp
Mathf.Sinmath.sin
Matrix4x4float4x4

反模式

内容错误原因修复方法
在 ISystem 可用时使用 SystemBase系统无法通过 Burst 编译,会产生堆分配使用带有 [BurstCompile] 的 ISystem(partial struct)
在迭代期间进行结构性更改会使迭代器失效并导致异常使用 EntityCommandBuffer 延迟更改
在没有 ECB 的并行作业中进行结构性更改竞争条件、崩溃使用带有排序键的 EntityCommandBuffer.ParallelWriter
在 Burst 编译的代码中使用托管类型Burst 无法编译托管类型使用非托管结构体、FixedString、NativeContainer
忘记在 ISystem 上添加 [BurstCompile]系统以托管代码运行,损失性能为结构体和所有方法添加 [BurstCompile]
每帧分配 NativeArray 却不释放内存泄漏使用 Allocator.Temp 分配,或在 OnDestroy 中释放
在 Burst 代码中使用 UnityEngine.Mathf未针对 Burst 优化使用 Unity.Mathematics.math
忘记调用 state.RequireForUpdate<T>()即使不存在匹配实体,系统也会运行在 OnCreate 中调用,以便在不需要时跳过更新
频繁修改 SharedComponentData每次更改都是结构性更改(块移动)对频繁变化的数据使用常规 IComponentData
在内层循环中使用 GetComponent随机访问会破坏缓存一致性使用 SystemAPI.Query 进行线性迭代

关键 API 速查

类 / 结构体关键成员说明
EntityManagerCreateEntity、DestroyEntity、AddComponent<T>、RemoveComponent<T>、GetComponentData<T>、SetComponentData<T>仅限主线程;结构性更改
EntityCommandBufferCreateEntity、DestroyEntity、AddComponent、RemoveComponent、Instantiate、Playback延迟执行结构性更改
ECB.ParallelWriter与 ECB 相同,但带有 sortKey 参数可在线程安全地用于并行作业
SystemAPIQuery<T>、Time、GetSingleton<T>、GetComponent<T>、GetComponentLookup<T>从系统中进行静态访问
EntityQueryToEntityArray、ToComponentDataArray<T>、CalculateEntityCount批量操作
IComponentData(标记接口)非托管结构体组件
IBufferElementData(标记接口)动态缓冲区元素
DynamicBuffer<T>Add、RemoveAt、Length、Clear、AsNativeArray每个实体的可变长度数组
IJobEntityExecute(ref T1, in T2, ...)根据参数自动生成查询
IJobChunkExecute(in ArchetypeChunk, ...)手动迭代块
WorldDefaultGameObjectInjectionWorld、EntityManager系统和实体的容器
RefRW<T> / RefRO<T>ValueRW / ValueRO可读写 / 只读组件引用

相关技能

  • unity-scripting -- MonoBehaviour 脚本、协程、async/await
  • unity-physics -- 物理引擎、Rigidbody、碰撞体(适用于 Unity Physics ECS 包)
  • unity-foundations -- GameObject、Transform、Scene、Prefab

其他资源

评论

支持 Markdown