使用NavMesh进行移动
效果
using UnityEngine; using UnityEngine.AI; public class NavMeshMoveControl : MonoBehaviour { private NavMeshAgent m_NavMeshAgent; private Animator m_Animator; public float m_StopDist = 0f; private Vector3 m_TargetPos = Vector3.zero; private Vector3 m_TargetForward = Vector3.zero; private bool m_CheckArriveTarget = false; void Awake() { m_NavMeshAgent = GetComponent<NavMeshAgent>(); m_Animator = GetComponent<Animator>(); } void Start() { m_NavMeshAgent.updateRotation = false; //自带的太慢了, 角速度设置的很大也很慢 m_Animator.SetBool("Grounded", true); } void OnEnable() { if ("running" == GetNavStatus()) { m_NavMeshAgent.Warp(m_TargetPos); //瞬移到目标位置 } } void OnDisable() { if (m_Animator != null) { m_Animator.logWarnings = false; } } void Update() { if (m_CheckArriveTarget && !m_NavMeshAgent.pathPending) { var tTransform = this.transform; tTransform.forward = Vector3.Slerp(tTransform.forward, m_TargetForward, Time.deltaTime * 10); if (m_NavMeshAgent.remainingDistance <= m_StopDist) { m_CheckArriveTarget = false; OnPlayStandAnim(); //m_NavMeshAgent.enabled = false; } } } public void NavToPosition(Vector3 toPos) { var myPos = transform.position; toPos.y = myPos.y; //y方向坐标不变 var distVec = toPos - myPos; if (distVec.sqrMagnitude <= 0) { Debug.LogWarning("pos not change"); return; } if (m_NavMeshAgent.enabled) { m_NavMeshAgent.enabled = false; //寻路过程中, 切换成新的寻路点, 如果不调用enable=false, 有惯性, 看着很奇怪 } m_NavMeshAgent.enabled = true; if (m_NavMeshAgent.isOnNavMesh) { m_TargetForward = distVec.normalized; m_TargetPos = toPos; m_CheckArriveTarget = true; m_NavMeshAgent.SetDestination(toPos); //在enable后调用 m_NavMeshAgent.isStopped = false; OnPlayMoveAnim(); } else { Debug.LogWarning($"{this.name} isOnNavMesh=false, can't SetDestination"); } } //当前寻路状态 public string GetNavStatus() { if (m_NavMeshAgent.isOnNavMesh && m_NavMeshAgent.remainingDistance > 0) { return m_NavMeshAgent.isStopped ? "paused" : "running"; } return "stopped"; } //暂停寻路 public void PauseNav() { if ("paused" == GetNavStatus()) return; if (m_NavMeshAgent.isOnNavMesh) { m_NavMeshAgent.isStopped = true; //暂停寻路 } OnPlayStandAnim(); } //强制停止(会清除寻路数据) public void StopNav() { if ("stopped" == GetNavStatus()) return; if (m_NavMeshAgent.hasPath) { m_NavMeshAgent.ResetPath(); //在disable前调用 //m_NavMeshAgent.enabled = false; } OnPlayStandAnim(); } public void PrintInfo() { Debug.Log($"onNavMesh:{m_NavMeshAgent.isOnNavMesh}, hasPath:{m_NavMeshAgent.hasPath}, pending:{m_NavMeshAgent.pathPending}, status:{m_NavMeshAgent.pathStatus}"); if (m_NavMeshAgent.isOnNavMesh) { Debug.Log($"remain:{m_NavMeshAgent.remainingDistance}, stopped:{m_NavMeshAgent.isStopped}"); } } protected virtual void OnPlayStandAnim() { m_Animator.SetFloat("MoveSpeed", 0); } protected virtual void OnPlayMoveAnim() { m_Animator.SetFloat("MoveSpeed", 1); } }
移动到点击位置代码
using UnityEngine; public class MouseClickMove : MonoBehaviour { void Update() { if (Input.GetMouseButtonDown(1)) { RaycastHit hit; var ray = Camera.main.ScreenPointToRay(Input.mousePosition); //摄像机方向发射1条射线 Debug.DrawRay(ray.origin, ray.direction * 20, Color.yellow); //画出这条射线 var maxDistance = 50; if (Physics.Raycast(ray, out hit, maxDistance)) //检测射线是否碰到地面 { Debug.Log($"hit pos: {hit.point}"); GetComponent<NavMeshMoveControl>().NavToPosition(hit.point); var clickPosGo = GameObject.Find("ClickPos"); if (clickPosGo) clickPosGo.transform.position = hit.point; } } } }
相机控制,相机跟随相关的参考这边
阴影实现 - 准备工作:场景中行走的角色 - yanghui01 - 博客园 (cnblogs.com)
NavMeshAgent使用的一些注意点
1) isStopped为true时, 调用SetDestination, 不会触发寻路, 此时hasPath为true, remainDist>0
2) 调用SetDestination开始寻路后,调用isStopped=true会暂停寻路, 此时hasPath还是true, remainDist>0;再把isStopped=false可以恢复寻路;
调用ResetPath, 才会使hasPath为false, remainDist为0
3) 调用SetDestination开始寻路后,调用ResetPath会停止寻路, 此时isStopped还是false,hasPath为false, remainDist为0
4) 调用SetDestination寻路到终点时,hasPath先变成false,remainDist要等几帧才会变成0,isStopped没设置过就还是false
5) enabled为false时, isOnNavMesh总是为false
几个状态:
1) 寻路中: isStopped为false, hasPath为true, remainDist>0
2) 寻路停止(无寻路数据): hasPath为false, remainDist为0, isStopped无要求
3) 寻路暂停(有寻路数据): hasPath为true, remainDist>0, isStopped为true
isOnNavMesh=true时才能调用的
1) isStopped
2) SetDestination, destination
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?