Unity学习笔记(七)使用状态机重构角色攻击

前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记

攻击状态重构

首先我们重构攻击状态的动画

之前的动画,我们是使用状态(isAttacking)+攻击次数(comboCounter)完成动画的过渡,这样虽然能完成功能,但是如果状态多了之后非常难维护。现在我们用子状态来处理攻击的动画

在这里插入图片描述

首先创建子状态机,将 playerAttack1、playerAttack2、playerAttack3 拷贝到子状态机里面,之后就跟普通的动画连线过程没什么区别了

在这里插入图片描述
最终完成的状态、Attack-攻击状态,ComboCounter -连招次数
在这里插入图片描述
需要注意的是要记得取消过渡时间
在这里插入图片描述

创建攻击状态脚本(PlayerPrimaryAttackState)

PlayerPrimaryAttackState

public class PlayerPrimaryAttackState : PlayerState
{private int comboCounter;private float lastTimeAttacked;private float comboWindow = 2;public PlayerPrimaryAttackState(Player3 _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName){}public override void Enter(){base.Enter();xInput = 0; // 修复移动后按攻击时,攻击方向相反的问题if (comboCounter > 2 || Time.time >= lastTimeAttacked + comboWindow)comboCounter = 0;player.anim.SetInteger("ComboCounter", comboCounter);float attackDir = player.facingDir;if (xInput != 0){attackDir = xInput;}player.SetVelocity(player.attackMovement[comboCounter].x * attackDir, player.attackMovement[comboCounter].y);stateTimer = .1f;}public override void Exit(){base.Exit();lastTimeAttacked = Time.time;comboCounter++;}public override void Update(){base.Update();if (stateTimer < 0){player.SetZeroVelocity();}if (triggerCalled){stateMachine.ChangeState(player.idleState);}}
}

PlayerAnimationTriggers 动画结束脚本

public class PlayerAnimationTriggers : MonoBehaviour
{private Player3 player => GetComponentInParent<Player3>();private void AnimationTrigger(){player.AnimationTrigger();}
}

在这里插入图片描述

Player 脚本,这里只展示变化

public class Player3 : MonoBehaviour{[Header("Attack details")]// 这个是来设置攻击时移动的 x,y 轴,通过这个来调整动画的展示效果public Vector2[] attackMovement;public float counterAttackDuration = 0.2f;public PlayerPrimaryAttackState attackState { get; private set; }private void Awake(){...attackState = new PlayerPrimaryAttackState(this, stateMachine, "Attack");}public void AnimationTrigger() => stateMachine.currentState.AnimatorFinishTrigger();
}

最终效果

请添加图片描述

当前版本的问题

攻击时突然转向

bug 效果

请添加图片描述

我们可以观察上面状态的变化,player在攻击的间隔会转为 Idle 状态,这个时候突然相反方向,那么就会转换方向

修复问题

使用一个变量表示玩家正在进行某个动作(isBusy),玩家这个状态时,不可以进行方向变化

只展示改动代码

Player3

public class Player3 : MonoBehaviour{public bool isBusy { get; private set; }// 这个方法是修复玩家攻击时突然转向的问题public IEnumerator BusyFor(float _seconds){isBusy = true;yield return new WaitForSeconds(_seconds);isBusy = false;}
}

PlayerIdleState

public class PlayerIdleState : PlayerGroundedState
{public override void Update(){base.Update();if(xInput != 0 && !player.isBusy){stateMachine.ChangeState(player.moveState);}}
}

PlayerPrimaryAttackState

public override void Exit()
{...player.StartCoroutine("BusyFor", .15f);
}
最终效果

请添加图片描述

拓展

Blend Tree & Sub-State Machine 区别

在 Unity 的动画系统中,混合树(Blend Tree)子状态(Sub-State Machine) 是两种不同的功能,分别用于实现动画的不同效果和组织动画的逻辑结构。以下是它们的区别和用途:

