2D平台游戏人物移动实现+手感优化

2D平台游戏人物移动实现+手感优化

  1. 目前实现的是人物左右移动、跳跃、蹲下和它们之间的动画切换。(以下代码分几个模块讲解,所以可能一个函数里面的内容比如GetInput里面的内容,在跳跃部分只展示和跳跃相关的)
  2. 不需要更改Project Setting
  3. 为人物添加组件RigidBody2D,CapsuleCollider2D等组件。
    a. 改变RigidBody2D的材质,新增一个Physics Material 2D材质,数值均设为0,解决卡墙问题。
    b. 更改RigidBody2D的Gravity Scale,让人物跳跃的时候能够下降的更快。

  • 整体逻辑

    代码方面,首先明确一点,键盘输入需要在Update中完成,而改变rigidbody属性等物理逻辑需要在FixedUpdate中完成。

    void Update()
    {
    	获取按键信息;
    }
    void FixedUpdate()
    {
    	物理状态检测;
    	蹲下、左右移动、跳跃等逻辑;
    	改变动画;
    }
    
        void Update()
        {
            //在Update中只处理按键输入
            GetInput();
        }
    
        private void FixedUpdate()
        {
            //在FixedUpdate中处理物理逻辑
            PhysicsCheck();
            Crouch();
            MoveOnGround();
            Jump();
            ChangeAnimator();
        }
    
  • 左右移动

    1. 获取按键信息

      需要注意的是GetAxis(-1~1之间)虽然会因为键盘输入不灵敏相比GetAxisRaw(只有-1、0、1三个值)移动的距离不那么平均,但是使用GetAxisRaw会导致我只是轻轻按了一下却移动了一大格,操作手感没那么舒服,看起来有点像漂移。

    2. 实现左右移动

      需要注意的点是xVelocity=0的时候也需要为rb.velocity赋值。不要写成当x.velocity ≠0时rb.velocity = …之类的代码,因为我们设置了人物的材质摩擦力为0,如果不在没有输入的时候为rb的速度赋值,那么它会沿着最后一次赋值的速度一直移动下去,所以没有输入的时候,我们也需要把速度赋值为0。

    3. 实现翻转

      需要注意的时xVelocity=0时不要不要翻转scale,否则会停下来的时候会一直朝向一个方向。

        private void GetInput()
        {
            xVelocity = Input.GetAxis("Horizontal");
            
        }
    	private void MoveOnGround()
        {
            rb.velocity = new Vector2(xVelocity * moveSpeed, rb.velocity.y);
            FlipX();
        }
        private void FlipX()
        {
            if(xVelocity < 0)
            {
                transform.localScale = new Vector3(-1, 1, 1);
            }
            if(xVelocity > 0)
            {
                transform.localScale = new Vector3(1, 1, 1);
            }
        }
    
  • 跳跃

    1. 获取按键:跳跃需要的是按键按下的那一瞬间,在update中能够获取GetButtonDown,但到物理帧更新的那一刻可能就是false了,这样会造成跳跃键按下但跳跃不发生,所以我们需要新增一个变量needJump保存该帧需要跳跃但是还没到物理帧去判断能否跳跃。
    2. 物理检测:需要检测是否在地面上,利用射线检测的方法实现。需要注意的一点是射线检测人物的脚底射线应该要比collider的size要更靠内一点,不然会把墙也识别为地板。这里我们人物的重心设置的是Pivot→Bottom,所以人物的position是脚底的中心。
    3. 物理实现:跳跃我们也用改变刚体的速度来实现,AddForce方法不太好控制,暂时不知道用改变速度的方法会不会存在什么隐患。
    4. 实现二段跳:新增一个变量canJump,该变量只有在跳第二次时赋值为false。
    5. 设置一个变量isJump,记录是否在跳跃状态,用于后面动画实现。
    	private void GetInput()
        {
            jumpPressed = Input.GetButtonDown("Jump");
            if(jumpPressed && !needJump)
            {
                needJump = true;    
            }
        }
    	private void PhysicsCheck()
        {
            leftFoot = new Vector2(transform.position.x - capsuleCollider.size.x / 2 +0.05f, transform.position.y);
            rightFoot = new Vector2(transform.position.x + capsuleCollider.size.x / 2 - 0.05f, transform.position.y);
            //检测是否在地面上 isOnGround
            RaycastHit2D leftCheck = Physics2D.Raycast(leftFoot, Vector2.down, footDistance, layerGround);
            RaycastHit2D rightCheck = Physics2D.Raycast(rightFoot, Vector2.down, footDistance, layerGround);
            isOnGround = leftCheck || rightCheck;
        }
    	private void Jump()
        {
            if (isOnGround && rb.velocity.y < 0)
            {
                canJump = true;
                isJump = false;
            }
            if (needJump)
            {
                if (isOnGround && !isCrouch)  //这里设置蹲下的时候不跳跃
                {
                    rb.velocity = new Vector2(rb.velocity.x, firstJumpForce);
                    isJump = true;
                }
                else if(canJump && !isCrouch)
                {
                    rb.velocity = new Vector2(rb.velocity.x, doubleJumpForce);
                    canJump = false;
                    isJump = true;
                }
                needJump = false; //已经进行过跳跃判断,把needJump设为false.
            }
        }
    
    
  • 蹲下

    1. 按键获取:GetButton
    2. 物理检测:检测头顶是否有物体,这里我们的头顶射线的出发点的y值一定要用原来碰撞体的高度减去射线的长度,这样射线检测刚好能检测到站起来的头顶。
    3. 物理实现:蹲下的时候设置isCrouch为真,站起时为假。只有处于蹲下状态并且没有按下蹲下键、可以站起的时候才能站起。
    	private void Start()
    	{
    	    originalSize = capsuleCollider.size;
            originalOffset = capsuleCollider.offset;
            crouchSize = new Vector2(originalSize.x, originalSize.y / 2);
            crouchOffset = new Vector2(originalOffset.x, originalOffset.y / 2);
    	}
    	private void GetInput()
        {
            crouchHeld = Input.GetButton("Crouch");
        }
    	private void PhysicsCheck()
        {
            headLeft = new Vector2(transform.position.x - originalSize.x / 2, transform.position.y + originalSize.y - headDistance);
            headRight = new Vector2(transform.position.x + originalSize.x / 2, transform.position.y + originalSize.y - headDistance);
            
            RaycastHit2D headLeftCheck = Physics2D.Raycast(headLeft, Vector2.up, headDistance, layerGround);
            RaycastHit2D headRightCheck = Physics2D.Raycast(headRight, Vector2.up, headDistance,layerGround);
            canStandUp = !headLeftCheck && !headRightCheck;
        }
    	private void CrouchDown()
        {
            capsuleCollider.size = crouchSize;
            capsuleCollider.offset = crouchOffset;
        }
        
        private void StandUp()
        {
            capsuleCollider.size = originalSize;
            capsuleCollider.offset = originalOffset;
        }
    
        private void Crouch()
        {
            if(crouchHeld && !isCrouch && !isJump)
            {
                isCrouch = true;
                CrouchDown();
            }
            if(isCrouch && !crouchHeld && canStandUp)
            {
                isCrouch = false;
                StandUp();
            }
        }
    		
    
  • 动画实现


    除了jump到idel有两个condition,其它都是一个。

    	private void ChangeAnimator()
        {
            if (isJump)
            {
                animator.SetBool("Jump", true);
            }
            else
            {
                animator.SetBool("Jump", false);
            }
            if (!isOnGround && rb.velocity.y < 0)
            {
                animator.SetBool("Jump", false);
                animator.SetBool("Fall", true);
                print("fall");
            }
            else
            {
                animator.SetBool("Fall",false);
            }
    
            if(xVelocity != 0) 
            {
                animator.SetBool("Run", true);
            }
            else
            {
                animator.SetBool("Run", false);
            }
    
            if (isCrouch)
            {
                animator.SetBool("Crouch", true);
            }
            else
            {
                animator.SetBool("Crouch", false);
            }
        }
    

【更新】
问题:在2D游戏中使用Tilemap制作地图时添加Tilemap Collider 2D后,人物脚底的射线检测是可以检测到地板的,而再Tilemap添加Composite Collider 2D后,射线检测检测不到地板了。
原因:Composite Collider 2D的属性Geometry type为Outlines,是空心的,射线检测正好漏过了边框。
解决办法:把它改为Polygons,实心的。

还可以通过在人物脚底加一个碰撞器的方法来检测人物是否在地面上,在碰撞器离开地面层的时候设置跳跃动画为true,在碰撞器接触地面层的时候设置跳跃动画为false。

posted @ 2022-11-07 10:39  三文咸鱼  阅读(354)  评论(0)    收藏  举报