【Unity3D Debug】跳跃时的地面检测:“CharacterController.isGrounded的值总是为false”的原因与解决方案
FPS控制器中处理跳跃时需要进行角色与地面的碰撞检测,如果使用Character Controller,会发现有一个isGrounded
属性,其描述如下:
isGrounded: Was the CharacterController touching the ground during the last move?
其实际意思是上一次调用CharacterController.Move
时有没有触地。
isGrounded
如果恒为false:其一,可能没有在判断其值之前调用Move函数;其二, 有可能是因为没有将其判断语句放在正确的位置,比如在Y轴方向的移动操作之前就进行该属性的判断,这时候可能还没有触地,像如下的代码就会导致isGrounded
恒为false。
// 根据curInput处理处理角色的真实移动
public void UpdateMove()
{
if (isDead) // 角色死亡
return;
float curSpeed = moveSpeed;
dealWithMoveSpeed(ref curSpeed); // 根据持有物确定移动速度
Vector3 v = transform.TransformDirection(curMoveXZInput); // 别忘了转为世界坐标系下的向量
// 处理XZ平面的移动
cc.Move(v * curSpeed * Time.deltaTime); // Move方法传入的向量解读为世界坐标系下的向量
// 处理重力与起跳
if (cc.isGrounded)
{
velocity.y = -2f; // 设为负数,确保在地面时能恒定接触地面,而不会误判为false
}
if(Input.GetKeyDown(KeyCode.Space) && cc.isGrounded)
{
// m*g*h = 0.5*m*v_y0^2 - 0 -> v_y0 = sqrt(2*g*h)
velocity.y = Mathf.Sqrt(-2f * GameManager.gravity * jumpHeight);
}
velocity.y += GameManager.gravity * Time.deltaTime; // dv = g*t
cc.Move(velocity * Time.deltaTime); // 处理y轴方向的移动
}
正确的做法是,把含有isGrounded
的判断语句放在施加重力以及Y轴方向的Move之后,如下代码所示(可以和上面的代码对比一下)。
// 根据curInput处理处理角色的真实移动
public void UpdateMove()
{
if (isDead) // 角色死亡
return;
float curSpeed = moveSpeed;
dealWithMoveSpeed(ref curSpeed); // 根据持有物确定移动速度
Vector3 v = transform.TransformDirection(curMoveXZInput); // 别忘了转为世界坐标系下的向量
// 处理XZ平面的移动
cc.Move(v * curSpeed * Time.deltaTime); // Move方法传入的向量解读为世界坐标系下的向量
// 处理重力与起跳
velocity.y += GameManager.gravity * Time.deltaTime; // dv = g*t
cc.Move(velocity * Time.deltaTime); // 处理y轴方向的移动
if (cc.isGrounded) // Code1
{
velocity.y = -2f; // 设为负数,确保在地面时能恒定接触地面,而不会误判为false
}
if(Input.GetKeyDown(KeyCode.Space) && cc.isGrounded)
{
// m*g*h = 0.5*m*v_y0^2 - 0 -> v_y0 = sqrt(2*g*h)
velocity.y = Mathf.Sqrt(-2f * GameManager.gravity * jumpHeight);
}
}
另外,观察上面的代码,可以发现:为了确保触地,当isGrounded
为true时,将Y轴方向的速度设为了一个较小的负值,这是为了确保在地面时能稳定触碰地面,避免isGrounded
误判为false。
最后,如果只需要角色与部分层进行碰撞检测,可以通过Edit → ightarrow → Project Settings → ightarrow → Physics → ightarrow → Layer Collision Matrix,即修改层的碰撞矩阵,确认角色层所在的行和列,勾选与之交互的层,并取消其他层即可。举个例子,对于FirstPerson层,我只要确保它与Ground层、UI层进行碰撞,则影响范围和相关勾选状态如下图红线所示。
笔者没有研究CharacterController.Move和.isGrounded的具体实现(Unity没有公开其源码),以上结论仅来自于笔者的个人实践,可能存在局限性。若存有谬误,欢迎大佬们批评指正!