  1. 混合树(Blend Tree)
  • 概念: 混合树是一种特殊的动画状态,用于在多段动画之间实现平滑过渡。通过混合参数的调整,Unity 会根据权重动态地混合多个动画剪辑或子混合树,从而生成实时动画。
  • 主要功能:
    • 实现动画的动态混合,例如:
      • 行走、奔跑、冲刺等动作的平滑过渡。
      • 不同方向(前、后、左、右)动作的流畅衔接。
    • 通过输入参数(如速度、方向)控制混合比例,从而生成适应当前状态的动画。
  • 使用场景:
    • 角色的运动状态(如行走和跑步)。
    • 需要在多个动画之间平滑转换的情况。
  • 优点:
    • 动态控制动画混合,非常适合多方向或多状态动画。
    • 减少状态机的复杂度,避免过多的状态和过渡线。
  • 结构:
    • 混合树中的每个动画剪辑(或子混合树)都有对应的权重,权重通过参数(如 SpeedDirection)实时调整。
    • 可以嵌套子混合树,进一步分层混合。
  1. 子状态机(Sub-State Machine)
  • 概念: 子状态机是一种逻辑分组机制,用于将动画状态机中的多个动画状态和它们的过渡逻辑组织到一个独立的子状态机中。
  • 主要功能:
    • 提高动画状态机的组织性和可读性。
    • 将复杂的状态机分解为更小、更易于管理的模块。
  • 使用场景:
    • 拥有复杂动画逻辑的角色(如战斗角色)的状态机。
    • 将状态机按照功能或逻辑分组,例如:
      • 一个子状态机管理移动相关的动画(行走、跑步、跳跃)。
      • 另一个子状态机管理攻击相关的动画(轻击、重击、连击)。
  • 优点:
    • 提高状态机的可维护性,减少主状态机的混乱。
    • 子状态机可以嵌套,支持递归组织逻辑。
  • 结构:
    • 每个子状态机可以包含若干状态(动画剪辑)和它们的过渡线。
    • 子状态机之间可以通过入口和出口连接到主状态机或其他状态。

主要区别

特性混合树(Blend Tree)子状态机(Sub-State Machine)
作用动态混合多个动画,生成实时动画组织和分组动画状态,简化状态机逻辑
适用场景需要平滑过渡的多动画混合(如行走和跑步)复杂动画逻辑分组(如战斗、移动等模块化状态)
控制方式通过参数实时调整混合权重通过状态之间的过渡和触发条件切换状态
复杂度管理简化动画混合逻辑,但本身是一个状态简化状态机结构,适合管理复杂的动画状态
嵌套性支持嵌套子混合树支持嵌套多个子状态机

总结

  • 混合树: 专注于动画的实时混合(如平滑的方向切换、行走到跑步的动态过渡)。
  • 子状态机: 专注于逻辑分组和组织(将状态机模块化,便于管理复杂动画逻辑)。

在实际项目中,可以将两者结合使用:

  • 混合树用于处理连续性较强的动画(如运动状态的混合)。

  • 子状态机用于对动画状态机的逻辑分组(如区分移动逻辑和攻击逻辑)。

    • C# 协程(Coroutine)

      在 Unity 中,StartCoroutine 是一个用于执行 协程(Coroutine) 的方法。协程是 Unity 提供的一种方式,用来在多帧中断执行代码,而不是一次性运行整个方法。通过协程,你可以实现延迟、等待或分阶段的逻辑操作,而无需阻塞主线程。

      1. 协程的特点
      • 异步行为:协程允许代码在一定条件下暂停执行,并在后续某个时间点继续运行。
      • 基于帧更新:协程通过 yield 语句返回控制权,可以在多帧之间执行操作。
      • 与主线程同步:协程不是多线程,它与 Unity 主线程(游戏主循环)一起运行。

      2. StartCoroutine 的用法

      基本语法

      StartCoroutine(IEnumerator coroutineMethod);
      
      • 参数:IEnumerator 是一个迭代器,用来定义协程的行为。
      • 返回值:协程的引用(可以用来停止协程,详见 StopCoroutine)。

      示例 1:等待几秒后执行操作

      using UnityEngine;
      public class CoroutineExample : MonoBehaviour
      {void Start(){// 启动一个协程StartCoroutine(WaitAndPrint());}// 定义协程方法IEnumerator WaitAndPrint(){Debug.Log("开始等待...");// 等待 3 秒(暂停)yield return new WaitForSeconds(3f);Debug.Log("3 秒后继续执行!");}
      }
      

      示例 2:连续多步操作

      IEnumerator MultiStepProcess()
      {Debug.Log("Step 1");yield return new WaitForSeconds(2f); // 等待 2 秒Debug.Log("Step 2");yield return new WaitForSeconds(1f); // 等待 1 秒Debug.Log("Step 3");
      }
      

      示例 3:无限循环协程

      协程可以实现循环逻辑,例如每帧或固定时间间隔执行某些操作。

