Unity3D 之跳跃判断

一般来说,只有接地的角色才能跳起来,否则玩家可以一直隔空跳直到飞出地图。所以这将涉及到如何确定什么时候人物在地面上。主流的解决方案一般有两个:

  • 一个是使用碰撞检测
  • 另一个是使用射线

为了更直观的展示,一个简单的原理图1-1, 将辅助介绍。


图1-1

在碰撞方法中,两个UnityAPI将被用来检测碰撞:Collider.OnCollisionEnter(collision)和Collider.OnCollisionExit(collision);

原理是设置一个布尔变量OnCollisionGrounded,当球接触到该对象时,将变量设置为true,当球离开该对象时,将变量设置为false:

 void OnCollisionEnter(Collision col) //可改为 OnCollisionStay
    {
        OnCollisionGrounded = true;
    }
    void OnCollisionExit(Collision col)
    {
        OnCollisionGrounded = false;
    }


该方法如图1-1中的虚线圆圈所示,绿色圆圈代表true,表示已着陆;红色圆圈代表false,表示未着陆。这个方法可以有效地运行当且仅当角色在绝对平坦的地面, 当表面不是平的,或者当角色有一点离开地面甚至只是一个很小的距离,物理引擎也将判定没有接地,这导致了玩家在平地运动时不能按他们所想的及时跳起来,如图1-1中的案例2所示。

在实际的用户体验反馈中,当玩家在复杂地形中有多个碰撞点时,这种方法有可能失败,将OnCollisionEnter改为OnCollisionStay可以避免这种情况,但仍然无法避免上述问题。


射线检测方法将得到更好的效果,该方法调用射线检测接口(Unity - Scripting API: Physics.Raycast (unity3d.com)),从玩家球体的几何重心竖直向下发送一条有距离的射线,如果射线在该距离内接触到物体,那么将返回true,否则将返回false,在图中用竖直线条表示,这样的好处时可以将射线超出球体半径一定距离(jumpThreshold),从而让玩家能够在球体小幅颠簸时也能够顺利起跳,而且这个方法一般不会出现意想不到的bug。

 public bool onTheGround()
    {
        Ray checkGround = new Ray(transform.position, Vector3.down);
        RaycastHit hit;
        Color rayColor;
        if (Physics.Raycast(checkGround, out hit, jumpThreshold))
        {
            Grounded = true;
            rayColor = Color.green;
            //Debug.Log("i'm grounded");
        }
        else
        {
            Grounded = false;
            rayColor = Color.red;
            //Debug.Log("not grounded");
        }
        Debug.DrawRay(transform.position, Vector3.down, rayColor, jumpThreshold);

        return Grounded;
    }

但是该方法却又有另外一个问题,当玩家在物体边缘起跳时,虽然球体是接触地面的,但是由于射线从球体中心发射出来,所以无法竖直投影到地面上,如图1-1 Case4所示,这导致玩家在地形的边缘或者有缝隙的情况下无法起跳。

图1-1

所以最终,我选择了将以上两种方案结合起来一起判断。首先先用射线检测,如果射线检测返回true, 那么此时球体下方一定踩着一个物体;当射线检测返回false时,再用碰撞检测判断,这将弥补射线检测忽略的地方。这样一来只要有一个方法返回true,那么球体就一定接触地面,反之如果二者都为false,那么就一定无法起跳:

   void OnCollisionStay(Collision col)
    {
        OnCollisionGrounded = true;
    }
    void OnCollisionExit(Collision col)
    {
        OnCollisionGrounded = false;
    }
 
public bool onTheGround()//a combination of touch and raycast
    {
        bool isRayGrounded = Physics.Raycast(transform.position, Vector3.down, jumpThreshold);
        Color rayColor;
        if (isRayGrounded ||( !isRayGrounded && OnCollisionGrounded))
        {
            combinedGrounded = true;
            rayColor = Color.green;
            //Debug.Log("i'm grounded");
        }
        else
        {
            combinedGrounded = false;
            rayColor = Color.red;
            //Debug.Log("not grounded");
        }
        Debug.DrawRay(transform.position, Vector3.down, rayColor, jumpThreshold);

        return combinedGrounded;
    }

但是该方法依然有问题,当玩家遇到竖直的地形和树木时,碰撞检测将返回true,此时玩家就可以不断跳跃,来达到蹬墙跳或者爬树的目的,如图1-1 Case3。

图1-1

解决方法是给玩家的跳跃增加冷却时间,这样玩家每次跳跃后将等待一段时间后才能再次跳跃。

    void JumpMethod()
    {

        if (JumpTime >= JumpcoolingTime)
        {
            JumpCommand();//allow the user to jump
            return;
        }
        JumpTime += Time.deltaTime;
        //fill the jump loading bar for user to see:
        fillValue = JumpTime / JumpcoolingTime;
        jumpPanel.fillAmount = fillValue;
    }
    void JumpCommand()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (!onTheGround()) { return; } //当玩家按下跳跃键再检测,这样省掉多余的检测和负担
  
            rg.AddForce(Vector3.up * jumpForce);
            JumpTime = 0;

            if (roomManager.isTrainingGround && keepSetting.showTutorial)
            {
                if (guidanceText.Goal == 3) { guidanceText.jumpRushMission += 1; }
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_46146935/article/details/124305309