在 Unity 里做角色移动通常有两种方式:
- 设置
rigidbody.velocity
刚体速度,通过物理引擎进行位移。
- 设置
transform.position
来改变物体位置。
使用第1种方式通过刚体来运动的话,可以不用自行处理碰撞检测,但是对角色移动不容易做到精确控制。我倾向于使用第2种方式,直接修改 transform.position
来进行位移。这样的话,我们就需要处理碰撞检测的问题。
我目前采用的移动逻辑是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| [RequireComponent(typeof(CapsuleCollider))] class CharacterMove : MonoBehaviour { float radius; float thickness = 0.05f; Vector3 localPoint1; Vector3 Point1 => transform.TransformPoint(localPoint1); Vector3 localPoint2; Vector3 Point2 => transform.TransformPoint(localPoint2); LayerMask layerMask;
void Awake() { var capsuleCollider = GetComponent<CapsuleCollider>(); radius = capsuleCollider.radius; float halfHeight = capsuleCollider.height * 0.5f; localPoint1 = capsuleCollider.center + (halfHeight - radius) * Vector3.up; localPoint2 = capsuleCollider.center + (halfHeight - radius) * Vector3.down; layerMask = LayerManager.playerMoveCastMask;
var state = GetComponent<Animator>().GetBehaviour<Character_State_Wander>(); state.SetCharacterMove(this); }
public void Move(Vector3 direciton, float distance) { while (distance > 0) { if (direciton == Vector3.zero) { break; }
direciton = direciton.normalized; bool hit = Physics.CapsuleCast(Point1, Point2, radius - thickness, direciton, out RaycastHit hitInfo, distance + thickness, layerMask); if (hit) { float actualDis = hitInfo.distance - thickness; transform.position += direciton * actualDis; Vector3 remain = (distance - actualDis) * direciton; Vector3 newMotion = Vector3.ProjectOnPlane(remain, hitInfo.normal); if (newMotion.sqrMagnitude < Mathf.Epsilon) { distance = 0; } else { direciton = newMotion.normalized; distance = newMotion.magnitude; } } else { transform.position += direciton * distance; distance = 0; } } } public void Move(Vector3 motion) { if (motion == Vector3.zero) { return; } Move(motion.normalized, motion.magnitude); } }
|
这个移动方式的特点是,在玩家碰撞到障碍物时,可以按原有移动向量在障碍物表面上的分量继续移动,不会卡住。
如果不使用 thickness
参数的话,在角色贴合碰撞体的时候,经常会出现检测不到碰撞的情况,也就会造成穿墙。使用 Thickness
参数相当于让碰撞体变小一点进行碰撞检测,然后在最终的位移距离上补偿回来。