1781 字
9 分钟
急速入门 Unity6.3 ECS
2026-09-11

ECS, Jobs, Burst 不是三个完全独立的系统。 ECS 负责:数据怎么组织,逻辑怎么批量处理。 Jobs 负责:这些处理怎么放到多线程执行。 Burst 负责:把适合的 C# 代码编译成高优化的原生机器码

NOTE

Unity 官方也明确把 Burst 描述为与 Job System 密切配合的高性能编译器,同时 Burst 也是 ECS 技术体系的重要组成部分。

ECS 到底是什么#

E = Entity
C = Component
S = System

Entiy:一个”编号”#

Entity 可以非常粗暴的理解为:一个没有具体功能的 ID。

他不像 GameObject 对象那样自带 Transform、Renderer、Collider 等大量东西。

它更像:这又一个东西,它的标号是 9527;真正的数据来自 Component。

Component:数据#

ECS 的 Component 更强调”数据”,这里并没有行为方法。它描述的是这个 Entity 有什么数据。

System:负责做事情#

Component 是数据,System 就是处理数据的逻辑。

public partial struct MoveSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
// 找到拥有 MoveSpeed 和 LocalTransform 的 Entity
// 然后让它们移动
}
}

ECS 与传统对象的对比#

// 传统对象
Enemy
├─ Transform
├─ Enemy.cs
│ ├─ hp
│ ├─ speed
│ └─ Update()
└─ Renderer
// ECS
Entity 1
├─ LocalTransform
├─ MoveSpeed
└─ Health
Entity 2
├─ LocalTransform
├─ MoveSpeed
└─ Health
Entity 3
├─ LocalTransform
└─ MoveSpeed
MoveSystem
↓
处理所有:
LocalTransform + MoveSpeed

这就是 ECS 和 MonoBehaviour 思维最大的区别。

ECS 为什么快#

TIP

一个误解:ECS 快是不是因为 Entity 比 GameObject 快?

ECS 快的真正原因是他的数据更加集中,传统的写法会让数据散落在大量的对象里(分散在内存的各处,物理上不连续)。

ECS 更倾向于把同类型的数据组织的非常紧凑(物理上连续)。

CPU 处理的时候非常适合:批量连续读取,批量连续计算,批量连续写入。这就特别适合:

  • CPU cache
  • 多线程
  • SIMD
  • Burst

这就是 ECS 真正强大的来源。

初识 ECS 世界#

ECS 并不是简单多出几个类这么简单。

它建立在 Entities Package 以及相关 DOTS 技术包上。Unity 官方对 Enitties 的定义就是一套现代的 Entity Component System 实现。

Unity.Entities
↓
ECS 核心
Unity.Transforms
↓
实体 Transform 相关功能
Unity.Collections
↓
NativeArray / NativeList 等高性能容器
Unity.Jobs
↓
多线程任务
Unity.Burst
↓
高性能编译

System 的生命周期#

OnCreate() // 创建 System,初始化
↓
OnUpdate() // 每次系统运行
↓
OnDestroy() // 销毁

OnCreate#

它类似于 MonoBehaviour 中的 Awake;也就是 System 创建时执行。通常用来做:

  • 初始化状态
  • 创建 EntityQuery
  • RequireForUpdate
  • 准备数据

OnUpdate#

这是 ECS System 最重要的函数;它相当于 MonoBehaviour 中的 Update,也就是系统运行时,真正处理游戏逻辑得到地方。

  • 移动
  • 攻击
  • 生成/销毁
  • 修改数据

OnDestroy#

System 被销毁时调用,可以理解为 MonoBehaviour 中的 OnDestroy。

  • 释放资源
  • 清理自己大的数据

SystemState 是什么#

Unity 给当前 System 提供的一组运行环境和控制入口。

state.RequireForUpdate<MoveSpeed>();

这段案例代码的作用是:只有存在 MoveSpeed 数据的时候,我这个 System 才需要运行。

RequireForUpdate 是干什么的?#

public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<MoveSpeed>();
}

如果世界上一个 MoveSpeed 都没有,这个 System 不需要执行 OnUpate;直接粗暴理解为”没有这个数据,就别让我跑。“

System 的完整最小模板#

using Unity.Burst;
using Unity.Entities;
[BurstCompile]
public partial struct MoveSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<MoveSpeed>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
}
[BurstCompile]
public void OnDestroy(ref SystemState state)
{
}
}

[BurstCompile]#

这是 Burst,Unity 官方说明,Burst 会使用 LLVM,把 .NET IL 转换成针对目标 CPU 优化过的原生代码。

