首页 > 其他分享 >Unity教程(五)角色冲刺的实现

Unity教程(五)角色冲刺的实现

时间:2024-07-20 23:54:49浏览次数:20  
标签:教程 void Update 冲刺 player Unity private public

Unity开发2D类银河恶魔城游戏学习笔记

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现


文章目录


前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节进行角色冲刺的实现。

对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P34
【Unity教程】从0编程制作类银河恶魔城游戏P35


一、概述

本节主要进行冲刺的实现和优化。

冲刺的切换条件如下:
在这里插入图片描述


我们还要对冲刺进行优化。有这几个小问题:
(1)解决空中无法进行冲刺的问题。我们改为在Player的Update函数中调用冲刺,这样我们可以在任何状态进行冲刺。
(2)解决空中冲刺过程中运动轨迹的问题。
(3)解决空中冲刺后没有落地就开始行走的问题。
(4)解决冲刺方向的问题,添加冲刺方向。
(5)解决连续冲刺冲出平台问题,为冲刺添加冷却,冷却未结束时无法进行冲刺。
切换条件如下:
在这里插入图片描述
具体类的实现如图:
在这里插入图片描述

二、冲刺的实现

(1)创建冲刺动画

层次面板中选中Animator,在Animation面板中创建动画playerDash
playerJump精灵表标号69-72,采样率改为10
具体讲解见Unity教程(零)Unity和VS的使用相关内容
在这里插入图片描述
在这里插入图片描述
连接状态机,并添加过渡条件Dash,并修改过渡设置
添加条件变量Dash,并连接过渡
在这里插入图片描述
Entry->playerDash的过渡,加条件变量
在这里插入图片描述

playerDash -> Exit的过渡,如图更改条件
在这里插入图片描述
在这里插入图片描述

(2)创建计时器

计时器在许多状态中都会用到,因此我们将它创建在状态基类playerState中,并在其中每帧更新。

  protected float stateTimer;
   public virtual void Update()
 {
     xInput = Input.GetAxisRaw("Horizontal");
     player.anim.SetFloat("yVelocity", rb.velocity .y);

     stateTimer -= Time.deltaTime;
 }

DeltaTime 是连续帧之间的时间差,准确说是完成上一帧所用的时间,增量时间是实时变动的。举个例子:假如帧率是1秒30帧,那增量时间就是 1/30秒。帧率是1秒60帧,那增量时间就是 1/60 秒。

(3)冲刺状态切换的完成

首先在Player中创建状态。
添加变量冲刺速度dashSpeed和冲刺持续时间dashDuration,下面是要添加的代码

    [Header("Dash Info")]
    public float dashSpeed=25f;
    public float dashDuration=0.2f;
 
 
    public PlayerDashState dashState { get; private set; }
 //创建对象
 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");

     anim = GetComponentInChildren<Animator>();
     rb = GetComponent<Rigidbody2D>();

 }


首先我们在playerDashSate里给计时器赋值,确定冲刺的持续时间。要写在Enter函数中,因为每次进入冲刺状态都要有一个冲刺持续时间。


    public override void Enter()
    {
        base.Enter();

        stateTimer = player.dashDuration;
    }

在playerGroudedState中写切换到冲刺状态

    //更新
    public override void Update()
    {
        base.Update();

        if (Input.GetKeyDown(KeyCode.LeftShift))
            stateMachine.ChangeState(player.dashState);

        if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())
            stateMachine.ChangeState(player.jumpState);
    }

在playerDashState中写结束冲刺切换到空闲状态

    public override void Update()
    {
        base.Update();

        if (stateTimer < 0)
            stateMachine.ChangeState(player.idleState);
    }

完成状态切换后运行,会发现状态动画可以顺利播放,但角色仍保持在原地,像这样
在这里插入图片描述

(4)设置冲刺速度

