Unity横版2D游戏学习实例(03)- 角色移动跳跃下蹲&射线检测判碰撞

前言:这节开始需要进行脚本编写,这里会把代码全部贴出来并加以注释。代码是在过程中逐步完善的,在每节最后会贴出较为完善的代码。

 

一、角色地面移动

1. 首先在Project -> Asset中创建两个文件夹 Scripts -> Player,在Player文件中创建一个C#脚本PlayerControl,双击打开。

 

2. 实现角色移动和跳跃

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    [Header("速度")]
    public float speed = 10.0f;

    [Header("跳跃")]
    public float jumpForce = 10.0f;

    private bool isJumpPressed;

    private Rigidbody2D player_Rbody;
    private float xVelocity;

    private readonly string Key_Horizontal = "Horizontal";
    private readonly string Key_Jump = "Jump";

    void Start()
    {
        //获取玩家刚体和碰撞体
        player_Rbody = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        //按下跳跃键 空格
        if (Input.GetButtonDown(Key_Jump))
        {
            isJumpPressed = true;
        }
    }

    void FixedUpdate()
    {
        KeyHeldCheck();//检测持续按键事件

        GroundMovement();//移动
        MidAirMovement();//跳跃
    }

    private void KeyHeldCheck()
    {
        xVelocity = Input.GetAxis(Key_Horizontal);
    }

    private void GroundMovement()
    {
        //移动
        player_Rbody.velocity = new Vector2(speed * xVelocity, player_Rbody.velocity.y);

        //转向
        if (xVelocity != 0)
            transform.localScale = new Vector3(xVelocity / Mathf.Abs(xVelocity), 1, 1);
    }

    private void MidAirMovement()
    {
        if (isJumpPressed)
        {
            isJumpPressed = false;
            player_Rbody.AddForce(new Vector2(0, jumpForce), ForceMode2D.Impulse);//对角色刚体添加纵向的力
        }
    }
}


3.拖拽PlayerControl脚本挂载到Player下(Inspector窗口中)

运行游戏后,按A键D键和Space键,就能分别控制角色左右移动和跳跃了。

如果跳跃幅度觉得有问题,可以在Player的Inspector窗口调节以下参数来达到你觉得舒服的手感
Mass:质量  Gravity Scale:重力比例
Speed:代码中定义的速度  Jump Force:代码中定义的跳跃力

 

二、射线检测Physics2D.Raycast 判断地面

Physics2D.Raycast官方文档:https://docs.unity.cn/cn/2021.2/ScriptReference/Physics2D.Raycast.html

虽然角色能跑能跳了,但我们会发现不断按下跳跃角色会一直上升,这就需要判断角色只有在地面时才允许跳跃。这里使用的是射线检测方式来判断角色是否站在地面上。

 

1.设置Layer

游戏中的碰撞体是否会发生碰撞是由碰撞体的Layer判断的。打开Edit -> Project Setting -> Physics2D -> Layer Collision Matrix,我们可以看到碰撞体之间的碰撞关系。

 

每次新添加的游戏对象,它的Layer是默认的Default,而在Layer Collision Matrix中Default <-> Default是设定为发生碰撞,所以这就是为什么我们没有设定,加入场景的碰撞体总是能发生碰撞。

 

这里我们需要给地面(Tilemap)添加一个Layer,这里命名Ground。(顺便可以给Player添加一个Player层)

 

2.添加射线检测判断地面的代码(这里只贴出射线检测部分,完整PlayerControl代码会在最后贴出来)

public LayerMask groundLayer;

private bool isOnGround;

private Vector2 playerStandSize;
private Vector2 playerStandOffset;

void Start()
{
  //初始化玩家站立状态碰撞体尺寸和位置修正
  playerStandSize = player_Coll.size;
  playerStandOffset = player_Coll.offset;
}

void FixedUpdate()
{
  RayCheck();//射线检测
}

private void RayCheck()
{
  float xOffset = playerStandSize.x / 2;
  float yOffset = -playerStandSize.y / 2 + playerStandOffset.y;
  //碰撞体左下角位置 (-xOffset, yOffset)
  RaycastHit2D leftFootCheck = Raycast(new Vector2(-xOffset, yOffset), Vector2.down, 0.2f, groundLayer);
  //碰撞体右下角位置 (xOffset, yOffset)
  RaycastHit2D rightFootCheck = Raycast(new Vector2(xOffset, yOffset), Vector2.down, 0.2f, groundLayer);

  isOnGround = leftFootCheck || rightFootCheck;
}

private RaycastHit2D Raycast(Vector2 offset, Vector2 rayDirection, float rayLength, LayerMask layer)
{
  Vector2 pos = transform.position;//角色轴心位置
  Vector2 startPos = pos + offset;//修正后射线起点的位置
  RaycastHit2D ray = Physics2D.Raycast(startPos, rayDirection, rayLength, layer);
  //在屏幕中绘制出射线,方便观察调试。 红色:射线接触了layer,绿色:射线没接触layer
  Debug.DrawRay(startPos, rayLength * rayDirection, ray ? Color.red : Color.green);

  return ray;
}

private void MidAirMovement()
{
  if (isJumpPressed && isOnGround)
  {
    isJumpPressed = false;
    player_Rbody.AddForce(new Vector2(0, jumpForce), ForceMode2D.Impulse);//对角色刚体添加纵向的力
  }
}

:记得设置groundLayer为 Ground

 

再次运行游戏后,就能看见角色左脚和右脚的射线,并且在空中重复跳跃的问题得到解决

 

三、角色下蹲

在地图上绘制一个障碍物,因为角色和障碍物的碰撞体发生碰撞,我们会发现角色无法前进,于是需要实现下蹲功能让角色能通过障碍物。这里通过按住下蹲键时减小角色的碰撞体来实现下蹲。

 

 1. 修改按键

Unity没有预置单独的下蹲键,因为Vertical在按上和下都会响应事件,所以我们需要自定义下蹲键。

在工具栏Edit -> Project Settings -> Input Manager -> Axes,鼠标右击一个按键(如:Jump),选择Duplicate复制一个键位

 

2. 修改键值Name为Crouch(下蹲),Positive Button 或 Alt Positive Button为S键,这样讲就能在代码中

(没有直接使用KeyDown("s"),是因为增加下蹲键可以实现玩家在游戏中自己修改键位功能)

 

3. 实现下蹲功能 

private bool isCrouch;

private Vector2 playerStandSize;
private Vector2 playerStandOffset;
private Vector2 playerCrouchSize;
private Vector2 playerCrouchOffsize;

private readonly string Key_Crouch = "Crouch";

void Start()
{
  //获取玩家碰撞体
  player_Coll = GetComponent<BoxCollider2D>();

  //初始化玩家站立状态和下蹲状态的碰撞体尺寸和位置修正
  playerStandSize = player_Coll.size;
  playerStandOffset = player_Coll.offset;
  playerCrouchSize = new Vector2(playerStandSize.x, playerStandSize.y / 2);
  playerCrouchOffsize = new Vector2(playerStandOffset.x, playerStandOffset.y - playerStandSize.y / 4);
}

void FixedUpdate()
{
  KeyHeldCheck();//检测持续按键事件
  Crouch();//下蹲
}

private void KeyHeldCheck()
{
  isCrouch = Input.GetButton(Key_Crouch);//按住下蹲键S
}

private void Crouch()
{
  if (isCrouch)
  {
    //按住下蹲键时,改变碰撞体尺寸
    player_Coll.size = playerCrouchSize;
    player_Coll.offset = playerCrouchOffsize;
  }
  else
  {
    //放开下蹲键时,恢复碰撞体尺寸
    player_Coll.size = playerStandSize;
    player_Coll.offset = playerStandOffset;
  }
}

 

运行游戏后按下S+D,就能看到角色能顺利通过障碍物了。

 

4.添加射线检测判断头顶是否有障碍物

虽然角色可以顺利通过障碍物了,当我们会发现,在障碍物中如果松开下蹲键,角色的碰撞体就会穿在地面或障碍物中。这里就需要添加头部检测,判断角色头上有障碍物时不能站起。

private bool isHeadBlocked;
private readonly string Key_Crouch = "Crouch";

void FixedUpdate()
{
  KeyHeldCheck();//检测持续按键事件
  RayCheck();//射线检测

  Crouch();//下蹲
}

private void KeyHeldCheck()
{
  isCrouch = Input.GetButton(Key_Crouch);//按住下蹲键S
}

private void RayCheck()
{
  float headYOffset = player_Coll.size.y / 2 + player_Coll.offset.y;
  //碰撞体左上角 (-xOffset, headYOffset)
  RaycastHit2D headLeftCheck = Raycast(new Vector2(-xOffset, headYOffset), Vector2.up, playerStandSize.y / 2, groundLayer);
  //碰撞体右上角 (xOffset, headYOffset)
  RaycastHit2D headRightCheck = Raycast(new Vector2(xOffset, headYOffset), Vector2.up, playerStandSize.y / 2, groundLayer);

  isHeadBlocked = headLeftCheck || headRightCheck;
}

private void Crouch()
{
  if (isCrouch)
  {
    //按住下蹲键时,改变碰撞体尺寸
    player_Coll.size = playerCrouchSize;
    player_Coll.offset = playerCrouchOffsize;
  }
  else if (!isHeadBlocked)
  {
    //放开下蹲键时,恢复碰撞体尺寸
    player_Coll.size = playerStandSize;
    player_Coll.offset = playerStandOffset;
  }
}

 

这样一来,角色就可以顺滑地下蹲通过障碍物了

4. 下蹲通过障碍物时角色仍是站立状态,下节将加入动画,使角色在跑跳下蹲时有相应动作,就能解决该问题了。

 

四、总结

1.update和FixedUpdate的区别分析以及实际场景的使用建议,解析把跳跃放在FixedUpdate中会失效的原因

https://www.cnblogs.com/rkmao/p/15715494.html

2、角色控制完整代码

