Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景
Unity教程(十三)敌人状态机
如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节进行角色蹬墙跳的实现。
对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P37
一、概述
本节主要进行蹬墙跳的实现。
状态切换如下:
然后解决了一些小问题,比如落地时仍处于墙跳状态、冲刺时扒到另一个墙上会有延迟。但是视频里解决的几个小问题,有些我个人认为并不需要,会讲到怎么更改,但并不会放在最终代码中。
二、实现蹬墙跳
(1)创建PlayerWallJumpState蹬墙跳类
创建类PlayerWallJumpState,生成构造函数和重写。
在Player中创建PlayerWallJumpState,这里对应的条件变量名是Jump
#region 状态
public PlayerStateMachine StateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
public PlayerJumpState jumpState { get; private set; }
public PlayerAirState airState { get; private set; }
public PlayerDashState dashState { get; private set; }
public PlayerWallSlideState wallSlideState { get; private set; }
public PlayerWallJumpState wallJumpState { get; private set; }
#endregion
//创建对象
private void Awake()
{
StateMachine = new PlayerStateMachine();
idleState = new PlayerIdleState(StateMachine, this, "Idle");
moveState = new PlayerMoveState(StateMachine, this, "Move");
jumpState = new PlayerJumpState(StateMachine, this, "Jump");
airState = new PlayerAirState(StateMachine, this, "Jump");
dashState = new PlayerDashState(StateMachine, this, "Dash");
wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");
wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
}
(2)退出蹬墙跳
首先我们设置计时器,在计时器时间小于零时退出蹬墙跳。
//进入
public override void Enter()
{
base.Enter();
stateTimer = 1.0f;
}
//更新
public override void Update()
{
base.Update();
if (stateTimer < 0)
stateMachine.ChangeState(player.airState);
}
(3)设置蹬墙跳速度
因为我们是面向墙,向墙的反方向跳,所以蹬墙跳x方向速度设置为速度值乘角色面向的反方向。蹬墙跳y方向速度设置为jumpForce。
//进入
public override void Enter()
{
base.Enter();
stateTimer = 1.0f;
player.SetVelocity(5.0f * -player.facingDir, player.jumpForce);
}
(4)进入蹬墙跳
我们在滑墙状态中按下跳跃键时进入蹬墙跳。
public override void Update()
{
base.Update();
if(xInput!=0 && xInput!=player.facingDir)
stateMachine.ChangeState(player.airState);
if(Input.GetKeyDown(KeyCode.Space))
stateMachine.ChangeState(player.wallJumpState);
if(yInput<0)
rb.velocity = new Vector2(0, rb.velocity.y);
else
rb.velocity = new Vector2(0, rb.velocity.y * 0.7f);
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
但如果仅仅这样,我们会发现更改速度被打断,这是因为在本次循环中,即使已经切换了墙跳状态,但这一次循环没有结束,墙跳下面这一堆代码都会被执行一次,在设置滑墙速度的判断中x方向会被设置为0。
这里的解决方式,是切换蹬墙跳状态后加一个return
public override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.Space))
{
stateMachine.ChangeState(player.wallJumpState);
return;
}
if(xInput!=0 && xInput!=player.facingDir)
stateMachine.ChangeState(player.airState);
if(yInput<0)
rb.velocity = new Vector2(0, rb.velocity.y);
else
rb.velocity = new Vector2(0, rb.velocity.y * 0.7f);
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
更改后
三、解决一些小问题
(1)落地时仍处于墙跳状态
落地时由于冷却时间没有结束,角色仍处于墙跳状态。为了看得明显一点我先加大一下冷却时间。
解决这个问题只要为蹬墙跳的退出条件加上地面检测就可以了。
//更新
public override void Update()
{
base.Update();
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
if (stateTimer < 0)
stateMachine.ChangeState(player.airState);
}
(2)冲刺时扒到另一个墙上会有延迟
你会发现从墙上冲刺到另一面墙上有个很小的延迟,画面闪了一下才变成爬墙。
我们在冲刺状态中添加一个条件,在检测到墙壁且没有检测到地面时切换到滑墙。
//更新
public override void Update()
{
base.Update();
//切换滑墙状态
if(!player.isGroundDetected() && player.isWallDetected())
stateMachine.ChangeState(player.wallSlideState);
//设置冲刺速度
player.SetVelocity(player.dashDir * player.dashSpeed, 0);
//切换到空闲状态
if (stateTimer < 0)
stateMachine.ChangeState(player.idleState);
}
后面的两个问题我个人觉得不需要解决,解决完了反而觉得有点奇怪。我把视频讲解放在下面,如果需要可以更改。
(3)对墙冲刺(可不解决)
要解决这个问题,可以添加以下代码:
这里记得把这个墙体检测放在计数器后面,不然在冲刺触壁时冷却时间会没法减少
//检查冲刺输入
public void CheckForDashInput()
{
dashUsageTimer -= Time.deltaTime;
if (isWallDetected())
return;
if (Input.GetKeyDown(KeyCode.LeftShift) && dashUsageTimer<0)
{
dashUsageTimer = dashCoolDown;
dashDir = Input.GetAxisRaw("Horizontal");
if (dashDir == 0)
dashDir = facingDir;
StateMachine.ChangeState(dashState);
}
}
(4)对墙奔跑(可不解决)
这里按照视频的把切换空闲状态的条件改为
//切换到空闲状态
if(xInput==0 || player.isWallDetected())
stateMachine.ChangeState(player.idleState);
会出现问题,空闲状态和移动状态会不停切换,因为会不停变为空闲状态然后检测到墙壁。
在空闲状态中添加以下条件可以解决
//更新
public override void Update()
{
base.Update();
//切换到移动状态
if(xInput!=0 && !(player.isWallDetected()&&xInput==player.facingDir))
stateMachine.ChangeState(player.moveState);
}
总结 完整代码
Player.cs
创建蹬墙跳状态
//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[Header("Move Info")]
public float moveSpeed = 8f;
public int facingDir { get; private set; } = 1;
private bool facingRight = true;
public float jumpForce = 12f;
[Header("Dash Info")]
[SerializeField] private float dashCoolDown;
private float dashUsageTimer;
public float dashSpeed=25f;
public float dashDuration=0.2f;
public float dashDir { get; private set; }
[Header("Collision Info")]
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckDistance;
[SerializeField] private Transform wallCheck;
[SerializeField] private float wallCheckDistance;
[SerializeField] private LayerMask whatIsGround;
#region 组件
public Animator anim { get; private set; }
public Rigidbody2D rb { get; private set; }
#endregion
#region 状态
public PlayerStateMachine StateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
public PlayerJumpState jumpState { get; private set; }
public PlayerAirState airState { get; private set; }
public PlayerDashState dashState { get; private set; }
public PlayerWallSlideState wallSlideState { get; private set; }
public PlayerWallJumpState wallJumpState { get; private set; }
#endregion
//创建对象
private void Awake()
{
StateMachine = new PlayerStateMachine();
idleState = new PlayerIdleState(StateMachine, this, "Idle");
moveState = new PlayerMoveState(StateMachine, this, "Move");
jumpState = new PlayerJumpState(StateMachine, this, "Jump");
airState = new PlayerAirState(StateMachine, this, "Jump");
dashState = new PlayerDashState(StateMachine, this, "Dash");
wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");
wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
}
// 设置初始状态
private void Start()
{
StateMachine.Initialize(idleState);
}
// 更新
private void Update()
{
StateMachine.currentState.Update();
CheckForDashInput();
}
//检查冲刺输入
public void CheckForDashInput()
{
dashUsageTimer -= Time.deltaTime;
if (Input.GetKeyDown(KeyCode.LeftShift) && dashUsageTimer<0)
{
dashUsageTimer = dashCoolDown;
dashDir = Input.GetAxisRaw("Horizontal");
if (dashDir == 0)
dashDir = facingDir;
StateMachine.ChangeState(dashState);
}
}
//设置速度
public void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);
FlipController(_xVelocity);
}
//翻转实现
public void Flip()
{
facingDir = -1 * facingDir;
facingRight = !facingRight;
transform.Rotate(0, 180, 0);
}
//翻转控制
public void FlipController(float _x)
{
if (_x > 0 && !facingRight)
Flip();
else if(_x < 0 && facingRight)
Flip();
}
//碰撞检测
public bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsGround);
public bool isWallDetected() => Physics2D.Raycast(wallCheck.position,Vector2.right * facingDir,wallCheckDistance,whatIsGround);
//绘制碰撞检测
private void OnDrawGizmos()
{
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x+ wallCheckDistance, wallCheck.position.y));
}
}
PlayerWallJumpState.cs
设置冷却时间,设置退出条件切换到空中或空闲状态
public class PlayerWallJumpState : PlayerState
{
//构造函数
public PlayerWallJumpState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
{
}
//进入
public override void Enter()
{
base.Enter();
stateTimer = 1.0f;
player.SetVelocity(5.0f * -player.facingDir, player.jumpForce);
}
//退出
public override void Exit()
{
base.Exit();
}
//更新
public override void Update()
{
base.Update();
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
if (stateTimer < 0)
stateMachine.ChangeState(player.airState);
}
}
PlayerWallSlideState.cs
进入蹬墙跳状态
//PlayerWallSlideState:滑墙状态
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class PlayerWallSlideState : PlayerState
{
public PlayerWallSlideState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if(Input.GetKeyDown(KeyCode.Space))
{
stateMachine.ChangeState(player.wallJumpState);
return;
}
if(xInput!=0 && xInput!=player.facingDir)
stateMachine.ChangeState(player.airState);
if(yInput<0)
rb.velocity = new Vector2(0, rb.velocity.y);
else
rb.velocity = new Vector2(0, rb.velocity.y * 0.7f);
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
}
PlayerDashState.cs
切换滑墙状态
//PlayerDashState:冲刺状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerDashState : PlayerState
{
//构造函数
public PlayerDashState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName) : base(_stateMachine, _player, _animBoolName)
{
}
//进入状态
public override void Enter()
{
base.Enter();
//设置冲刺持续时间
stateTimer = player.dashDuration;
}
//退出状态
public override void Exit()
{
base.Exit();
}
//更新
public override void Update()
{
base.Update();
//切换滑墙状态
if(!player.isGroundDetected() && player.isWallDetected())
stateMachine.ChangeState(player.wallSlideState);
//设置冲刺速度
player.SetVelocity(player.dashDir * player.dashSpeed, 0);
//切换到空闲状态
if (stateTimer < 0)
stateMachine.ChangeState(player.idleState);
}
}