我们要设置冲刺速度,之后角色才能动起来。这里设置速度基本与移动一致

    public override void Update()
    {
        base.Update();

        //设置冲刺速度
        player.SetVelocity(player.facingDir * player.dashSpeed, rb.velocity.y);


        //切换到空闲状态
        if (stateTimer < 0)
            stateMachine.ChangeState(player.idleState);
    }

这时角色冲刺后会不停往前冲刺直至冲出平台,因为我们冲刺后就一直保持着冲刺速度。
在这里插入图片描述
我们只需要在冲刺结束时将x方向速度设置为0就可以解决了。由于冲刺结束后会切换空闲状态,我决定在进入空闲状态时将角色x方向速度设置为0。

    //进入
    public override void Enter()
    {
        base.Enter();

        player.SetVelocity(0, rb.velocity.y);
    }

结果如下:
在这里插入图片描述

二、冲刺的完善

(1)解决空中无法冲刺的问题

在游戏中,我们需要灵活的操作去躲避攻击或者平台跳跃,这就要求我们在几乎任何状态下都能进行冲刺去进行一些操作。
原来我们切换冲刺状态是在接地状态中进行,这就导致我们跳跃起来处于跳跃状态和空中状态时无法进行冲刺。所以我们改为直接在Player中进行冲刺。

    // 更新
    private void Update()
    {
        StateMachine.currentState.Update();

        CheckForDashInput();
    }

    //检查冲刺输入
    public void CheckForDashInput()
    {
        if (Input.GetKeyDown(KeyCode.LeftShift))
            StateMachine.ChangeState(dashState);
    }

现在我们可以在空中冲刺了。
在这里插入图片描述

(2)解决空中冲刺的速度问题

上面我们可以看到角色在冲刺时掉落的运动轨迹有些奇怪。这是因为我们在跳跃时切换冲刺,跳跃状态y方向的速度不会马上消失而是继续保持,这时角色的速度是斜向上的,然后y方向速度会继续保持跳跃时的状态,角色接着会在冲刺过程中下落,这样就导致了角色轨迹是一条弧线。这不方便我们做一些操作。
在这里插入图片描述
要改变这一点我们只需在冲刺时不再保持y方向速度,而是直接设置为0即可。在playerDashState中更改:

//更新
public override void Update()
{
    base.Update();

    //设置冲刺速度
    player.SetVelocity(player.facingDir * player.dashSpeed, 0);


    //切换到空闲状态
    if (stateTimer < 0)
        stateMachine.ChangeState(player.idleState);
}

这样我们的角色就会基本直着冲刺,不会在冲刺过程中往下掉啦
在这里插入图片描述

(3)解决空中冲刺后没有落地就开始行走的问题

为了看的更明显一点我调小重力,调整跳跃高度,设置了一个比较极端的数据。你会看到角色冲刺结束后开始“空中漫步”。
在这里插入图片描述
产生这个问题是因为角色在冲刺结束后还没有落地,于是直接在空中转换为空闲状态也就相当于没有判定是否接地直接转换为了接地状态,这时按方向键角色会转换为移动状态直接播放移动动画。
要解决这个问题很简单,只需要在接地状态时添加一个地面检测,如果没有检测到地面则转换为空中状态

    //更新
    public override void Update()
    {
        base.Update();

        if(!player.isGroundDetected())
            stateMachine.ChangeState(player.airState);

        if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())
            stateMachine.ChangeState(player.jumpState);
    }

现在在冲刺后再按左右移动角色不会再有变化
在这里插入图片描述
但这样又产生一个问题角色在下落过程中没法左右移动,这样在游戏中我们就没法在下落时进行一些移动和躲避,这不符合我们的控制习惯,所以我们在空中状态添加一个角色速度设置接收输入。
我们在进行移动状态的实现时提到,在水平方向的输入有许多状态都会遇到,于是我们把水平收入的获取放在了为了便于在空中调整我们让方向速度比在地面上稍小一点。

    //更新
    public override void Update()
    {
        base.Update();

        //落地切换到空闲状态
        if(player.isGroundDetected())
            stateMachine.ChangeState(player.idleState);

        //设置下落速度
        if(xInput!=0)
            player.SetVelocity(0.8f * xInput * player.moveSpeed, rb.velocity.y);

    }

