Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
文章目录
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节进行角色移动的实现。
对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P28
【Unity教程】从0编程制作类银河恶魔城游戏P29
【Unity教程】从0编程制作类银河恶魔城游戏P30
【Unity教程】从0编程制作类银河恶魔城游戏P33
本节实现需要用到一些素材。
素材链接:https://pan.baidu.com/s/1PnN2t7xOcNpPDLteLm4EJw?pwd=7pec。
提取码: 7pec
一、概述
本节主要在PlayerMoveState中实现移动状态的相关功能和状态切换,并根据需要对其他类进行完善。
在此过程中需要为Player添加碰撞和和刚体,并在Player脚本中获取相应组件和变量以支持功能的实现。
实现基本的移动后还要为角色添加翻转,在Player中创建Flip()和FlipController()函数实现。
二、创建动画
1.创建playerIdle和playerMove两个动画。
将素材文件夹Graphics拖入Unity,素材路径Graphics->Main->Warrior_Sheet
此处所用素材已经分割好了,如果想学习从头到尾分割精灵表可以参照
Unity教程(零)Unity和VS的使用相关内容
将精灵表中的第一帧拖到层次面板中Player下面作为子物体,并重命名为Animator
此时Animator并不在Player的中心位置。接下来将Animator图片调整到父对象Player的中心。
注意:此时Toggle Tool Handle Position 这里要调整为Pivot,否则父物体与子物体会同步变动位置
Pivot::将辅助图标定位在游戏对象的实际轴心点(由变换组件进行定义)。
Center:将辅助图标定位在中心位置(根据所选游戏对象)。
在右侧面板调整Animator位置到Player的中心,在本例中可将x设为0.4
此时点击选中Player发现图片已经在中心位置了
在层次面板中点击选中Animator,添加Animator组件
在右侧面板中点击下方Add Component添加组件,搜索Animator并点击添加
右击创建Animator Controlller,命名为Player_AC
右键->Create->Animator Controller
拖动Player_AC到Animator的Animator组件上
接下来创建动画playerIdle
选中Animator,在左侧Animation界面选择Create,创建动画playerIdle
选择精灵表前六帧,将空闲状态动画的帧拖入
调整采样率为10
在Animation面板中可能没有显示采样率的选项,这时点击面板中的三个点,再点击Show Sample Rate显示采样率。再调整采样率为10。
空闲动画创建完毕。
再创建playerMove动画,点击Create New Clip创建,拖入标号6-13帧,步骤与上述相同。
2.连接状态机
创建参数Idle和Move用于状态转换,它们分别表示是否处于空闲状态和移动状态。
Parameters->加号->Bool->重命名为Idle
Parameters->加号->Bool->重命名为Move
右击状态,点击Make Transition连接状态
进行如图所示连接
这里起始默认playerIdle状态,空闲状态的进入已经默认连好了。
设置切换为playerMove状态的条件为Move等于true时。
点击选中过渡,在Conditions列表中点击加号添加变量,选择Move,把值改为在true时。
设置两个状态的退出条件
playerIdle->Exit:Idle为false
playerMove->Exit:Move为false
除此之外,还要对退出状态的过渡进行一些设置,参数更为详细的内容可以参照手册。
参数的示意图如下:
Has Exit Time是否有退出时间:去掉
Transition Duration过渡时间所占百分比:改为0
三、创建平台、添加碰撞器和刚体
角色的移动要在平台上进行,我们先创建一个平台。
在层次面板中右击
点击右键->2D Object->Sprites->Square->重命名为Platform
按快捷键T键使用矩形工具,编辑平台。
这时点击运行,你会发现没有什么反应。因为Unity中碰撞的产生需要两个物体都有碰撞器,且其中一个有刚体。2D碰撞盒和2D刚体详细讲解请见 官方手册2D物理系统参考。
我们给平台和角色加上碰撞盒。
点击Platform,Add Component->Box Collider 2D
点击Player,Add Component->Capsule Collider 2D
如下图所示,碰撞盒的大小不匹配角色。
点击如图所示按钮打开碰撞盒编辑,使碰撞盒大致符合角色大小。
然后,要想让角色按照物理规则正常反应我们还要添加刚体组件。
Add Component->Rigidbody 2D
添加刚体之后,如果直接进行移动的实现最终会发生如下情况:
要解决这一问题,我们可以进行如下设置:
在刚体中设置Constraints->Freeze Rotation->Z,冻结Z轴,这样角色在移动时就不会倒下了。
Interpolate插值方式选择Interpolate,因为有时物理与图形的不同步会导致视觉抖动,打开插值可以进行平滑。
Collision Detection碰撞检测选择Continuous,防止角色没有检测碰撞而穿过其他对象。
四、角色移动状态切换的实现
移动状态要接受玩家的输入,使角色相应移动。空闲状态和移动状态的切换需要判断玩家是否有移动的输入:
若有输入,由空闲状态切换为移动状态;若无输入,从移动状态 切换回空闲状态。
上面在创建动画时我们创建了两个条件变量Idle和Move,我们要通过设置这两个变量的值来实现,角色动画的切换。切换时的过程大致如下:
(1)移动状态与空闲状态的切换
切换状态时,我们要切换角色动画,因此如上图所示,在进入状态和退出状态时,我们要相应地设置条件变量的值。进入状态时,我们将状态对应的条件变量设置为true;退出状态时,设置为false。
由于所有状态切换时都要经过同样的步骤,因此我们可以在PlayerState类中书写设置条件变量的部分。类中的变量animBoolName就是此状态在Unity动画师中对应的条件变量。
依照Animator的官方手册,我们可以调用SetBool函数来进行设置。
因此要设置条件变量,我们要先获取Animator组件,这一步要在player中进行,因为player继承自MonoBehaviour类。
定义变量anim:
public Animator anim { get; private set; }
然后在Awake()中获取组件,获取组件的函数如下:
函数 | 作用 |
---|---|
GetComponent | 如果游戏对象附加了类型为 type 的组件,则将其返回,否则返回 null。 |
GetComponentInChildren | 使用深度首次搜索返回 GameObject 或其任何子项中类型为 type 的组件。 |
GetComponentInParent | 返回 GameObject 或其任何父项中类型为 type 的组件。 |
GetComponents | 返回 GameObject 中类型为 type 的所有组件。 |
GetComponentsInChildren | 返回 GameObject 或其任何子项中类型为 type 的所有组件。 |
GetComponentsInParent | 返回 GameObject 或其任何父项中类型为 type 的所有组件。 |
由于Animator是player的子物体,使用GetComponentInChildren获取:
anim = GetComponentInChildren<Animator>();
PlayerState类的Enter()和Exit()中设置条件变量,代码如下:
//进入状态
public virtual void Enter()
{
player.anim.SetBool(animBoolName, true);
}
//退出状态
public virtual void Exit()
{
player.anim.SetBool(animBoolName, false);
}
(2)读取输入信息
在状态的基类PlayerState中添加变量xInput,这样输入就可以被所有状态使用。
float xInput;
Unity中输入的获取,可以调用Input类接口,接收键盘,鼠标,触摸屏等输入,可以在Edit > Project Settings > Input Manager中查看和更改对应输入方式。
这里我们使用float GetAxisRaw(string axisName),这里axisName是输入轴的名称,根据上述输入信息,我们选择Horizontal 轴,他由 Left 和 Right 以及 A 和 D 键管理,返回-1到1之间的值。
详情参照官方手册Input。
在PlayerState的Update()中为xInput赋值,方便时刻获取输入。代码如下:
public virtual void Update()
{
xInput = Input.GetAxisRaw("Horizontal");
}
运行,按A、D键,会看到动画进行了切换
(3)设置移动速度
上述内容中我们已经完成了状态的切换,但角色动画仍在原地播放,因为我们还没有设置角色在移动方向的速度。
速度我们要在刚体组件中设置
定义变量
public Rigidbody2D rb { get; private set; }
Awake()中获取
rb = GetComponent<Rigidbody2D>();
写设置速度的函数
public void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);
}
接下来我们要在处于移动状态时设置速度,这要在PlayerMoveState的Update()函数中进行。我们进行得是横向的移动,因此把x方向速度设置为输入,y方向速度保持不变。由于此过程中需要使用刚体,为了方便使用我们将它写在PlayerSate基类里
protected Rigidbody2D rb;
public virtual void Enter()
{
player.anim.SetBool(animBoolName, true);
rb = player.rb;
}
PlayerMoveState中设置速度
public override void Update()
{
base.Update();
player.SetVelocity(xInput, rb.velocity.y);
if(xInput==0)
{
stateMachine.ChangeState(player.idleState);
}
}
这时按A、D键我们的角色可以左右移动了,但速度很慢。这时因为xInput的范围在-1到1之间。
我们添加一个新的变量moveSpeed来设置速度。
player中添加变量并在面板上添加移动信息的标题。
[Header("Move Info")]
public float moveSpeed = 8.0f;
我们可以在面板上调整速度大小直至合适。
上述MoveState设置速度时乘上moveSpeed改为
public override void Update()
{
base.Update();
player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y);
if(xInput==0)
{
stateMachine.ChangeState(player.idleState);
}
}
效果如下图:
五、角色翻转
经过上述步骤我们基本完成了移动功能,但角色在向左跑时还是倒着跑的。
我们添加两个变量,角色朝向和角色是否面向右。
设置他们的默认值
public int facingDir { get; private set; } = 1;
private bool facingRight = true;
在player中添加Flip()函数完成翻转的功能。把角色朝向facingDir反向;facingRight取反,若原来角色朝右,翻转后朝左facingRight由true变为false;若原来角色朝左,翻转后facingRight由true变为false,因此取反即可;最后将角色沿y轴旋转180度。
public void Flip()
{
facingDir = -1 * facingDir;
facingRight = !facingRight;
transform.Rotate(0, 180, 0);
}
有了翻转实现的函数我们再写一个函数控制什么条件下进行翻转。
我们在以下条件下需要反转
角色向右移动(速度向右) 但 角色朝向为左
角色向左移动(速度向左) 但 角色朝向为右
代码如下:
public void FlipController()
{
if (rb.velocity.x > 0 && !facingRight)
Flip();
else if(rb.velocity.x < 0 && facingRight)
Flip();
}
在Update()中调用,这时你会发现这样一个问题,角色在停止时就会翻转
我显示了xInput和rb.velocity.x发现在停止按键时会出现这样一种情况,虽然停止输入xInput已经为0了,但刚体在x方向的速度仍然有一个很小的值。我不是很确定这是什么原因,或许是因为Unity的物理模拟。
我们可以用xInput的值做条件判断,做以下更改
public void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);
FlipController(_xVelocity);
}
public void FlipController(float _x)
{
if (_x > 0 && !facingRight)
Flip();
else if(_x < 0 && facingRight)
Flip();
}
角色正常移动翻转,移动功能完成。
总结 完整代码
有改动的部分
PlayerState.cs
增加了动画切换和获取刚体
//PlayerState:状态基类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerState
{
protected PlayerStateMachine stateMachine;
protected Player player;
protected Rigidbody2D rb;
protected float xInput;
private string animBoolName;
//构造函数
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");
}
//退出状态
public virtual void Exit()
{
//退出动画播放条件变量设置
player.anim.SetBool(animBoolName, false);
}
}
PlayerIdleState.cs
添加切换到移动状态
//PlayerIdleState:空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
public class PlayerIdleState : PlayerState
{
//构造函数
public PlayerIdleState(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)
{
stateMachine.ChangeState(player.moveState);
}
}
}
PlayerMoveState.cs
添加设置移动速度和切换到空闲状态部分
public class PlayerMoveState : PlayerState
{
//构造函数
public PlayerMoveState(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();
//设置移动速度
player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y);
//切换到空闲状态
if(xInput==0)
{
stateMachine.ChangeState(player.idleState);
}
}
}
Player.cs
添加获取Animator、rb等组件,定义移动相关变量,设置速度,实现翻转等部分。
//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;
#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; }
#endregion
//创建对象
private void Awake()
{
StateMachine = new PlayerStateMachine();
idleState = new PlayerIdleState(StateMachine, this, "Idle");
moveState = new PlayerMoveState(StateMachine, this, "Move");
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
}
// 设置初始状态
private void Start()
{
StateMachine.Initialize(idleState);
}
// 更新
private void Update()
{
StateMachine.currentState.Update();
}
//设置速度
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();
}
}