O(n²)→O(n):ECS + 空间哈希实现 Boids 群体模拟 📅 发布时间:2026/9/20 19:42:28 👁 浏览次数: O(n²)→O(n)ECS 空间哈希实现 Boids 群体模拟【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamplesUnity DOTS 官方的 Boids 示例把完整群体模拟——鱼群、捕食者追逐、障碍物规避——收进一个 ECS 系统用空间哈希加 Burst 并行 Job把经典 O(n²) 的邻居搜索压到近 O(n)。这套方案可以直接套用到你项目里的 NPC 群体移动、鸟群、鱼群场景。结论先行三个 Burst Job 撑起大场面鱼群先说结果仓库里的 Boids 示例把几百到几万条鱼同时做分离、对齐、趋近目标这件事全部放在 BoidSystem.cs 这一个系统里完成重活由三个加了[BurstCompile]的 Job 分担。根据项目说明该示例演示的是用高级 ECS 与 Job 用法实现复杂的大规模模拟行为也就是让成千上万的实体在合理帧时间内完成群体演算。它的核心思路只有两句话第一用空间哈希把找邻居从全场两两比较变成查桶第二三条 Boids 规则不做逐鱼精算而是先按格子聚合出平均朝向和平均位置再让每条鱼只读自己格子的聚合值。这套架构里数据定义、生成逻辑、行为计算、动画表现各自独立成文件改哪块看哪块。后文按数据层 → 计算层的维度拆开讲最后是运行步骤和调参表。为什么传统 OOP 双层循环撑不住写过的都知道朴素 Boids 是双层 for 循环每条鱼遍历全场所有鱼判断距离。n 条鱼每帧就是 n² 次比较——1 万条鱼意味着上亿次距离计算单线程根本跑不完更别提每条鱼还涉及 transform 读写。GameObject 路线的另一个问题是内存布局。几千个独立 MonoBehaviour 的 transform 散落在堆上缓存命中率低CPU 大部分时间在等内存。ECS 的思路反过来把位置、朝向按实体类型连续排布在一个大块内存里组件数据按原型聚集Job 顺序扫过去就是顺序访存缓存友好。所以瓶颈不在算法对不对而在算得快不快。空间哈希解决比较次数ECS 的内存布局解决单次比较的成本Burst 解决指令级效率——三者各管一段。数据层Boid 共享组件与变体自动分区鱼的行为参数定义在 BoidAuthoring.cs 里Boid是一个ISharedComponentData结构体字段包括 CellRadius空间格大小、SeparationWeight / AlignmentWeight / TargetWeight三大规则权重、ObstacleAversionDistance避障距离和 MoveSpeed。这里有个不显眼但很关键的机制共享组件的值相同的实体会被 ECS 归入同一个变体。系统运行时先枚举出所有Boid变体逐个加上 shared filter 后单独跑一整轮 Job 流水线。这个设计的代价和收益都要知道。收益是同变体鱼共享一套参数和一份格子数据Job 循环里零分支。代价是参数不同的两群鱼互不交互。如果你想让快鱼和慢鱼混游成一群必须把它们放进同一个 Boid 变体里行为差异只能靠别的方式表达。追逐目标与障碍物则用纯标记组件表达BoidTargetAuthoring.cs 的BoidTarget和 BoidObstacleAuthoring.cs 的BoidObstacle都是空结构体只靠有没有这个组件区分身份位置直接读LocalToWorld。计算层哈希、合并、转向的三级 Job 流水线整帧的计算是一条 Job 依赖链InitialPerBoidJob建哈希表 →MergeCells合并格子 →SteerBoidJob算转向。三个 Job 全部 Burst 编译通过JobHandle串联保证执行顺序。第一级把空间切成桶InitialPerBoidJob是IJobEntity并行遍历所有鱼。它做两件事把朝向和位置抽到NativeArray供后面随机访问并把位置量化成格子哈希写入并行哈希表var hash (int)math.hash(new int3( math.floor(localToWorld.Position * InverseBoidCellRadius))); ParallelHashMap.Add(hash, entityIndexInQuery);注意这是稀疏网格没有预定义世界边界鱼游到多远就在哪建桶。CellRadius 取 8 时位置除以 8 再取整就是格子坐标。第二级把桶里的鱼压成聚合值MergeCells遍历哈希表把落进同一格的所有鱼累加成格子级数据鱼数、朝向和对齐用的、位置和分离用的同时顺手算出每个格子离最近的捕食者和障碍物有多远。这样后面每条鱼要读的只是本格的常数次数据而不是 N 条鱼的数据。第三级三条规则合成一个转向SteerBoidJob里经典三规则变成三行加权向量。对齐是本格平均朝向减去我的朝向分离是把我的位置放大 neighborCount 倍再减格子位置总和两者都指向让鱼回到格子的平均状态var alignmentResult CurrentBoidVariant.AlignmentWeight * math.normalizesafe((alignment / neighborCount) - forward); var separationResult CurrentBoidVariant.SeparationWeight * math.normalizesafe((currentPosition * neighborCount) - separation); var targetHeading CurrentBoidVariant.TargetWeight * math.normalizesafe(nearestTargetPosition - currentPosition);最后一层处理是避障。鱼与鲨鱼的距离小于 ObstacleAversionDistance 时直接用math.select把整个转向向量切掉换成沿远离鲨鱼方向走出安全距离的向量var avoidObstacleHeading (nearestObstaclePosition math.normalizesafe(obstacleSteering) * CurrentBoidVariant.ObstacleAversionDistance) - currentPosition; var targetForward math.select(normalHeading, avoidObstacleHeading, nearestObstacleDistanceFromRadius 0);新朝向不是瞬间转向而是forward DeltaTime * (targetForward - forward)的缓动插值所以鱼群转弯是平滑的弧线而不是折线。另外系统在开头把DeltaTime钳制到 0.05 秒防止卡顿帧后鱼群瞬移穿墙。三步跑起 Boids从克隆到 Play 模式仓库基于 Unity 6.2 与 Entities 1.4 系列包先装好对应版本再开工。克隆仓库git clone https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples用 Unity Hub 打开EntitiesSamples子项目首次打开等包解析完成打开场景 Boids.unity进 Play 模式场景分两块鱼群和捕食者由Simulation.unity子场景里的BoidSchool实体驱动BoidSchoolSpawnSystem.cs 在运行起始把 prefab 批量实例化成一个球体分布的鱼群水底环境、红鱼目标两条和鲨鱼一条放在Environment.unity子场景目标与鲨鱼走的是关键帧动画可以在 UI 的 ActorAnimation 面板里播放/暂停。鱼自身的摆动动画由 SampledAnimationClipPlaybackSystem.cs 按游速采样动画帧游得越快摆尾越快。这里最容易踩的坑prefab 默认被强制为 Dynamic Transform会多出鱼根本用不到的LocalTransform所以 spawn 系统里专门有一步对所有 Boid 移除该组件。如果你自己加鱼时忘了这步系统查询虽然不要求它但白白多了一份组件数据。鱼群参数调优表改一个数看一处行为参数都挂在BoidAuthoring组件上Inspector 里直接改默认值与行为对照如下参数默认值调大的效果调小的效果CellRadius8.0社交半径变大更容易聚成大群单格内鱼更多鱼群细碎化局部小群增多SeparationWeight1.0鱼间距拉开避免叠成一团鱼互相挤压、穿模AlignmentWeight1.0朝向更统一整群转向更明显队形松散各自为政TargetWeight2.0更果断地追逐红鱼目标鱼群懒游目标感弱ObstacleAversionDistance30.0离鲨鱼更远就开始绕行几乎贴到鲨鱼身边才躲MoveSpeed25.0整体游速加快转向弧线拉长鱼群显得迟缓调参时的顺序建议先定 CellRadius它决定了邻居范围这个前提再调 SeparationWeight 和 AlignmentWeight 的比值——两者之比基本就是散开 vs 抱团的滑块。TargetWeight 默认给到 2.0明显高于另外两个这解释了场景里鱼群整体追着目标走的观感。边界与坑动手前先知道的三件事第一变体隔离是硬约束。如前所述Boid共享组件值不同就互不感知做多群鱼各自为政时这是特性做混合鱼群时是坑。第二最近目标/障碍物是按格子算的同格所有鱼共用同一个最近鲨鱼格内个体差异被抹掉——格子越大越省但行为越粗糙。第三空间哈希没有边界鱼游出去就是出去了示例靠环境场景的外墙把鱼圈住你的场景没有墙就要自己处理回收逻辑。下一步可以试给鱼群加行为状态机觅食/警戒/溃散三态切换按BoidSchool的思路做领导者-跟随者角色把动画采样换成基于游速的骨骼权重混合。延伸阅读仓库自带文档 EntitiesSamples/Docs/其中 entities-components.md 和 cheatsheet 里的 collections.md、mathematics.md 与本文用到的 NativeContainer、数学函数一一对应想看更多规模的 ECS 用例同仓库的 Boids README 和 PhysicsSamples、NetcodeSamples 也是不错的起点。【免费下载链接】EntityComponentSystemSamples项目地址: https://gitcode.com/GitHub_Trending/en/EntityComponentSystemSamples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考