using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    [Header("速度")]
    public float speed = 10.0f;

    [Header("跳跃")]
    public float jumpForce = 10.0f;

    [Header("环境")]
    public LayerMask groundLayer;

    private bool isJumpPressed;
    private bool isCrouch;
    private bool isOnGround;
    private bool isHeadBlocked;

    private Rigidbody2D player_Rbody;
    private BoxCollider2D player_Coll;
    private float xVelocity;

    //记录玩家站立和蹲下时碰撞体
    private Vector2 playerStandSize;
    private Vector2 playerStandOffset;
    private Vector2 playerCrouchSize;
    private Vector2 playerCrouchOffsize;

    private readonly string Key_Horizontal = "Horizontal";
    private readonly string Key_Jump = "Jump";
    private readonly string Key_Crouch = "Crouch";

    void Start()
    {
        //获取玩家刚体和碰撞体
        player_Rbody = GetComponent<Rigidbody2D>();
        player_Coll = GetComponent<BoxCollider2D>();

        //初始化玩家站立状态和下蹲状态的碰撞体尺寸和位置修正
        playerStandSize = player_Coll.size;
        playerStandOffset = player_Coll.offset;
        playerCrouchSize = new Vector2(playerStandSize.x, playerStandSize.y / 2);
        playerCrouchOffsize = new Vector2(playerStandOffset.x, playerStandOffset.y - playerStandSize.y / 4);
    }

    void Update()
    {
        //按下跳跃键 空格
        if (Input.GetButtonDown(Key_Jump))
        {
            isJumpPressed = true;
        }
    }

    void FixedUpdate()
    {
        KeyHeldCheck();//检测持续按键事件
        RayCheck();//射线检测

        GroundMovement();//移动
        MidAirMovement();//跳跃
        Crouch();//下蹲
    }

    private void KeyHeldCheck()
    {
        xVelocity = Input.GetAxis(Key_Horizontal);
        isCrouch = Input.GetButton(Key_Crouch);//按住下蹲键S
    }

    private void GroundMovement()
    {
        //移动
        player_Rbody.velocity = new Vector2(speed * xVelocity, player_Rbody.velocity.y);

        //转向
        if (xVelocity != 0)
            transform.localScale = new Vector3(xVelocity / Mathf.Abs(xVelocity), 1, 1);
    }

    private void MidAirMovement()
    {
        if (isJumpPressed && isOnGround)
        {
            isJumpPressed = false;
            player_Rbody.AddForce(new Vector2(0, jumpForce), ForceMode2D.Impulse);//对角色刚体添加纵向的力
        }
    }

    private void Crouch()
    {
        if (isCrouch)
        {
            //按住下蹲键时,改变碰撞体尺寸
            player_Coll.size = playerCrouchSize;
            player_Coll.offset = playerCrouchOffsize;
        }
        else if (!isHeadBlocked)
        {
            //放开下蹲键时,恢复碰撞体尺寸
            player_Coll.size = playerStandSize;
            player_Coll.offset = playerStandOffset;
        }
    }

    private void RayCheck()
    {
        float xOffset = playerStandSize.x / 2;
        float yOffset = -playerStandSize.y / 2 + playerStandOffset.y;
        //碰撞体左下角位置 (-xOffset, yOffset)
        RaycastHit2D leftFootCheck = Raycast(new Vector2(-xOffset, yOffset), Vector2.down, 0.2f, groundLayer);
        //碰撞体右下角位置 (xOffset, yOffset)
        RaycastHit2D rightFootCheck = Raycast(new Vector2(xOffset, yOffset), Vector2.down, 0.2f, groundLayer);

        isOnGround = leftFootCheck || rightFootCheck;

        float headYOffset = player_Coll.size.y / 2 + player_Coll.offset.y;
        //碰撞体左上角 (-xOffset, headYOffset)
        RaycastHit2D headLeftCheck = Raycast(new Vector2(-xOffset, headYOffset), Vector2.up, playerStandSize.y / 2, groundLayer);
        //碰撞体右上角 (xOffset, headYOffset)
        RaycastHit2D headRightCheck = Raycast(new Vector2(xOffset, headYOffset), Vector2.up, playerStandSize.y / 2, groundLayer);

        isHeadBlocked = headLeftCheck || headRightCheck;
    }

    private RaycastHit2D Raycast(Vector2 offset, Vector2 rayDirection, float rayLength, LayerMask layer)
    {
        Vector2 pos = transform.position;//角色轴心位置
        Vector2 startPos = pos + offset;//修正后射线起点的位置
        RaycastHit2D ray = Physics2D.Raycast(startPos, rayDirection, rayLength, layer);
        //在屏幕中绘制出射线,方便观察调试。 红色:射线接触了layer,绿色:射线没接触layer
        Debug.DrawRay(startPos, rayLength * rayDirection, ray ? Color.red : Color.green);

        return ray;
    }

}

 

Unity横版2D游戏学习实例(04)- 为角色添加动画&状态机&Blend Tree

posted @ 2021-12-26 13:34  rkmao  阅读(2036)  评论(0编辑  收藏  举报