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;
}
}
本文来自博客园,作者:rkmao,转载请注明原文链接:https://www.cnblogs.com/rkmao/p/15721779.html