角色在下落过程中可以正常移动
在这里插入图片描述
正常速度和重力下是这样
在这里插入图片描述

(4)解决冲刺方向的问题

在运行时你会发现这样一个问题,在角色移动时同时按下冲刺键和方向键,无论你按的方向是什么,你只能向角色面向的方向冲刺。在这里我向右移动时,同时按下了冲刺键和向左,但角色先向右冲刺一段距离后再改为面向左。
在这里插入图片描述
为了解决这个问题,我们添加一个冲刺方向,在冲刺过程中先接收一个冲刺方向的输入再进行冲刺,当没有输入时我们以面向方向为冲刺方向。
注意:要先获取冲刺方向再改变状态
Player中的代码添加

    [Header("Dash Info")]
    public float dashSpeed=25f;
    public float dashDuration=0.2f;
    public float dashDir {  get; private set; }

    // 更新
    private void Update()
    {
        StateMachine.currentState.Update();

        CheckForDashInput();
    }

    //检查冲刺输入
    public void CheckForDashInput()
    {
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            dashDir = Input.GetAxisRaw("Horizontal");

            if (dashDir == 0)
                dashDir = facingDir;

            StateMachine.ChangeState(dashState);
        }
    }

加上之后反应灵敏了一点,但上面的情况并没有完全避免,还要以后想办法解决。

(5)解决冲刺冷却的问题

我们的冲刺现在写在player中,无论什么状态都可以切换冲刺,这就导致了一个问题。当我们连续不断的按冲刺,角色就会直接冲出去。

在这里插入图片描述
为了解决这个问题,我们需要给冲刺添加冷却。
在Player中添加冷却时间和计时器。在面板中设置dashCoolDown的值。

[Header("Dash Info")]
[SerializeField] private float dashCoolDown=1.0f;
private float dashUsageTimer;
public float dashSpeed=25f;
public float dashDuration=0.2f;
public float dashDir {  get; private set; }

//检查冲刺输入
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);
    }
}

连续冲刺的问题解决,冲刺功能完成。
在这里插入图片描述

总结 完整代码

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;

    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");
        player.anim.SetFloat("yVelocity", rb.velocity .y);

        stateTimer -= Time.deltaTime;
    }

    //退出状态
    public virtual void Exit()
    {
        //退出动画播放条件变量设置
        player.anim.SetBool(animBoolName, false);
    }
}

PlayerDashState.cs

重置计时器、设置冲刺速度以及切换到空闲状态

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

        //设置冲刺速度
        player.SetVelocity(player.dashDir * player.dashSpeed, 0);


        //切换到空闲状态
        if (stateTimer < 0)
            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; }
    #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");

        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);
    //绘制碰撞检测
    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));
    }
}

PlayerGroundedState.cs

添加切换到空中状态,解决空中漫步问题

//超级状态PlayerGroundedState:接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerGroundedState : PlayerState
{
    //构造函数
    public PlayerGroundedState(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(!player.isGroundDetected())
            stateMachine.ChangeState(player.airState);

        if (Input.GetKeyDown(KeyCode.Space)&& player.isGroundDetected())
            stateMachine.ChangeState(player.jumpState);
    }
}

PlayerAirState

解决下落过程中角色方向控制问题

//超级状态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();

        //落地切换到空闲状态
        if(player.isGroundDetected())
            stateMachine.ChangeState(player.idleState);

        //设置下落速度
        if(xInput!=0)
            player.SetVelocity(0.8f * xInput * player.moveSpeed, rb.velocity.y);

    }
}

标签:教程,void,Update,冲刺,player,Unity,private,public
From: https://blog.csdn.net/cyr___/article/details/140458669

