Unity教程(二)角色移动的实现

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();
    }
}

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值