补充:在胶囊碰撞体里的物体都会被检测到,因此当添加了诸如武器之类的碰撞盒时,应该将他们的layer设置为和角色本身一样的Layer,否则将会导致碰撞数量大于一个,而且即使脱离了地面,OverLarpCapsule返回碰撞数组也会>=1,导致浮空判断为真.或者你可以重新写关于数组的判断方法.
这个问题困扰了我几天,在我找到解决方案之前,尝试了以下方法
1.射线,在角色坐标(一般是脚底),发射一根向下的射线,长度大约为0.2,这样是的确可以检测到玩家是否在地面的,但只适用于简单地形,如果你配置好了下落动画,那么用这种方法在斜面上往下走的话就会导致着地状态检测为false,如果有动画的话,就会播放浮空动画..这是由于角色往前走了一步时,刚体还没来得及让玩家因重力而下降足够的高度,导致射线不能射到斜面.因此判false,加长射线长度可以缓解问题,但又会导致跳跃动画出现问题,(动画会提前着地).而且在经过脚下有缝隙的情况而角色胶囊体半径大于缝隙并不会掉下去的时也会导致着地状态判false;
2.unity官方角色控制器,简单的加上控制器并调用控制器脚本查询是否着地时无法达到想要的效果的,着地状态返回false,折腾了半天发现控制器只能在调用simplemove时(和move等移动函数)判断isGrounded(是否着地),而且播放一些动画会导致判断在true和false状态来回切换.并且Skinwidth也会导致这种问题.再加上一些角色控制器的限制,逻辑上不是那么自由.例如需要自己编写重力,因此我放弃了这个方法.
如果你有能用角色控制器达到正确的效果的方法的话,希望能告诉我,谢谢
最终使用的方法是投射一个胶囊碰撞体来检测地面
方法来自这个视频,视频作者并不是我:视频链接
原理:投射一个和角色本身胶囊体碰撞器一样大小的胶囊碰撞器,(也可以不一样,知道原理后就会知道什么样的大小合适了)通过这个胶囊体能比较准确的检测着地状态,再不是特别陡的坡和不是特别快的速度下都能比较准确的检测到.
API:
Physics.OverlapCapsule(pointBottom, pointTop, radius, LayerMask);
以pointBottom为底部半圆圆心,PointTop为顶部半圆圆心,radius为半径,连接起来构成一个胶囊体。radius从玩家碰撞体获取,不过可以设置的小一点来避免侧面碰撞,一般设置为radius*0.9左右。
值得注意的是:LayerMask的设置方法,假设ground层为10,那么这里如果要只碰撞第10层Layer,那么Layermask mask=1<<10;这是位运算.按这种格式写就行了.
然而,投射的胶囊体也会检测自己本身,而再游戏中也希望基本上任何能碰撞物体都能够用来站脚,所以实际上我们要做的应该是碰撞除了用户本身Layer以外的所有层.假设Player层为8
写出来的话就是:~(1<<8)
~可以理解为位运算的取反符号
注意
关于层级的三种写法:
第一种:
LayerMask.GetMask("Player")是获取玩家层级并且经过位运算后的层级,所以不能写成
~(1<<LayerMask.GetMask("Player"))
这样并不会忽略玩家层级,获取到的还是玩家层级。应该写成下面这样才能忽略玩家层级。
LayerMask ignoreMask = ~LayerMask.GetMask("Player");
第二种忽略玩家层级的正确写法(玩家层级为8):
LayerMask ignoreMask = ~(1<<8);
删除~符号则为打开玩家层级。
第三种忽略玩家层级的正确写法:
LayerMask ignoreMask = ~(1<<LayerMask.NameToLayer("Player"));
要想忽略多个或打开多个层级可以使用 | 符号(键盘上顿号的英文符号),如(1 << 10) | (1 << 8)表示打开第10层和第8层,
~((1<<10)|(1<<8))表示忽略第10层和第8层。
总结:LayerMask.GetMask("Player")实际上相当于(1<<8),而LayerMask.NameToLayer("player")返回值为8,因此要注意两种函数的区别与写法。给LayerMask添加public修饰符的话还可以在Inspector面板中进行多选。
实际的代码:
private CapsuleCollider capsuleCollider;
private Vector3 pointBottom, pointTop;
private float radius;
public LayerMask ignoreLayer;
void Awake () {
capsuleCollider = GetComponent<CapsuleCollider>();
radius = capsuleCollider.radius*0.9f;
}
bool OnGround()
{
pointBottom = transform.position + transform.up * radius-transform.up*overLapCapsuleOffset;
pointTop = transform.position + transform.up * capsuleCollider.height - transform.up * radius;
LayerMask ignoreMask = ~ignoreLayer;
colliders = Physics.OverlapCapsule(pointBottom, pointTop, radius, ignoreMask);
Debug.DrawLine(pointBottom, pointTop,Color.green);
if (colliders.Length!=0)
{
isOnGround = true;
return true;
}
else
{
isOnGround = false;
return false;
}
}
注意玩家坐标的位置!overlapCapsuleOffset代表将胶囊体的下半球下移一定距离,如果在玩家中心,玩家高度为2的话,overlapCapsuleOffset则需要设置为1.1f,而如果坐标在玩家脚底则应该设置为0.1f左右。今天用自己博客里的检测方法半天检测不到地面才反应过来,赶紧来补充一下。
另外: 为避免角色在下陡坡时直接浮空,应该增加一个时间变量来计算浮空时间,当达到一定时间后才播放浮空动画.同时也能避免角色在经过一个小坡后离地面只有几厘米时直接播放浮空动画的不合理情况.