Unity角色移动穿模问题求解(不使用NavMeshAgent)
角色斜坡/下坡移动穿透地形问题解决
角色带有Rigidbody和Capsule Collider组件,地形配备Terrain Collider,通过鼠标或触摸点击目标点移动,下坡或行走斜坡时会出现穿透地形的情况,如何在不使用NavMeshAgent的前提下解决这个问题?
原实现代码
PlayerController.cs
using UnityEngine; public class PlayerController : BaseController { protected override void Init() { base.Init(); WorldObjectType = Define.WorldObject.Player; Managers.Input.MouseAction -= OnMouseEvent; Managers.Input.MouseAction += OnMouseEvent; } protected override void UpdateMoving() { Vector3 dir = _destPos - transform.position; if (dir.magnitude < 0.1f) { State = Define.State.Idle; } else { float moveDist = Mathf.Clamp(5.0f * Time.deltaTime, 0, dir.magnitude); transform.position += dir.normalized * moveDist; transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime); } } private void OnMouseEvent(Define.MouseEvent evt) { OnMouseEvent_IdleRun(evt); } private void OnMouseEvent_IdleRun(Define.MouseEvent evt) { RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); bool raycastHit = Physics.Raycast(ray, out hit, 70.0f); switch (evt) { case Define.MouseEvent.PointerDown: if (raycastHit) { int layer = hit.transform.gameObject.layer; if (layer == (int)Define.Layer.Ground) { _destPos = hit.point; State = Define.State.Moving; } else { DeliverGameObject dgo = gameObject.GetComponent<DeliverGameObject>(); dgo.Selected = hit.transform.gameObject; } } break; case Define.MouseEvent.Press: if (raycastHit) _destPos = hit.point; break; } } }
BaseController.cs
using System.Collections; using UnityEngine; public abstract class BaseController : MonoBehaviour { [SerializeField] protected Vector3 _destPos; [SerializeField] protected Define.State _state = Define.State.Idle; [SerializeField] public GameObject _lockTarget; protected Rigidbody _rigidbody; public Define.WorldObject WorldObjectType { get; protected set; } = Define.WorldObject.Unknown; public virtual Define.State State { get { return _state; } set { _state = value; Animator anim = GetComponent<Animator>(); switch (_state) { case Define.State.Die: anim.CrossFade("DIE", 0.1f); break; case Define.State.Idle: anim.CrossFade("IDLE", 0.1f); break; case Define.State.Moving: anim.CrossFade("RUN", 0.1f); break; case Define.State.Rush: anim.CrossFade("RUSH", 0.1f); break; case Define.State.Attack: anim.CrossFade("ATTACK", 0.1f, -1, 0.0f); break; case Define.State.Skill: anim.CrossFade("SKILL", 0.1f, -1, 0.0f); break; case Define.State.Skill2: anim.CrossFade("SKILL2", 0.1f, -1, 0.0f); break; case Define.State.Jump: anim.CrossFade("JUMP", 0.1f); break; case Define.State.KnockBackCreeper: anim.CrossFade("RUSH", 0.1f); break; } } } private void Start() { Init(); } private void Update() { switch (State) { case Define.State.Die: UpdateDie(); break; case Define.State.Moving: UpdateMoving(); break; case Define.State.Idle: UpdateIdle(); break; case Define.State.Rush: UpdateRush(); break; case Define.State.Attack: UpdateAttack(); break; case Define.State.Skill: UpdateSkill(); break; case Define.State.Skill2: UpdateSkill2(); break; case Define.State.KnockBackCreeper: UpdateKnockBackCreeper(); break; } } protected virtual void Init() { _rigidbody = gameObject.GetComponent<Rigidbody>(); _rigidbody.constraints = RigidbodyConstraints.FreezeAll; } protected virtual void UpdateDie() { } protected virtual void UpdateMoving() { } protected virtual void UpdateIdle() { } protected virtual void UpdateRush() { } protected virtual void UpdateAttack() { } protected virtual void UpdateSkill() { } protected virtual void UpdateSkill2() { } protected virtual void UpdateKnockBackCreeper() { } protected IEnumerator Despawn(GameObject gameObject, float animPlayTime) { yield return new WaitForSeconds(animPlayTime); Managers.Game.Despawn(gameObject); } }
问题分析与解决方案
原代码直接修改transform.position移动角色,忽略了地形高度变化,导致下坡时角色位置穿透地形。可通过射线检测修正移动后的位置,确保角色始终贴合地形表面。
修改后的PlayerController中UpdateMoving函数如下:
protected override void UpdateMoving() { Vector3 dir = _destPos - transform.position; if (dir.magnitude < 0.1f) { State = Define.State.Idle; } else { RaycastHit hit; float moveDist = Mathf.Clamp(5.0f * Time.deltaTime, 0, dir.magnitude); Vector3 rayStart = transform.position + Vector3.up; Vector3 rayDir = (transform.position + dir.normalized * moveDist) - rayStart; bool raycastHit = Physics.Raycast(rayStart, rayDir, out hit, 70f, _mask); Debug.DrawRay(rayStart, rayDir * 5, Color.blue, 1.0f); if (raycastHit) { transform.position = hit.point; transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 20 * Time.deltaTime); } } }
方案说明
- 从角色位置上方发射射线,检测移动路径终点的地形位置
- 将角色位置设置为射线命中的地形点,确保角色始终贴合地形表面
- 保留原有的朝向平滑过渡逻辑,保证移动时的视觉流畅性
内容的提问来源于stack exchange,提问作者Adam_WY_Jeong
相关产品推荐
相关产品推荐

