1. 前言
在实时战略(RTS)、模拟经营或战术类游戏中,AI 单位的智能移动是核心功能之一。
传统的寻路(如 A*)可以计算出从起点到终点的路径,但它在动态环境下显得僵硬:当有移动障碍、其他单位或复杂的地形时,AI 往往会出现“卡死”或“频繁抖动”的问题。
为了解决这一问题,我们可以借助Steering Behavior(操舵行为)的思想,让单位像真实生物一样动态调整自己的移动方向,实现平滑、自然且可预测的移动效果。
本文基于Unity,结合Flow Field寻路,实现了一个具备目标追踪、避障、群体协同功能的简易 Steering Agent。
2. 功能概述
该系统的核心目标是让一个智能单位(Steering Agent)能够在以下场景中自然移动:
- 目标追踪
- 接收目标点命令(
MoveTo),自动移动到目标位置。 - 靠近目标时自动停止(
stoppingDistance控制)。
- 接收目标点命令(
- 避障行为(Obstacle Avoidance)
- 检测前方一定范围内的障碍物。
- 在多方向采样中选择最佳的可通行方向,保证单位不会卡住。
- 群体协作(Flocking Behavior)
- 分离(Separation):避免与其他单位重叠。
- 凝聚(Cohesion):向队伍中心靠拢(可选)。
- 平滑移动
- 防止小幅度方向波动导致的抖动(方向阈值控制)。
- 使用插值(
Quaternion.Slerp)平滑旋转。
3. 系统核心结构
(1)主逻辑循环
核心思想:
- 优先判断是否有移动目标。
- 合成多个 steering 向量(路径指引、避障、分离等)。
- 平滑更新方向,减少视觉抖动。
private void FixedUpdate()
{
AlignToGround(); // 保持贴地
if (targetPosition != Vector3.zero) // 有目标时
{
Vector3 toTarget = targetPosition - transform.position;
toTarget.y = 0f;
if (toTarget.magnitude > stoppingDistance) // 未到达
{
Vector3 newSteering = ComputeSteering(); // 计算合成方向
float angle = Vector3.Angle(lastSteering, newSteering);
// 避免小幅抖动
if (angle > directionThreshold || lastSteering == Vector3.zero)
{
steering = newSteering;
lastSteering = newSteering;
}
MoveAgent(steering);
isMoving = true;
}
else // 到达
{
targetPosition = Vector3.zero;
isMoving = false;
OnDestinationReached();
}
}
}
(2)Steering向量合成逻辑
核心思想:
- 基础方向:来源于流场寻路算法,确保全局寻路正确性。
- 避障方向:防止与场景中的障碍物发生碰撞。
- 分离方向:防止多个单位挤在一起。
private Vector3 ComputeSteering()
{
Vector3 moveDir = GetFlowFieldDirection(); // 基础导航方向
Vector3 avoidDir = ComputeObstacleAvoidance(); // 避障方向
Vector3 separation = ComputeSeparation(); // 分离方向
// 按权重合成
Vector3 finalDir = moveDir
+ avoidDir * avoidStrength
+ separation * separationWeight;
return finalDir.normalized;
}
(3)障碍物避让逻辑
通过方向采样 + 射线检测的方式,为单位寻找一条尽量畅通且接近目标方向的路径:
- 方向采样
- 将周围 360° 等分成若干个方向(代码中是 16 个方向),逐一检测每个方向的可行性。
- 障碍检测
- 对每个方向发射一个球形射线(SphereCast),检测该方向在给定半径和距离内是否有障碍物。
- 评分与选择
- 如果方向没有被阻挡,则计算该方向与目标方向的夹角余弦(
Vector3.Dot),越接近目标方向,分数越高。 - 选出得分最高的方向作为当前最佳避让方向。
- 如果方向没有被阻挡,则计算该方向与目标方向的夹角余弦(
- 方向平滑
- 最佳方向会与上一次避让方向进行插值(
Lerp)平滑过渡,减少方向突变造成的抖动。
- 最佳方向会与上一次避让方向进行插值(
private Vector3 ComputeObstacleAvoidance()
{
float castRadius = 0.5f;
float castDistance = obstacleAvoidanceRadius;
// 方向采样:16个方向更细致
int numSamples = 16;
Vector3 bestDir = Vector3.zero;
float bestScore = float.MinValue;
Vector3 toTarget = (targetPosition - transform.position).normalized;
for (int i = 0; i < numSamples; i++)
{
float angle = i * 360f / numSamples;
Vector3 dir = Quaternion.Euler(0, angle, 0) * Vector3.forward;
Ray ray = new Ray(transform.position + Vector3.up * 0.5f, dir);
bool blocked = Physics.SphereCast(ray, castRadius, castDistance, obstacleMask);
if (!blocked)
{
// 越靠近目标方向分数越高
float alignment = Vector3.Dot(dir, toTarget);
if (alignment > bestScore)
{
bestScore = alignment;
bestDir = dir;
}
}
}
if (bestDir != Vector3.zero)
{
lastAvoidanceDir = Vector3.Lerp(lastAvoidanceDir, bestDir, 0.2f);
return lastAvoidanceDir;
}
return Vector3.zero;
}
(4)单位间距控制逻辑
确保单位之间保持合理的距离,避免出现重叠或拥挤现象:
- 邻居检测
- 在一定感知半径内(
neighborRadius)检测其他单位的碰撞体。
- 在一定感知半径内(
- 分离力计算
- 对每个邻居计算一个反向力(单位位置与邻居位置的差向量),并且距离越近,分离力越大(使用
1 / distance衰减)。
- 对每个邻居计算一个反向力(单位位置与邻居位置的差向量),并且距离越近,分离力越大(使用
- 均值处理
- 将所有邻居的分离力求平均,得到整体的分离趋势。
- 方向归一化
- 最终返回一个单位方向向量,用于在移动时与其他 steering 力结合,避免贴得过近。
private Vector3 ComputeSeparation()
{
Vector3 separationForce = Vector3.zero;
int neighborCount = 0;
Collider[] neighbors = Physics.OverlapSphere(transform.position, neighborRadius, unitLayer);
foreach (var neighbor in neighbors)
{
if (neighbor.gameObject == gameObject) continue;
Vector3 away = transform.position - neighbor.transform.position;
float distance = away.magnitude;
if (distance > 0)
{
separationForce += away.normalized / distance; // 越近力越大
neighborCount++;
}
}
if (neighborCount > 0)
{
separationForce /= neighborCount;
}
return separationForce.normalized;
}

Leave a comment