      IEnumerator ContinuousAction()
      {while (true){Debug.Log("每 2 秒执行一次!");yield return new WaitForSeconds(2f); // 等待 2 秒}
      }
      // 在某些时候启动,例如 Start()
      StartCoroutine(ContinuousAction());
      

      3. yield 的作用

      在协程中,yield 用于暂停协程的执行,并指定暂停条件。以下是一些常见的 yield 语句:

    常用 yield 表达式

    语句说明
    yield return null;暂停到下一帧再继续执行。
    yield return new WaitForSeconds(float time);等待指定时间(以秒为单位),然后继续执行。
    yield return new WaitUntil(() => condition);等待直到条件满足(返回 true),然后继续执行。
    yield return new WaitWhile(() => condition);等待直到条件不再满足(返回 false),然后继续执行。
    yield break;提前退出协程,后续代码不会执行。

    停止协程

    可以通过 StopCoroutineStopAllCoroutines 停止协程。

    停止指定协程

    Coroutine myCoroutine;
    void Start()
    {// 保存协程引用myCoroutine = StartCoroutine(MyCoroutineMethod());
    }
    void StopMyCoroutine()
    {// 停止协程StopCoroutine(myCoroutine);
    }
    

    停止所有协程

    void StopAll()
    {StopAllCoroutines(); // 停止当前脚本中所有运行的协程
    }
    
    1. 协程的局限性
    • 协程不是线程
      • 协程运行在主线程上,不适用于多线程并发处理。
      • 如果主线程阻塞,协程也会暂停。
    • 耗时操作问题
      • 不适合处理非常长时间的操作(如加载大文件),因为它会阻塞 Unity 的主线程。
    • 需要注意生命周期
      • 如果一个对象销毁了,它附加的协程也会自动停止。
    1. 常见用例
    • 延迟操作:在游戏中实现延迟(如攻击冷却、技能施放)。
    • 动画控制:在动画或粒子系统中等待一定时间后触发事件。
    • 网络请求:等待网络响应完成后处理结果。
    • 阶段处理:分帧处理复杂计算或加载操作,避免主线程卡顿。
    1. 总结
    • StartCoroutine 是 Unity 中一种优雅的异步机制,用于执行多帧操作。
    • 它的本质是基于 IEnumeratoryield 来实现的逻辑暂停。
    • 虽然简单易用,但需要注意它并不是真正的多线程,不适合所有异步场景。

    如果你需要更高性能的异步处理,可以结合 Unity 的 异步任务(Async/Await 来使用,例如配合 Task 和 UnityWebRequest 等功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/66512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows10环境下安装RabbitMq折腾记

最近有个老项目需要迁移到windows10环境&#xff0c;用的是比较老的rabbitmq安装包&#xff0c;如下所示。经过一番折腾&#xff0c;死活服务起不来&#xff0c;最终果断放弃老版本启用新版本。现在把折腾过程记录下&#xff1a; 一、安装erlang 安装完成后的目录结构&#xff…

了解RabbitMQ中的Exchange:深入解析与实践应用

在分布式系统设计中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;而RabbitMQ作为开源消息代理软件的佼佼者&#xff0c;以其高性能、高可用性和丰富的功能特性&#xff0c;成为了众多开发者的首选。在RabbitMQ的核心组件中&…

分布式主键ID生成方式-snowflake雪花算法

这里写自定义目录标题 一、业务场景二、技术选型1、UUID方案2、Leaf方案-美团&#xff08;基于数据库自增id&#xff09;3、Snowflake雪花算法方案 总结 一、业务场景 大量的业务数据需要保存到数据库中&#xff0c;原来的单库单表的方式扛不住大数据量、高并发&#xff0c;需…

Linux 系统搭建网络传输环境汇总

Ubuntu 系统搭建 TFTP 服务器 1. 创建 /home/username/workspace/tftp 目录并赋予最大权限&#xff0c;username 是自己用户名 sudo mkdir -p /home/username/workspace/tftp sudo chmod 777 /home/username/workspace/tftp 2. 安装 tftp-hpa&#xff08; 客户端软件包&#x…

“AI智慧语言训练系统:让语言学习变得更简单有趣

大家好&#xff0c;我是你们的老朋友&#xff0c;一个热衷于探讨科技与教育结合的产品经理。今天&#xff0c;我想和大家聊聊一个让语言学习变得不再头疼的话题——AI智慧语言训练系统。这个系统可是我们语言学习者的福音&#xff0c;让我们一起来揭开它的神秘面纱吧&#xff0…

线性代数考研笔记

行列式 背景 分子行列式&#xff1a;求哪个未知数&#xff0c;就把b1&#xff0c;b2放在对应的位置 分母行列式&#xff1a;系数对应写即可 全排列与逆序数 1 3 2&#xff1a;逆序数为1 奇排列 1 2 3&#xff1a;逆序数为0 偶排列 将 1 3 2 只需将3 2交换1次就可以还原原…

精选2款.NET开源的博客系统

前言 博客系统是一个便于用户创建、管理和分享博客内容的在线平台&#xff0c;今天大姚给大家分享2款.NET开源的博客系统。 StarBlog StarBlog是一个支持Markdown导入的开源博客系统&#xff0c;后端基于最新的.Net6和Asp.Net Core框架&#xff0c;遵循RESTFul接口规范&…

关于FPGA中添加FIR IP核(采用了GOWIN EDA)

文章目录 前言一、IP核二、MATLAB文件三、导出系数COE文件1.设计滤波器2.用官方的matlab代码或者直接用文本文件 四、进行模块化设计源文件 前言 FIR滤波器的特点是其输出信号是输入信号的加权和&#xff0c;权值由滤波器的系数决定。每个系数代表了滤波器在特定延迟位置上的“…

51单片机——中断(重点)

学习51单片机的重点及难点主要有中断、定时器、串口等内容&#xff0c;这部分内容一定要认真掌握&#xff0c;这部分没有学好就不能说学会了51单片机 1、中断系统 1.1 概念 中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的&#xff0c;中断功能的存在&#…

VVenC 编码器源码结构与接口函数介绍

VVenC VVenC&#xff08;Fraunhofer Versatile Video Encoder&#xff09;是由德国弗劳恩霍夫海因里希研究所&#xff08;Fraunhofer Heinrich Hertz Institute, HHI&#xff09;开发的一个开源的高效视频编码器。它实现了最新的视频编码标准——Versatile Video Coding (VVC)…

耗时一天,我用AI开发了AI小程序

小码哥从事前后端开发近十年&#xff0c;但是随着技术的更新迭代&#xff0c;有时候没有时间和精力去优化UI、实现一些前后端功能&#xff0c;以及解决一些bug。特别是我想开发小码哥AI的移动端&#xff0c;但觉得自己没有那么多时间去研究移动端了&#xff0c;准备放弃了&…

C#中的关键字out和ref的区别

目录 一、out 二、ref 三、拓展 一、out 在 C# 中&#xff0c;out 是一个关键字&#xff0c;通常用于方法参数&#xff0c;表示该参数是输出参数。使用 out 关键字的参数要求在方法内部必须被赋值&#xff0c;而这个参数的值会在方法返回时传递给调用者。可以理解为&#xf…

SpringBootWeb案例-1(day10)

准备工作 需求 & 环境搭建 需求说明 环境搭建 步骤&#xff1a; 准备数据库表(dept、emp)创建 springboot 工程&#xff0c;引入对应的起步依赖&#xff08;web、mybatis、mysql 驱动、lombok&#xff09;配置文件 application.properties 中引入 mybatis 的配置信息&…

VUE条件树查询 自定义条件节点

之前实现过的简单的条件树功能如下图&#xff1a; 经过最新客户需求确认&#xff0c;上述条件树还需要再次改造&#xff0c;以满足正常需要&#xff01; 最新暴改后的功能如下红框所示&#xff1a; 页面功能 主页面逻辑代码&#xff1a; <template><div class"…

保险丝驱动电路·保险丝有什么用应该如何选型详解文章!!!

目录 保险丝基础知识 保险丝常见类型 保险丝功能讲解 保险丝驱动电路 ​​​​​​​ ​​​​​​​ 编写不易&#xff0c;仅供学习&#xff0c;请勿搬运&#xff0c;感谢理解 常见元器件驱动电路文章专栏连接 LM7805系列降压芯片驱动电路降压芯片驱动电…

李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide

第一步&#xff1a;分析loss on training data 先检查在训练数据上模型是否很好的学习 情况1&#xff1a;如果在训练集上&#xff0c;loss很大&#xff0c;说明在训练资料上没有训练好 可能性1&#xff1a;设置的模型太简单了&#xff0c;模型存在model bias模型偏差&#x…

Gitlab-runner 修改默认的builds_dir并使用custom_build_dir配置

gitlab-runner 修改默认的builds_dir并使用custom_build_dir配置 1. 说明2. 实操&#xff08;以docker执行器为例&#xff09;2.1 修改默认的builds_dir2.1.1 调整gitlab-runner的配置文件2.1.2 CI文件 2.2 启用custom_build_dir2.2.1 调整gitlab-runner的配置文件2.2.2 CI文件…

概率基本概念 --- 离散型随机变量实例

条件概率&独立事件 随机变量 - 离散型随机变量 - 非离散型随机变量 连续型随机变量奇异性型随机变量 概率表示 概率分布函数概率密度函数概率质量函数全概率公式贝叶斯公式 概率计算 数学期望方差协方差 计算实例 假设有两个离散型随机变量X和Y&#xff0c;它们代…

QT RC_FILE 应用程序图标设置

1.先做一个app.ico 文件&#xff0c;并将文件放入资源文件夹中 2.打开QT项目的.pro文件在最下面增加 RC_FILE $$PWD/res/app.rc 3.在资源文件夹中创建一个app.rc文件。在QT开发工具中编辑并输入下在内容 IDI_ICON1 ICON "app.ico" 4.测试效果

SQLark:高效数据库连接管理的新篇章

在我们日常工作中&#xff0c;不同的项目使用的数据库不同&#xff0c;比如我这边涉及的数据库就有达梦、Oracle、Mysql&#xff0c;每种数据库都有自己的数据库连接工具&#xff0c;我们不得不在电脑上针对每个数据库都装一个客户端&#xff0c;使用时还得在不同数据库连接工具…