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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM