前言:
此文章仅作本人学习笔记留存分享。如有出错欢迎指正。
首先展示效果如下:
斜坡常用解决方式:
计算出一个和斜坡坡面水平的向量,然后给刚体一个此方向的力。
阶梯常用解决方式:
在前进方向一定范围向下打一条射线进行检测,如果碰撞到的点,和自己所在的点高度差超过设定的台阶高度,那么就认为前面有一个台阶
https://zhuanlan.zhihu.com/p/566988703
悬浮胶囊体技术
简介:
简单来说,就是将胶囊碰撞体提高到离开地面一定高度,这样只要斜坡和阶梯不和胶囊体发生碰撞,那么胶囊体就可以顺利通过了。
如图中的胶囊碰撞体情况所示。(底下的矩形碰撞盒请忽略,是和此技术无关的)
实现:
一、实现胶囊体尺寸的自定义
胶囊体的CenterY、Radius、Height。这个三参数与CapsuleCollider中的同名参数类似,主要是定义胶囊体的尺寸和位置。
public class DefaultColliderData
{
[field: SerializeField]
public float Height { get; private set; } = 1.6f;
[field: SerializeField]
public float CenterY { get; private set; } = 0.8f;
[field: SerializeField]
public float Radius { get; private set; } = 0.12f;
}
获得并缓存胶囊碰撞体,且提供设置以上数据的方法。
public class CapsuleColliderData
{
public CapsuleCollider collider { get; private set; }
public Vector3 colliderCenterInLocalSpace { get; private set; }
public Vector3 colliderVerticalExtents { get; private set; }
public void Init(GameObject gameObject) {
if (this.collider != null) {
return;
}
this.collider = gameObject.GetComponent<CapsuleCollider>();
UpdateColliderData();
}
public void UpdateColliderData() {
this.colliderCenterInLocalSpace = this.collider.center;
this.colliderVerticalExtents = new Vector3(0f, this.collider.bounds.extents.y, 0f);
}
//设置胶囊体的半径
public void SetCapsuleColliderRaidus(float radius) {
this.collider.radius = radius;
}
//设置胶囊体的高
public void SetCapsuleColliderHeight(float height) {
this.collider.height = height;
}
//设置胶囊体的高
public void SetCapsuleColliderCenter(Vector3 newColliderCenter) {
this.collider.center = newColliderCenter;
}
}
SlopeData 数据类。
public class SlopeData {
[field: SerializeField][field: Range(0f, 1f)]
[field: Header("步长高度")]
[field:Tooltip("影响胶囊体底部距离顶部的高度,即影响可跨越的阶梯高度")]
public float StepHeightPercentage { get; private set; } = 0.25f;
[field: SerializeField][field: Range(0f, 5f)]
[field: Header("射线长度")]
[field: Tooltip("需要确保长度足以超过模型的底部")]
public float FloatRayDistance { get; private set; } = 2f;
[field: SerializeField]
[field: Range(0f, 100f)]
[field: Tooltip("提供悬浮力时需要的系数")]
public float StepReachForce { get; private set; } = 25f;
}
StepHeightPercentage :胶囊体底部距离地面的高度占整体高度的百分比。如下图中红框部分。
FloatRayDistance :从胶囊体中心向下发射的射线的长度。
StepReachForce :提供悬浮力时需要的系数,保证悬浮力够大,足以抵消重力。
public class CapsuleColliderUtility
{
public CapsuleColliderData capsuleColliderData { get; private set; }
[field: SerializeField]
public DefaultColliderData defaultColliderData { get; private set; }
[field: SerializeField]
public SlopeData slopeData { get; private set; }
public void Init(GameObject gameObject) {
if (this.capsuleColliderData != null) {
return;
}
this.capsuleColliderData = new CapsuleColliderData();
this.capsuleColliderData.Init(gameObject);
OnInit();
}
protected virtual void OnInit() {
}
//计算胶囊体尺寸
public void CalculateCapsuleColliderDimensions() {
//设置胶囊体半径
this.capsuleColliderData.SetCapsuleColliderRaidus(this.defaultColliderData.Radius);
//StepHeightPercentage是胶囊体高度减去的百分比
this.capsuleColliderData.SetCapsuleColliderHeight(this.defaultColliderData.Height * (1f-this.slopeData.StepHeightPercentage));
RecalculateCapsuleColliderCenter();
float halfColliderHeight = this.capsuleColliderData.collider.height / 2f;
//胶囊体的两端是半球体,当高度等于半径的两倍,就形成了一个球。
//所以要限制半径小于或等于高度的一半。可以自己创建一个Capsule出来,观察到当高度小于2倍的半径,高度值就没有了。
if (halfColliderHeight < this.capsuleColliderData.collider.radius) {
this.capsuleColliderData.SetCapsuleColliderRaidus(halfColliderHeight);
}
this.capsuleColliderData.UpdateColliderData();
}
//重新计算胶囊体的中心位置
void RecalculateCapsuleColliderCenter() {
float colliderHeightDifference = this.defaultColliderData.Height - this.capsuleColliderData.collider.height;
Vector3 newColliderCenter = new Vector3(0, this.defaultColliderData.CenterY + (colliderHeightDifference / 2f), 0);
this.capsuleColliderData.SetCapsuleColliderCenter(newColliderCenter);
}
}
二、添加悬浮力
从包围盒中心点,向下发射一条射线,计算出中心点距离地面的距离。会有三种情况。
- 当射线距离大于胶囊体中心位置时,也就是下图左一情况,这时需要一个向下的垂直力来将模型拉到地面。
- 当射线距离等于胶囊体中心位置时,也就是下图中间情况,这时不需要额外的垂直力。
- 当射线距离小于胶囊体中心位置时,也就是下图右一情况,这时需要一个向上的垂直力来让模型移动到地面以上。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public CapsuleColliderUtility colliderUtility;
public LayerMask GroundLayer;
Rigidbody rb;
//为了方便在不运行的时候也可以设置和观察到数据带来的变化
private void OnValidate() {
colliderUtility.Init(gameObject);
colliderUtility.CalculateCapsuleColliderDimensions();
}
private void Awake() {
rb = GetComponent<Rigidbody>();
colliderUtility.Init(gameObject);
colliderUtility.CalculateCapsuleColliderDimensions();
}
private void FixedUpdate() {
FloatCapsule();
}
private void FloatCapsule() {
//碰撞包围盒的中心点
Vector3 colliderCenterInWorldSpace = colliderUtility.CapsuleColliderData.Collider.bounds.center;
//从碰撞盒中心向下发射射线
Ray downRayForomColliderCenter = new Ray(colliderCenterInWorldSpace, Vector3.down);
//如果击中地面
if (Physics.Raycast(downRayForomColliderCenter, out RaycastHit hit, colliderUtility.SlopeData.FloatRayDistance, GroundLayer, QueryTriggerInteraction.Ignore)) {
//计算胶囊体中心和地面的距离
float disteanceToFloatingPoint = colliderUtility.CapsuleColliderData.ColliderCenterInLocalSpace.y * transform.localScale.y - hit.distance;
if (disteanceToFloatingPoint == 0f) {
return;
}
//计算需要添加的垂直速度
float amountToLife = disteanceToFloatingPoint * colliderUtility.SlopeData.StepReachForce - rb.velocity.y;
Vector3 liftForce = new Vector3(0f, amountToLife, 0f);
rb.AddForce(liftForce, ForceMode.VelocityChange);
}
}
}
脚本最终情况 (数值仅供参考)
这个技术的优缺点
优点:
- 可以一次性解决斜坡和阶梯问题。
缺点:
- 需要每一帧都设置胶囊体垂直方向的速度。
- 因为胶囊体是悬空的,也就是说底部到地面的地方不会有碰撞。需要再添加额外的碰撞盒来填补其中的空白。
原文: