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编程制作类银河恶魔城游戏P36
一、概述
本节主要进行滑墙的实现。
要创建超级状态PlayerWallSlide滑墙状态进行实现,我们的图又进一步壮大了。
在这节里我们要完善墙体检测,创建滑墙动画,还有完成状态切换和速度设置。
二、创建滑墙动画
在创建动画之前我们先再添加两个高的平台方便测试。
然后我们创建动画playerWallSlide
层次面板中选中Animator,在Animation面板中创建动画
playerWallSlide精灵表标号60-62,采样率改为10
具体讲解见Unity教程(零)Unity和VS的使用相关内容
连接状态机,并添加过渡条件WallSlide,并修改过渡设置
添加bool型条件变量WallSlide,并连接过渡
Entry->playerWallSlide的过渡,加条件变量
playerWallSlide->Exit的过渡,加条件变量,并更改设置
三、滑墙的实现
(1)更换材质
首先我们要解决一个问题,角色会粘在墙上不会下滑。发生这种情况的原因是Unity的物理效果,角色与墙壁的摩擦力太大了,导致角色无法下滑
创建文件夹Materials,在其中创建2D材质
右键->Create->2D->Physics Material 2D->重命名为Slippy Material
在右边面板中更改摩擦力Friction
将材质拖到碰撞盒的材质这里
这个问题就解决了,效果如下:
(2)实现墙体检测
在Player中添加墙体检测的函数,同时注意到,墙体检测应该随着角色转向而转向,于是墙体检测的方向我们乘上facingDir角色面向的方向。
public bool isWallDetected() => Physics2D.Raycast(wallCheck.position,Vector2.right * facingDir,wallCheckDistance,whatIsGround);
(3)条件切换的实现
创建类PlayerWallSlideState,生成构造函数和重写。
在Player中创建PlayerWallSlideState
#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; }
#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");
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
}
我们在空中下落,贴近墙壁从而检测到墙壁时,转换为滑墙状态。我们在PlayerAirState中添加
//更新
public override void Update()
{
base.Update();
//落地切换到滑墙状态
if (player.isWallDetected())
stateMachine.ChangeState(player.wallSlideState);
//落地切换到空闲状态
if (player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
//设置下落速度
if(xInput!=0)
player.SetVelocity(0.8f * xInput * player.moveSpeed, rb.velocity.y);
}
角色正常进入滑墙状态播放动画
接下来我们完成滑墙状态的退出条件。
角色在滑墙时是面向墙的。当角色输入与墙壁即角色面向方向相反时我们就脱离了墙壁,这就是滑墙状态的退出条件。在滑墙状态中书写条件,首先我们要保证有输出,然后保证角色输出与角色面向方向相同。
我们还可以再添加一个条件检测到地面时退出滑墙状态。
public override void Update()
{
base.Update();
if(xInput!=0 && xInput!=player.facingDir)
stateMachine.ChangeState(player.idleState);
if(player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
可以选择这种方式先转为空闲状态,在空闲状态检测不到地面再转换空中状态,这时视频中的处理方式。
我想在做第一个判断时直接转换为空中状态
if(xInput!=0 && xInput!=player.facingDir)
stateMachine.ChangeState(player.airState);
但这回出现问题
这是因为空中状态在切换滑墙之前并不会改变角色方向,于是角色墙体检测的方向也没变,依然可以检测到墙壁,会不断在滑墙和空中状态切换。
要解决这个问题可以
把空中状态设置速度的函数提到切换滑墙状态前面,或者退出前再添加一个方向转换。
这里我采取了第一种,空中状态中改为
//更新
public override void Update()
{
base.Update();
//设置下落速度
if (xInput != 0)
player.SetVelocity(0.8f * xInput * player.moveSpeed, rb.velocity.y);
//落地切换到滑墙状态
if (player.isWallDetected())
stateMachine.ChangeState(player.wallSlideState);
//落地切换到空闲状态
if (player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
这样滑墙又恢复了正常
(4)设置滑墙速度
上图我们可以看出角色下滑速度太快了,我们将它变得慢一点方便滑墙时进行其他操作。
同时我们希望能对角色做更灵活的控制,能够根据y方向输入改变滑墙快慢。
在基类playerState中添见变量获取y方向输入
protected float xInput;
protected float yInput;
//更新状态
public virtual void Update()
{
xInput = Input.GetAxisRaw("Horizontal");
yInput = Input.GetAxisRaw("Vertical");
player.anim.SetFloat("yVelocity", rb.velocity .y);
stateTimer -= Time.deltaTime;
}
如果玩家按了向下键,就让角色滑得更快,如果没有就缓慢下滑。
public override void Update()
{
base.Update();
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);
}
效果如图
(3)进行一些小调整
有的时候由于碰撞距离过长角色会离墙有一段距离
调整碰撞检测的距离和位置,让他与碰撞盒相接,距离调短。大概这样:
对精灵的中心点进行编辑,让中心稍微偏左
点击要修改的精灵,Pivot改为Custom,改变中心点的值Custom Pivot。
现在角色与墙壁距离正常,滑墙功能完成。
总结 完整代码
PlayerState.cs
获取y方向输入
protected Rigidbody2D rb;
protected float xInput;
protected float yInput;
private string animBoolName;
protected float stateTimer;
//构造函数
public PlayerState(PlayerStateMachine _stateMachine, Player _player, string _animBoolName)
{
this.stateMachine = _stateMachine;
this.player = _player;
this.animBoolName = _animBoolName;
}
//进入状态
public virtual void Enter()
{
//进入动画播放条件变量设置
player.anim.SetBool(animBoolName, true);
//获取刚体
rb = player.rb;
}
//更新状态
public virtual void Update()
{
xInput = Input.GetAxisRaw("Horizontal");
yInput = Input.GetAxisRaw("Vertical");
player.anim.SetFloat("yVelocity", rb.velocity .y);
stateTimer -= Time.deltaTime;
}
//退出状态
public virtual void Exit()
{
//退出动画播放条件变量设置
player.anim.SetBool(animBoolName, false);
}
}
PlayerAirState.cs
设置下落速度和切换滑墙状态
//超级状态PlayerAirState:空中状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerAirState : PlayerState
{
//构造函数
public PlayerAirState(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();
Debug.Log(player.isWallDetected());
//设置下落速度
if (xInput != 0)
player.SetVelocity(0.8f * xInput * player.moveSpeed, rb.velocity.y);
//落地切换到滑墙状态
if (player.isWallDetected())
stateMachine.ChangeState(player.wallSlideState);
//落地切换到空闲状态
if (player.isGroundDetected())
stateMachine.ChangeState(player.idleState);
}
}
PlayerWallSlideState.cs
实现切换空中状态,切换空闲状态,和根据y方向输入设置滑墙速度。
//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(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);
}
}
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; }
#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");
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));
}
}
之前博客里的Bug更正
在后续发现了以下问题,角色开始滑墙后,即使下面没有墙体了也一直保持滑墙的状态不变。
我们添加一个没检测到墙体时,转换到空中状态就可以解决这个问题
在滑墙状态的Update()中添加
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 (!player.isWallDetected())
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);
}
问题解决: