using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
public class UnitMoveSystem : MonoBehaviour
{
NativeArray positions;
NativeArray velocities;
Transform\[\] units; // 普通 C# 侧还是用这些
void Update()
{
// 1) 普通 C#:把场景数据填进原生数组(或上帧结果已在数组里)
for (int i = 0; i < units.Length; i++)
positions[i] = units[i].position;
// 2) Burst Job:只做纯计算
var job = new MoveJob
{
positions = positions,
velocities = velocities,
deltaTime = Time.deltaTime
};
JobHandle handle = job.Schedule(units.Length, 64);
handle.Complete(); // 小项目先 Complete;大项目可延迟一帧再取
// 3) 普通 C#:把结果写回 Unity 对象
for (int i = 0; i < units.Length; i++)
units[i].position = positions[i];
}
[BurstCompile]
struct MoveJob : IJobParallelFor
{
public NativeArray<float3> positions;
[ReadOnly] public NativeArray<float3> velocities;
public float deltaTime;
public void Execute(int i)
{
positions[i] += velocities[i] * deltaTime;
}
}
}
项目里怎么落地(实操规则)
先用普通 C# 写对
功能稳定后,再找 Profiler 里 CPU 高的循环。
只抽"纯计算热点"进 Burst
例如:批量移动、距离排序、伤害结算的数值段、生成高度图。
不要把 UI、网络、Instantiate、直接碰 Transform 塞进 Job。
数据用原生容器
NativeArray、NativeList 等,并在合适时机 Dispose,避免泄漏。
什么规模值得上
情况 建议
几十个对象、逻辑不重
普通 C# 即可
成百上千、循环明显吃 CPU
Jobs + Burst
上万实体、要极致
再考虑 DOTS/ECS(仍是这套思想的强化版)
一句话工作流
Profiler 找到热循环 → 数据改成数组 → Job 里 Burst 算 → 主线程写回场景。
.为什么Burst代码会比普通的代码快/效率高.
1.burst有.自动向量化 SIMD的计算(数组型计算时很有效率),CPU 用宽一点的寄存器,一次装好几个 float同时运算。数组/连续内存,循环规整、依赖少,减少了内存切页。
2.Burst 限制你多用 struct、NativeArray,少用/不用 class、new 托管对象、随便装箱。这样就不会有托管环境里的额外成本,Burst 就变成了连续的数值内存 → 直接算 → 算完。而普通的类对象引用 → 托管检查 → 可能触发/牵扯 GC → 再算 普通的类跑在托管环境里的额外成本:对象头、引用检查,数组边界检查(更频繁/更难优化时),不能当纯数值那样随便重排指令,和 GC 相关的写屏障等。所以burst代码几乎没有 GC / 托管开销,算得更纯粹。
3.Burst 编成高度优化的原生码:普通 C# 脚本编译好在Mono/IL2CPP 托管执行,Burst 编译的代码编成原生机器码(类似 C/C++)。
Burst :受限 C# → Burst/LLVM → 直接生成原生机器码
普通 C# → IL → IL2CPP 转成 C++ → 再编成原生机器码
4.常配合 Jobs 多线程(这是另一层加速,严格说不属于 Burst 本身)
打包过程中 打进 APK 后
普通 C#(IL2CPP)
C# → 生成临时 C++ → 再编译
机器码(在 libil2cpp.so 等里)
Burst
受限 C# → LLVM → 生成原生码
机器码(常在 lib_burst_generated.so 一类库里)
普通 C#
class、普通数组、字符串 → 托管堆(GC)
Burst Job
NativeArray 等 → 原生内存(非 GC)
是不是原生内存就更快?
不是"原生"两个字本身自带加速度,而是原生这条路上通常更少拖后腿:
没有 GC 干扰(热路径不分配托管对象、不被回收打断)
数据更连续、更好缓存(结构体数组,不像一堆对象东一块西一块)
方便 Burst 做 SIMD/激进优化
少一层托管对象语义负担
如果只是读一个 float,原生和托管数组的理论带宽差不多;
真正拉开差距的,是上面这些在大批量热循环里的累积优势。
一句话: 普通 C# 多用托管堆,Burst 多用原生内存;更快主要是因为少 GC、更连续、更好优化,不是单纯"原生内存魔法加速"。
结构体数组为什么香
NativeArray 或 EnemyData\[\](EnemyData 是 struct)大致是这样排的:
敌1整份敌2整份敌3整份敌4整份...
←------------------ 连续一大块 ------------→
循环从头扫到尾时,CPU 按块预取,一次缓存行能服务好几个元素。
对比:对象数组(Enemy是class)
Enemy\[\](元素是引用)往往是:
数组里: 指针1指针2指针3... ← 指针本身连续
堆上: 敌1在东,敌2在西,敌3在南 ← 对象本体分散
循环时要"跳来跳去"找对象,缓存经常对不上,就慢。
所以结构体数组的cpucache命中率高
CPU Cache(L1/L2/L3)
最常见瓶颈
连续访问 → 命中率高,这是主因
Burst 适合"算得多、逻辑单纯、重复次数极高"的场景,不适合 UI、网络、杂乱玩法逻辑。