当你在《魔兽争霸》中框选一队步兵点击敌方基地时,那些单位会自动分散路线、避开树木、保持队形移动——这种看似简单的群体寻路背后,是实时战略游戏(RTS)开发中最复杂的AI挑战之一。本文将带你用Unity的NavMesh系统,重现这种经典RTS的群体移动体验。
在Hierarchy中创建平面作为地面,添加若干Cube作为障碍物。全选这些对象,在Inspector右上角勾选Navigation Static:
csharp复制// 快速批量设置Static的脚本
[MenuItem("Tools/Mark Selected As NavigationStatic")]
static void MarkAsNavStatic() {
foreach (var obj in Selection.gameObjects) {
GameObjectUtility.SetStaticEditorFlags(
obj,
StaticEditorFlags.NavigationStatic
);
}
}
打开Navigation窗口(Window > AI > Navigation),重点关注Bake页签的以下参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Agent Radius | 0.5 | 单位碰撞体半径 |
| Agent Height | 2.0 | 单位高度 |
| Max Slope | 45° | 可攀爬坡度 |
| Step Height | 0.3 | 可跨越台阶高度 |
点击Bake生成导航网格后,你会看到场景中出现的蓝色可通行区域。此时添加一个带NavMeshAgent组件的胶囊体,就能实现基础寻路。
RTS游戏中经常需要处理建筑被摧毁等动态变化,通过NavMeshObstacle组件实现:
csharp复制// 动态添加障碍物
void AddDynamicObstacle(Vector3 position) {
var obstacle = new GameObject("DynamicObstacle").AddComponent<NavMeshObstacle>();
obstacle.shape = NavMeshObstacleShape.Box;
obstacle.carving = true; // 实时更新导航网格
obstacle.transform.position = position;
}
注意:大量动态障碍物会显著影响性能,建议对移动单位使用Agent避让而非Obstacle
创建10个单位的群体移动控制器:
csharp复制public class SquadController : MonoBehaviour {
public NavMeshAgent[] agents;
public float spreadRadius = 3f;
public void MoveTo(Vector3 destination) {
for(int i=0; i<agents.Length; i++) {
// 计算分散位置
var angle = i * (360f/agents.Length);
var offset = Quaternion.Euler(0,angle,0) * Vector3.forward * spreadRadius;
agents[i].SetDestination(destination + offset);
}
}
}
在Inspector中调整NavMeshAgent的以下关键参数:
csharp复制// 动态调整避让参数
void ConfigureAvoidance(NavMeshAgent agent, bool isImportantUnit) {
agent.avoidancePriority = isImportantUnit ? 50 : 70;
agent.obstacleAvoidanceType = ObstacleAvoidanceType.HighQualityObstacleAvoidance;
}
实现单位跨越壕沟或翻越矮墙:
csharp复制// 动态生成OffMeshLink
void CreateDynamicLink(Vector3 start, Vector3 end) {
var link = new GameObject("DynamicLink").AddComponent<OffMeshLink>();
link.startTransform = new GameObject("Start").transform;
link.endTransform = new GameObject("End").transform;
link.startTransform.position = start;
link.endTransform.position = end;
link.biDirectional = true;
NavMesh.AddNavMeshData(NavMeshBuilder.BuildNavMeshData(
new NavMeshBuildSettings(),
new List<NavMeshBuildSource>(),
new Bounds(Vector3.zero, new Vector3(100,100,100))
));
}
通过Area Type实现不同地形移动速度差异:
csharp复制// 让单位避开高成本区域
agent.areaMask = ~(1 << NavMesh.GetAreaFromName("Swamp"));
csharp复制void UpdateAgents() {
for(int i=0; i<agents.Length; i++) {
if(Time.frameCount % agents.Length == i) {
agents[i].UpdatePath();
}
}
}
csharp复制public void SetSharedDestination(Vector3 target) {
var path = new NavMeshPath();
if(NavMesh.CalculatePath(transform.position, target, NavMesh.AllAreas, path)) {
foreach(var agent in agents) {
agent.SetPath(path);
}
}
}
对于可破坏场景,推荐组合使用:
csharp复制IEnumerator RebuildNavMeshAsync() {
var operation = NavMeshBuilder.UpdateNavMeshDataAsync(
navMeshData,
buildSettings,
sources,
new Bounds(center, size)
);
while(!operation.isDone) {
yield return null;
}
NavMesh.RemoveAllNavMeshData();
NavMesh.AddNavMeshData(navMeshData);
}
在RTS项目实践中,我发现最影响体验的往往是边缘情况处理——比如单位卡在狭窄通道时,可以临时调小Agent Radius通过后再恢复。另一个实用技巧是为近战单位设置比远程单位更高的避让优先级,这样前排会自然推开后排形成合理阵型。