文章目录
在 RTS 游戏或自然模拟中,如何让成百上千个单位像鸟群一样自然地移动?为什么它们既不会撞成一团,又不会四散逃跑?本文将深入拆解 Craig Reynolds 的 Boids 算法,拒绝伪代码,直接提供生产环境可用的 Unity C# 实现方案。
标签:#游戏开发 #Unity3D #算法 #人工智能 #C#
1. 什么是“涌现” (Emergence)?
在编写游戏 AI 时,新手往往会陷入通过大量的 if-else 去控制每一个单位的逻辑陷阱。例如:“如果前面有墙就左转,如果太近就停下……”这样的代码不仅难以维护,而且表现极其生硬。
自然界告诉我们:复杂的群体行为,往往源自个体遵循的简单规则。
Craig Reynolds 在 1986 年提出的 Boids (Bird-oid objects) 模型证明了这一点。他发现,只要每个个体(Boid)都严格遵守三条简单的规则,整个群体就会自发地表现出复杂的、类似鸟群或鱼群的队形。
这种由个体简单规则导致群体复杂特征的现象,被称为涌现 (Emergence)。
2. 核心数学基础:向量操纵 (Vector Steering)
在 Boids 算法中,我们不直接控制位置(Position),而是控制力(Force),进而影响加速度(Acceleration)和速度(Velocity)。
核心公式基于 Reynolds 的操纵行为理论:
S t e e r i n g F o r c e ⃗ = D e s i r e d V e l o c i t y ⃗ − C u r r e n t V e l o c i t y ⃗ \vec{SteeringForce} = \vec{DesiredVelocity} - \vec{CurrentVelocity} SteeringForce=DesiredVelocity−CurrentVelocity
DesiredVelocity (期望速度):个体想要去的地方(基于规则计算)。
CurrentVelocity (当前速度):个体目前的惯性。
SteeringForce (操纵力):为了达到期望速度,需要施加的力。这个力通常会被限制最大值(MaxForce),模拟真实的物理惯性,防止物体瞬间掉头。
3. 核心三定律 (The Three Rules)
所有的群体行为都由以下三个向量叠加而成:
3.1 分离 (Separation) —— “别挤我!”
目的:避免与周围的邻居发生碰撞或拥挤。
算法逻辑:
遍历感知半径内的所有邻居。
对于每一个邻居,计算一个指向自己的向量(MyPos - NeighborPos)。
根据距离加权:距离越近,排斥向量的模长(Magnitude)越大。
将所有排斥向量相加并归一化。
3.2 对齐 (Alignment) —— “随大流!”
目的:让自己的方向与周围邻居的平均方向保持一致。
算法逻辑:
获取感知半径内所有邻居的当前速度。
计算这些速度的平均值。
这个平均速度就是我们的“期望速度”。
3.3 凝聚 (Cohesion) —— “别掉队!”
目的:向邻居的中心靠拢,保持群体的整体性。
算法逻辑:
获取感知半径内所有邻居的位置。
计算这些位置的平均值(重心 Center of Mass)。
产生一个从自己位置指向这个重心的向量。
4. Unity C# 代码实战
为了方便管理,我们将代码分为两个部分:
-
Boid.cs:挂载在每个个体上,负责计算自身的受力。
-
FlockManager.cs:挂载在空物体上,负责生成单位并统一调整参数。
4.1 Boid 类:个体的思考
using UnityEngine;
using System.Collections.Generic;
public class Boid : MonoBehaviour
{
// 当前的速度和加速度
public Vector3 velocity;
private Vector3 acceleration;
// 引用管理器以获取全局参数
private FlockManager manager;
public void Initialize(FlockManager manager, Vector3 initialVelocity)
{
this.manager = manager;
this.velocity = initialVelocity;
}
public void UpdateBoid()
{
// 1. 获取感知范围内的邻居
List<Boid> neighbors = GetNeighbors();
// 2. 计算三个规则的力
Vector3 separation = Separation(neighbors);
Vector3 alignment = Alignment(neighbors);
Vector3 cohesion = Cohesion(neighbors);
// 3. 累加力 (F = ma, 这里假设 m=1)
acceleration = Vector3.zero;
acceleration += separation * manager.separationWeight;
acceleration += alignment * manager.alignmentWeight;
acceleration += cohesion * manager.cohesionWeight;
// 4. 更新速度与位置
velocity += acceleration * Time.deltaTime;
// 限制最大速度
velocity = Vector3.ClampMagnitude(velocity, manager.maxSpeed);
// 移动物体
transform.position += velocity * Time.deltaTime;
// 让物体朝向移动方向
if (velocity != Vector3.zero)
{
transform.forward = velocity;
}
}
// 获取邻居的函数 (O(N)复杂度,后续可优化)
private List<Boid> GetNeighbors()
{
List<Boid> neighbors = new List<Boid>();
foreach (var boid in manager.allBoids)
{
if (boid == this) continue;
float dist = Vector3.Distance(transform.position, boid.transform.position);
if (dist < manager.perceptionRadius)
{
neighbors.Add(boid);
}
}
return neighbors;
}
// --- 规则实现 ---
// 规则1:分离
private Vector3 Separation(List<Boid> neighbors)
{
Vector3 steering = Vector3.zero;
if (neighbors.Count == 0) return steering;
foreach (var boid in neighbors)
{
// 计算背离向量:自己位置 - 邻居位置
Vector3 diff = transform.position - boid.transform.position;
// 距离越近,权重越大 (1/distance)
float dist = diff.magnitude;
if (dist > 0)
{
diff /= dist; // 归一化
steering += diff / dist; // 再次除以距离加强近距离排斥
}
}
return steering.normalized; // 返回方向
}
// 规则2:对齐
private Vector3 Alignment(List<Boid> neighbors)
{
Vector3 steering = Vector3.zero;
if (neighbors.Count == 0) return steering;
foreach (var boid in neighbors)
{
steering += boid.velocity;
}
steering /= neighbors.Count; // 平均速度
return steering.normalized;
}
// 规则3:凝聚
private Vector3 Cohesion(List<Boid> neighbors)
{
Vector3 steering = Vector3.zero;
if (neighbors.Count == 0) return steering;
Vector3 centerOfMass = Vector3.zero;
foreach (var boid in neighbors)
{
centerOfMass += boid.transform.position;
}
centerOfMass /= neighbors.Count; // 平均位置
// 期望移动方向:重心 - 自己位置
Vector3 desired = centerOfMass - transform.position;
return desired.normalized;
}
}
4.2 FlockManager 类:上帝视角
这个脚本负责生成 Boids,并允许你在 Unity Inspector 面板中实时调整权重。
using UnityEngine;
using System.Collections.Generic;
public class FlockManager : MonoBehaviour
{
[Header("Settings")]
public GameObject boidPrefab;
public int boidCount = 50;
public float spawnRadius = 10f;
[Header("Boid Parameters")]
[Range(0, 10)] public float minSpeed = 2f;
[Range(0, 20)] public float maxSpeed = 5f;
[Range(0, 10)] public float perceptionRadius = 2.5f;
[Header("Weights")]
[Range(0, 5)] public float separationWeight = 1.5f;
[Range(0, 5)] public float alignmentWeight = 1.0f;
[Range(0, 5)] public float cohesionWeight = 1.0f;
[HideInInspector]
public List<Boid> allBoids = new List<Boid>();
void Start()
{
for (int i = 0; i < boidCount; i++)
{
Vector3 pos = transform.position + Random.insideUnitSphere * spawnRadius;
GameObject obj = Instantiate(boidPrefab, pos, Quaternion.identity);
Boid boid = obj.GetComponent<Boid>();
// 赋予一个随机初始速度
Vector3 initialVel = Random.onUnitSphere * Random.Range(minSpeed, maxSpeed);
boid.Initialize(this, initialVel);
allBoids.Add(boid);
// 将生成的 Boid 设为管理器的子物体,保持 Hierarchy 整洁
obj.transform.parent = transform;
}
}
void Update()
{
// 统一控制所有 Boid 的更新
foreach (var boid in allBoids)
{
boid.UpdateBoid();
}
}
}
5. 常见问题与优化进阶
5.1 边界处理
上面的代码没有处理边界,Boids 最终会飞出屏幕。通常有两种处理方式:
环绕模式 (Wrap Around):从左边飞出去,从右边飞回来(类似贪吃蛇)。
软边界 (Soft Bound):当接近边界时,施加一个指向屏幕中心的反向力。
C# 实现软边界逻辑:
可以在 Boid.cs 中添加一个 BoundCheck 方法,加到 acceleration 中。
private Vector3 BoundCheck()
{
float boundRadius = 20f; // 假设活动范围是20米
Vector3 centerOffset = Vector3.zero - transform.position; // 指向原点的向量
float dist = centerOffset.magnitude;
if (dist > boundRadius)
{
// 如果超出了范围,返回一个指向中心的强力
return centerOffset.normalized * (dist - boundRadius);
}
return Vector3.zero;
}
5.2 性能灾难与空间划分
上述代码中最耗时的部分是 GetNeighbors()。它是一个典型的 O ( N 2 ) O(N^2) O(N2) 算法。如果场景里有 100 个单位,每一帧要进行 100 × 100 = 10 , 000 100 \times 100 = 10,000 100×100=10,000 次距离检测。
解决方案:
空间网格 (Spatial Grid):将地图划分为 10x10 的格子。每个 Boid 只需检测同一个格子或相邻格子里的单位。
Unity Job System & Burst Compiler:
这是 Unity 的大杀器。Boids 的计算是纯数学运算,且个体之间没有强依赖(除了读位置),非常适合并行化。使用 Unity 的 IJobParallelForTransform 可以轻松支持 5000+ 个单位。
6. 总结与参数调优建议
在实现完代码后,你会发现调参是一门艺术:
分离 (Separation) 权重过大:个体会四散而逃,像无头苍蝇。
凝聚 (Cohesion) 权重过大:所有个体会缩成一个极小的球,像黑洞一样。
对齐 (Alignment) 权重过大:看起来像阅兵方阵,但这会导致它们很难转弯。
黄金比例推荐:通常让 分离 > 对齐 = 凝聚。例如 Separation: 1.5, Alignment: 1.0, Cohesion: 1.0 是一个不错的起点。
希望这篇详细的 C# 指南能帮你实现自己的人工智能群聚效果!如果有任何问题,欢迎在评论区讨论。
关注博主,获取更多 Unity 算法硬核干货!
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_42555291/article/details/156271536



