寻路就是提供一个目标点,根据障碍物自动计算出一条最优的路径,Unity寻路使用的是A*算法。
寻路可分为动态寻路以及静态寻路两种。动态寻路就是障碍物的位置可以动态修改,而静态寻路表示障碍物永远都不会发生改变。
静态寻路的效率会更高。
//--设置寻路
参与寻路计算的游戏对象需要选中Navigation Static 复选框。
接着打开寻路烘培面板Window→AI→Navigation。
还可以设置一些信息。
Agent Radius 表示角色胶囊体的半径,Agent Height 表示胶囊体的高度,Max Slope 表示爬坡的最高坡度。
Generated Off Mesh Links用于设置角色落下或者跳起来没有连接在一起的两个点的高度和距离。
using UnityEngine; using UnityEngine.AI; public class NavMeshAgentTest : MonoBehaviour { private NavMeshAgent navMeshAgent; private void Start() { navMeshAgent = GetComponent<NavMeshAgent>(); } private void Update() { if (Input.GetMouseButton(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit[] hits = Physics.RaycastAll(ray); foreach (var hit in hits) { string name = hit.collider.gameObject.name; if (name.Contains("Plane") || name.Contains("Cube")) { navMeshAgent.SetDestination(hit.point); } } } } }写一个简单的控制角色寻路脚本,角色要挂上NavMeshAgent组件。
//--连接两点
寻路必须保证两点是能走过去的,但是设计上不一定是走过去,比如跳过去掉下去等。
寻路专门提供了一个OffMeshLink组件来处理两点之间的连接。
//--获取寻路路径
有时候,需要在寻路之前判断一下目标点是否合法,或者寻路的路径是否合法,此时就要提前获取寻路的完整路径。
using UnityEngine; using UnityEngine.AI; public class DrawNavMeshPath : MonoBehaviour { public NavMeshAgent navMeshAgent; public Transform target; private NavMeshPath path; private void Start() { path = new NavMeshPath(); NavMesh.CalculatePath(navMeshAgent.transform.position, target.position, NavMesh.AllAreas, path); } private void Update() { for (int i = 0; i < path.corners.Length - 1; i++) { Debug.DrawLine(path.corners[i], path.corners[i + 1], Color.red); } } }//--动态阻挡
寻路中很多元素是支持动态阻挡的,例如空气墙等。需要玩家经历指定事件后放行等。
给需要动态阻挡的游戏对象添加NavMeshObstacle组件。设置游戏对象的显示隐藏即可控制是否发生动态阻挡。
Carve属性,一旦选中表示这个对象支持动态烘培。(游戏对象显示和隐藏后行走面会重新烘培,别的触发方式不知)。
Move Threshold 表示移动多长的距离后启动动态烘培,Time To Stationary表示元素停止运动后多久标记为静止状态,
Carve Only Stationary表示元素是否需要移动。
我没有勾选 这个属性,只是控制动态阻挡的显示隐藏就满足需要了。
//--导出寻路网格信息
Unity的寻路是能满足客户端的,但是如果是网络游戏,服务器需要控制怪物寻找主角,此时就需要将寻路的网格信息导出来。
using System.IO; using System.Text; using UnityEngine; using UnityEngine.AI; using UnityEditor; public class ExportNavMeshInfo : MonoBehaviour { public int width; public int height; public int size; private void OnDrawGizmosSelected() { if (NavMesh.CalculateTriangulation().indices.Length > 0) { string scenePath = UnityEngine.SceneManagement.SceneManager.GetSceneAt(0).path; string sceneName = Path.GetFileName(scenePath); string filePath = Path.ChangeExtension(Path.Combine(Application.dataPath, sceneName), "txt"); if (File.Exists(filePath)) { File.Delete(filePath); } StringBuilder sb = new StringBuilder(); sb.AppendFormat("scene={0}", sceneName).AppendLine(); sb.AppendFormat("width={0}", width).AppendLine(); sb.AppendFormat("height={0}", height).AppendLine(); sb.AppendFormat("size={0}", size).AppendLine(); sb.Append("data={").AppendLine(); Gizmos.color = Color.yellow; Gizmos.DrawSphere(transform.position, 1); float widthHalf = (float)width / 2; float heightHalf = (float)height / 2; float sizeHalf = (float)size / 2; for (int i = 0; i < height; i++) { sb.Append("\t{"); Vector3 startPos = new Vector3(-widthHalf + sizeHalf, 0, -heightHalf + (i * size) + sizeHalf); for (int j = 0; j < width; j++) { Vector3 source = startPos + Vector3.right * size * j; NavMeshHit hit; Color color = Color.red; int a = 0; if (NavMesh.SamplePosition(source, out hit, 0.2f, NavMesh.AllAreas)) { color = Color.blue; a = 1; } sb.AppendFormat(j > 0 ? ",{0}" : "{0}", a); Debug.DrawRay(source, Vector3.up, color); } sb.Append("}").AppendLine(); } sb.Append("}").AppendLine(); Gizmos.DrawLine(new Vector3(-widthHalf, 0, -heightHalf), new Vector3(widthHalf, 0, -heightHalf)); Gizmos.DrawLine(new Vector3(widthHalf, 0, -heightHalf), new Vector3(widthHalf, 0, heightHalf)); Gizmos.DrawLine(new Vector3(widthHalf, 0, heightHalf), new Vector3(-widthHalf, 0, heightHalf)); Gizmos.DrawLine(new Vector3(-widthHalf, 0, heightHalf), new Vector3(-widthHalf, 0, -heightHalf)); File.WriteAllText(filePath, sb.ToString()); AssetDatabase.Refresh(); } } }