前言:這節開始需要進行腳本編寫,這里會把代碼全部貼出來並加以注釋。代碼是在過程中逐步完善的,在每節最后會貼出較為完善的代碼。
一、角色地面移動
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;
}
}