Unity开发中的坑点记录

1,006 阅读3分钟

不定期更新

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

| 原文出自blog.csdn.net/H2ojunjun/a…

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.GetKeyDown("Jump"))
            {
                moveDirection.y = jumpSpeed;
            }
        }
        
        moveDirection.y -= gravity * Time.deltaTime;
        characterController.Move(moveDirection * Time.deltaTime);
    }
}

在运行过程中经常会出现按了跳跃的按键后人物不跳的情况。Debug一下会发现CharacterController.IsGround的值在true和false之间反复横跳, 更离谱的是就算游戏角色站在地面上动都没动,这个值都在反复横跳。所以按了按键偶尔跳得起来,偶尔跳不起来。

两种解决方案:

  • 将GetKeyDown改成GetKey, 因为GetKeyDown只在按键按下的一瞬间返回true, 后面的时间里都为false。而我们这里characterController.IsGrounded的值在true和false之间反复横跳, 没准你按下按键的那一帧刚好characterController.IsGrounded为false。而GetKey是按键按下的过程中都为true, 这样我们按键只要有几帧的时间都能在characterController.IsGround为true的那一帧跳起来。

  • 第二种办法就是要解决角色放在地面上不动characterController.IsGround一会为true, 一会儿为false的问题。研究发现,调用characterController.Move方法时传入的三维向量和这个有关系。

官方文档里有一句话:

在上一次移动期间CharacterController是否碰触到地面。

也就是说当我们调用CharacterController.Move()方法的时候, 传入的Vector3类型的数据对characterController.isGrounded有影响。推测可能是要用传入的三维向量来和当前接触平面的法线做一些运算, 如果我们传入的三维向量是零向量则该运算一定会使得characterController.isGrounded为false。而如果该三维向量在不规则得变化的话可能会导致characterController.isGrounded为false。

而在游戏运行期间帧率是在不停变化的, 会导致该向量在不规则得变化, 所以characterController.isGrounded的值就可能为false, 不信的话在Start方法中写Application.targetFrameRate = 60; 来控制游戏帧数恒定, 这时isGrounded的值就不会发生跳变了。但是我们不可能在不同的平台和设备统一帧率, 解决的办法就是如下所示:

public class ExampleClass : MonoBehaviour
{
    CharacterController characterController;
    
    public Vectoer3 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("Jump"))
            {
                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被置为零向量, 最后Move方法被调用,传入的值是(moveDirection + upwardVector) * Time.deltaTime的值, moveDirection是零向量, 但是upwardVector是上一帧的upwardVector, 两者相加后和上一帧传入的值一样, 所以传入Move方法的向量并没有发生不规则变化, 在之后的时间里也不会产生变化, characterController.isGrounded为true, 当我们按空格跳跃也能够模拟出重力效果。但是这个方法有一个问题,就是跳跃后传入的三维向量的y值回事跳跃结束时的y值, 不过这个也不怎么影响游戏效果, 如果要把它置为0的话又会发生isGrounded在true和false之间反复出现的现象。