学习笔记 - 悬浮胶囊体,解决斜坡和上阶梯问题

前言:

此文章仅作本人学习笔记留存分享。如有出错欢迎指正。


首先展示效果如下:

斜坡常用解决方式:

计算出一个和斜坡坡面水平的向量,然后给刚体一个此方向的力。

http://t.csdnimg.cn/5LGt2

阶梯常用解决方式:

在前进方向一定范围向下打一条射线进行检测,如果碰撞到的点,和自己所在的点高度差超过设定的台阶高度,那么就认为前面有一个台阶
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);
        }
    }

}

脚本最终情况 (数值仅供参考)


这个技术的优缺点

优点:

  • 可以一次性解决斜坡和阶梯问题。

缺点:

  • 需要每一帧都设置胶囊体垂直方向的速度。
  • 因为胶囊体是悬空的,也就是说底部到地面的地方不会有碰撞。需要再添加额外的碰撞盒来填补其中的空白。

原文:

https://www.youtube.com/watch?v=xOKorIeVNrA&t=676s

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值