相关文章

  • unity3d缩放物体
    unity3d缩放物体 usingUnityEngine;publicclassScaleObject:MonoBehaviour{//缩放速度,可以根据需要调整publicfloatscaleSpeed=0.1f;//控制缩放的方向,这里以X轴为例publicboolscaleX=true;publicboolscaleY=false;publi......
  • 入局大模型!(非常详细)从入门到精通,值得开发者好好看一看的 AI 入门法!(内附教程)
    今年招聘市场确实是好点了,我发现群友都在讨论,得赶快学点AI大模型。他们有的是想正式转到一些新兴的AI行业,需要系统的学习训练。更多的是想跟已有的技能结合,辅助编程提效,或上手实操应用,增加自己的职场竞争力。这也可以理解,ChatGPT推出仅一年半的时间,就将生成式AI推......
  • 用Python调整图片尺寸教程【附源码】
    就像我们学习数学一样,多做题多练习,才会熟能生巧。更多项目源码,实战案例,文件夹领取方式在文末学习Python也是一样,练习才是学好Python的最优解。只有不停的敲代码,不断练习,才能不断进步。实操练习你是否遇到过上传照片有大小要求?不管是上传证件照报名参赛,总会有一个不要......
  • gitlab安装与修改默认端口,并设置开机自启(保姆教程)
    第一步安装,首先下载rpm安装包,我下载的版本是gitlab-ce-16.8.8-ce.0.el7.x86_64.rpm,下载地址为:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-16.8.8-ce.0.el7.x86_64.rpm 第二步在/usr/local文件夹下创建一个gitlab文件夹,将下载的包放进去,如下图: ......
  • Node.js安装及环境配置超详细教程【Windows系统】
    目录Step1:下载安装包Step2:安装程序Step3:环境配置Step4:查看step5:nmp镜像 1.下载node.js  下载地址:Node.js 2.安装   2.1自定义安装路径(可以选择默认) 下图根据本身的需要进行,我选择了默认Node.jsruntime,然后Next:Node.jsruntime:表示运行环境npmpacka......
  • Pycharm安装详细教程(详细步骤)
    PyCharm是目前最流行、使用最广泛的PythonIDE(IntegratedDevelopmentEnvironment,集成开发环境),带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、单元测试、版本控制等,此外,还提供了一些高级功能,以用于支持Dja......
  • Python教程:json中load和loads的区别
    一.相同点dump和dumps都实现了序列化load和loads都实现反序列化变量从内存中变成可存储或传输的过程称之为序列化序列化是将对象状态转化为可保存或可传输格式的过程。变量内容从序列化的对象重新读到内存里称之为反序列化反序列化是流转换为对象。二.区别1.load......
  • 【Python爬虫学习】7个好玩有趣的爬虫教程!(附源码)
    本文介绍了7个Python爬虫小案例,包括爬取豆瓣电影Top250、猫眼电影Top100、全国高校名单、中国天气网、当当网图书、糗事百科段子和新浪微博信息,帮助读者理解并实践Python爬虫基础知识。在文章开始之前先给大家简单介绍一下python爬虫1.什么是爬虫?简单介绍爬虫爬虫的......
  • Unity入门之重要组件和API(4) : Screen
    Screen类主要处理屏幕相关的操作。1.静态属性1.1常用属性【设备分辨率】Resolutionresolution=Screen.currentResolution;print("设备分辨率宽:"+resolution.width+"高:"+resolution.height);【屏幕窗口的宽高】这里得到的是当前窗口的宽高,不是设备分辨率的宽......
  • DVWA靶场通关教程
    一:SQLinjection(回显-手工)low1、漏洞复现(1)首先提交正常的数据,看是否有回显(2)使用orderby判断有多少列1'orderby2#当为3的时候会出现错误(3)判断显示位,有哪几个列是显示出来的(两个列都显示)1'unionselect1,2#使用union查询会出现这个问题解决方法:找到mysql.php,......