Unity中CharacterController.IsGrounded的值在True和False中反复出现导致跳跃键失灵的原因及解决方案

Unity中CharacterController.IsGrounded的值在True和False中反复出现导致跳跃键失灵的原因及解决方案


网上有很多用CharacterController实现游戏角色移动和跳跃的功能的代码,举个例子:

public class ExampleClass : MonoBehaviour
{
    CharacterController characterController;

    public float speed = 6.0f;
    public float jumpSpeed = 8.0f;
    public float gravity = 20.0f;

    private Vector3 moveDirection = Vector3.zero;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
    }
    
    void Update()
    {
        if (characterController.isGrounded)
        {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
            moveDirection *= speed;
            if (Input.GetButton("Jump"))
            {
                moveDirection.y = jumpSpeed;
            }
        }
		moveDirection.y -= gravity * Time.deltaTime;
        
        characterController.Move(moveDirection * Time.deltaTime);
    }
}

Unity官方文档是用的Input.GetButton(“Jump”)的方式来触发跳跃,但通常我们都是通过Input.GetKeyDown按某个键实现跳跃功能,比如空格之类的,但是在运行的过程中经常会出现按了跳跃的按键后人物不跳的情况。

这时如果我们Debug一下就会发现CharacterController.IsGround的值在true和false之间反复横跳,更离谱的是就算游戏角色站在地面上动都没动这个值都在反复横跳,导致程序经常进不到下面的if 语句块中。所以按了按键偶尔跳得起来,偶尔跳不起来。

if (characterController.isGrounded)
{
    moveDirection = new Vector3(horizontal, 0, vertical);
    if (Input.GetKeyDown(KeyCode.Space))
    {
        moveDirection.y = jumpSpeed;
    }
}

解决的办法有两种,第一种比较简单,将GetKeyDown改成GetKey,因为GetKeyDown只在按键按下的一瞬间返回true,就算我们按着那个键不放它也只在按下的瞬间为true,后面的时间里都为false。而我们这里characterController.IsGrounded的值在true和false之间反复横跳,没准你按下按键的那一帧刚好characterController.IsGrounded为false,那不就相当于没按吗?而GetKey是按键按下的过程中都为true,这样我们按键只要有几帧的时间都能在characterController.IsGrounded为true的那么一帧跳起来。

第二种办法就是要解决角色放在地面上不动characterController.IsGrounded一会为true,一会儿为false的问题,经过我一天的研究,总算发现调用characterController.Move方法时传入的三维向量和这个有关系,注意characterController.isGrounded的官方文档中有一句话:

Was the CharacterController touching the ground during the last move?
在上一次移动期间CharacterController是否触碰到地面?

也就是说当我们调用CharacterController .Move()方法的时候,传入的Vector3类型的数据对characterController.isGrounded有影响,官方文档也没说到底咋回事。根据我在网上的资料推测可能是要用传入的三维向量来和当前接触平面的法线做一些运算,如果我们传入的三维向量是零向量则该运算一定会使得characterController.isGrounded为false。不信可以试试将characterController.Move(moveDirection * Time.deltaTime);改成CharacterController .Move(Vector3.zero)再Debug isGrounded的值,全为false!而如果该三维向量在不规则得变化的话可能会导致characterController.isGrounded为false,因为我们这里写了一句moveDirection.y -= gravity * Time.deltaTime;
在这里插入图片描述
而在游戏运行期间帧率是在不停变化的,会导致该向量在不规则得变化,所以characterController.isGrounded的值就可能为false,不信可以在Start方法中写Application.targetFrameRate = 60;来控制游戏帧数恒定,这时isGrounded的值就不会发生跳变了。但是我们不可能在不同的平台和设备都设置统一的帧率吧,解决的办法就是将该向量拆解为两个向量,见代码:

public class ExampleClass : MonoBehaviour
{
    CharacterController characterController;
    
	public Vector3 upwardVector;
    
    public float speed = 6.0f;
    public float jumpSpeed = 8.0f;
    public float gravity = 20.0f;

    private Vector3 moveDirection = Vector3.zero;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (characterController.isGrounded)
        {
            moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
            moveDirection *= speed;

            if (Input.GetKeyDown(KeyCode.Space))
            {
                upwardVector.y = jumpSpeed;
            }
        }
        else
        {        
        upwardVector.y -= gravity * Time.deltaTime;
        }

        characterController.Move((moveDirection+upwardVector) * Time.deltaTime);
    }
}

可以看到我声明了一个全局变量upwardVector,当characterController.isGrounded为false的时候才对这个向量的y减去重力。现在试想一下这个过程:游戏开始前把角色放在地上,运行游戏期间不要让其产生任何移动,游戏开始的第一帧characterController.isGrounded肯定为false(因为我们在判断characterController.isGrounded之前没有调用Move方法),然后upwardVector的y值变为一个负数,下一帧的时候characterController.isGrounded为true(因为当前游戏角色和地面有接触并且上一帧调用了Move方法且该方法是让角色向下移动),接着moveDirection 被置为零向量(我们没有按WASD也没有按空格),最后Move方法被调用,传入的值是(moveDirection +upwardVector)* Time.deltaTime的值,moveDirection是零向量,但是upwardVector是上一帧的upwardVector,两者相加后和上一帧传入的值一样,所以传入Move方法的向量并没有发生不规则变化,在之后的时间里也不会产生变化,characterController.isGrounded为true,当我们按空格跳跃也能够模拟出重力效果。但是这个方法有一个问题,就是跳跃后传入的三维向量的y值会是跳跃结束时的y值,不过这个也不怎么影响游戏效果,如果要把它置为0的话又会发生isGrounded在true和false之间反复出现的现象,所以我也没管它。
提醒一点Input.GetKeyDown一定要放在Update里而不是FiexedUpdate里,原因是什么我就不在此赘述了,各位可自行查阅。

  • 29
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值