Burst 不是另一个逻辑系统,而是把适合 Burst 的代码编译的更快。

Jobs 系统是什么#

Jobs 系统会将适合拆开的代码拆成多个部分,让后交给多个核心并行处理。

一段案例代码:

for (int i = 0; i < 100000; i++)
{
data[i] *= 2;
}

Jobs 将任务拆开:

Job 1 → 0 ~ 24999
Job 2 → 25000 ~ 49999
Job 3 → 50000 ~ 74999
Job 4 → 75000 ~ 99999

于是便可以并处处理。

一个简单的 Jobs 案例#

[BurstCompile]
public struct MyJob : IJob
{
public void Execute()
{
// 工作
}
}
// =========
MyJob job = new MyJob();
JobHandle handle = job.Schedule();

案例中的 Schedule() 不是马上执行,而是把这个 Job 交给 Job System。之后 handle.Complete();;表示我要确保这个 Job 已经完成。

Jobs 不能随便使用普通的 C# 对象#

这是 Jobs 中非常重要的一条规则。

Jobs/Burst 并不喜欢这种托管对象;因为他们背后需要更加可控的内存布局。

Unity 官方也明确说明,Burst 不支持 managed objects,所以 Job 中通常需要使用 unmanaged 类型以及 NativeContainer(使用 NativeArray<int> 而不是 int[])。

NativeArray 是什么#

可以将其理解为:适合 Jobs 使用的原生内存数组。

NOTE

Unity 的 NativeContainer 还带有 Job Safety System,会跟踪读写关系,避免多个 Job 对共享数据进行危险访问。

Allocator.TempJob 是什么#

new NativeArray<int>(1000, Allocator.TempJob);
  • Temp 非常短
  • TempJob 给 Job 临时使用
  • Persistent 长期存在
NOTE

Unity 官方对 TempJob 的定位就是适用于生命周期较短的 Job 数据,并要求即使 Dispose(array.Dispose();)。

Jobs + Burst 最简单组合#

using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
[BurstCompile]
public struct AddJob : IJob
{
public NativeArray<int> Numbers;
public void Execute()
{
Numbers[0] += 10;
}
}
// 后续调用
NativeArray<int> numbers =
new NativeArray<int>(1, Allocator.TempJob);
AddJob job = new AddJob
{
Numbers = numbers
};
JobHandle handle = job.Schedule();
handle.Complete();
numbers.Dispose();

SystemAPI#

现代 ECS 中 SystemAPI 至关重要。可以将其理解成:从 ECS 世界里查找我需要的数据。

// 最常用到的查询操作。
// 找到具有 LocalTransform 的 Entity,后续可以对其进进行读写操作。
SystemAPI.Query<RefRW<LocalTransform>>()

RefRO 和 RefRW#

RefRO#

RefRO<MoveSpeed> // 只读操作

RefRW#

RefRW<LocalTransform> // 读写操作

SystemAPI.Time.DeltaTime#

当前 ECS 世界这一帧经过的时间,也就是对应 Mono 中的 Time.deltaTime。 获取每帧时间

SystemAPI.Query 做了什么#

SystemAPI.Query<RefRW<LocalTransform>,RefRO<MoveSpeed>>()

可以直接翻译成:找出同时拥有LocalTransform和MoveSpeed的 Entity。

这就是 ECS 最核心的能力之一:按照组件组合筛选数据。

这就是 ECS 和传统写法最大的思维转换,这个转变比记住 API 重要的多。

ECS 中的另一个重要概念:World#

可以粗暴将其理解为 ECS 的运行世界,里面放着 Entities,Systems,Components。

WARNING

所以 Scene 和 ECS World 并不是完全等价的概念。

System 的执行顺序#

如果你有多个 System,例如 MoveSystem 需要 InputSystem,那么我们就需要 System Update Order。

这部分相当庞大(SystemGroup 概念),在现阶段只需知道即可。

SystemGroup#

所有的系统并不是全部扔在一起的,就像上面提到的,有时候我们需要控制系统的执行顺序。

SystemGroup 通常会有:

  • Initialization
  • Simulation
  • Presentation

这样的更新阶段,他们的顺序依次进行,所以 ECS 实际运行并不是 System 逐一执行,而是一颗系统树。

ECS 代码通常会是 partial struct#

public partial struct CubeSystem : ISystem

这里的 partial 并不是我们要拆成多个文件。

NOTE

现代 Entities 会使用代码生成等机制,partial 是 ECS 某些 API / Source Generation 工作方式的一部分。

当前阶段直接按照规范写即可。