【unity实战】使用unity的新输入系统InputSystem+有限状态机设计一个玩家状态机控制——实现玩家的待机 移动 闪避 连击 受击 死亡状态切换

news2025/2/27 6:52:19

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • 人物素材
  • 新输入系统InputSystem的配置
  • 动画配置
  • 代码文件路径
  • 状态机脚本
  • 创建玩家不同的状态脚本
  • 玩家控制
  • 源码
  • 完结

前言

前面我们已经写过了使用有限状态机制作一个敌人AI:【unity实战】在Unity中使用有限状态机制作一个敌人AI

那么玩家的状态机要怎么做呢?目前网上这一块内容也很少,当我们对人物的操作越来越多时,有限状态机技术可以很好的帮我们将各部分功能拆开,单独配置逻辑,代码更加优雅,接下来我们就用新输入系统InputSystem设计一个玩家状态机控制系统,其实跟之前的敌人有限状态机类似。

人物素材

https://bdragon1727.itch.io/16x16-pixel-adventures-character
在这里插入图片描述

新输入系统InputSystem的配置

新输入系统还不会使用的可以参考这篇文章:【推荐100个unity插件之18】Unity 新版输入系统InputSystem的基础使用

其实就是默认的配置加了攻击和闪避的操作
在这里插入图片描述

动画配置

动画基础知识:【Unity游戏开发教程】零基础带你从小白到超神27——混合状态,混合动画,动画分类

除了攻击动画,其他的都放在第一层
在这里插入图片描述
闪避动画我是通过不断修改玩家图片的FlipY值实现的
在这里插入图片描述

重点讲讲攻击动画连击
两个参数控制进入,isMeleeAttack主要是有效防止播放最后一段连击后,再播放一次第一段攻击
在这里插入图片描述
如果动画播放90%再次按下就会进入下一段攻击
在这里插入图片描述
所有动画播放为1,即播放完时退出
在这里插入图片描述

代码文件路径

在这里插入图片描述

状态机脚本

定义状态类型枚举

// 定义状态类型枚举
public enum StateType
{
    Idle, //待机
    Move, //移动
    Dodge, //闪避
    MeleeAttack, //近战攻击
    Hit, //受击
    Death //死亡
}

抽象基类,定义了所有状态类的基本结构

//抽象基类,定义了所有状态类的基本结构

public abstract class IState
{
    protected FSM manager;// 当前状态机
    protected Parameter parameter;// 参数
    public abstract void OnEnter();// 进入状态时的方法
    public abstract void OnUpdate();// 更新方法
    public abstract void OnFixedUpdate();// 固定更新方法
    public abstract void OnExit();// 退出状态时的方法
}

可序列化的参数类,存储了角色的各种状态参数和配置

// 可序列化的参数类,存储了角色的各种状态参数和配置
using System;
using UnityEngine;

[Serializable]
public class Parameter
{
    
    [Header("属性")]
    public float health; // TODO:生命值 仅仅用于测试,实际生命值可能并不放在这里
    [HideInInspector] public Animator animator;       // 角色动画控制器
    [HideInInspector] public AnimatorStateInfo animatorStateInfo;    // 动画状态信息
    [HideInInspector] public SpriteRenderer sr; // 精灵渲染器
    [HideInInspector] public Rigidbody2D rb; // 刚体
    [HideInInspector] public PlayerSystem inputSystem;//新的输入系统

    [Header("移动")]
    public float normalSpeed = 3f; // 默认移动速度
    public float attackSpeed = 1f; // 攻击时的移动速度
    [HideInInspector] public Vector2 inputDirection; // 输入的移动方向
    [HideInInspector] public float currentSpeed; // 当前移动速度

    [Header("攻击")]
    public float meleeAttackDamage; // 近战攻击造成的伤害
    [HideInInspector] public bool isMeleeAttack; // 是否进行近战攻击

    [Header("闪避")]
    public float dodgeForce; // 闪避的力量
    public float dodgeCooldown = 2f; // 闪避的冷却时间
    [HideInInspector] public bool isDodging = false; // 是否在闪避中
    [HideInInspector] public bool isDodgeOnCooldown = false; // 是否在闪避冷却中

    [Header("受伤与死亡")]
    [HideInInspector] public bool isHurt; // 是否受伤
    [HideInInspector] public bool isDead; // 是否死亡
    [HideInInspector] public bool getHit;             // 是否被击中
}

新增玩家状态机

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

// 玩家有限状态机类
public class FSM : MonoBehaviour
{
    private IState currentState;        // 当前状态接口
    protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>();  // 状态字典,存储各种状态

    public Parameter parameter;  // 状态机参数

    public virtual void Awake()
    {
        parameter.rb = GetComponent<Rigidbody2D>();
        parameter.animator = GetComponent<Animator>();
        parameter.sr = GetComponent<SpriteRenderer>();

        // 初始化各个状态,并添加到状态字典中
        states.Add(StateType.Idle, new IdleState(this));
        states.Add(StateType.Move, new MoveState(this));
        states.Add(StateType.Dodge, new DodgeState(this));
        states.Add(StateType.MeleeAttack, new MeleeAttackState(this));
        states.Add(StateType.Hit, new HitState(this));
        states.Add(StateType.Death, new DeathState(this));

        TransitionState(StateType.Idle);    // 初始状态为Idle
    }

    public virtual void OnEnable()
    {
        currentState.OnEnter();
    }

    public virtual void Update()
    {
        //有效防止播放最后一段连击后,再播放一次第一段攻击
        parameter.animator.SetBool("isMeleeAttack", parameter.isMeleeAttack);

        parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息

        currentState.OnUpdate();
    }

    public virtual void FixedUpdate()
    {
        currentState.OnFixedUpdate();
    }

    // 状态转换方法
    public void TransitionState(StateType type)
    {
        if (currentState != null)
            currentState.OnExit();// 先调用退出方法

        currentState = states[type];    // 更新当前状态为指定类型的状态
        currentState.OnEnter();         // 调用新状态的进入方法
    }

    // 切换操作映射
    public void SwitchActionMap(InputActionMap actionMap)
    {
        parameter.inputSystem.Disable(); // 禁用当前的输入映射
        actionMap.Enable(); // 启用新的输入映射
    }

    public void Move()
    {
        // 根据当前状态设置角色速度
        parameter.currentSpeed = parameter.isMeleeAttack ? parameter.attackSpeed : parameter.normalSpeed;
        // 设置角色刚体的速度为移动方向乘以当前速度
        parameter.rb.velocity = parameter.inputDirection * parameter.currentSpeed;

        FlipTo();
    }
    
    // 翻转角色
    public void FlipTo()
    {
        if (parameter.inputDirection.x < 0)
        {
            transform.localScale = new Vector3(-1, 1, 1);
        }
        if (parameter.inputDirection.x > 0)
        {
            transform.localScale = new Vector3(1, 1, 1);
        }
    }

    // 开始闪避技能冷却的协程
    public void DodgeOnCooldown()
    {
        StartCoroutine(nameof(DodgeOnCooldownCoroutine));
    }

    public IEnumerator DodgeOnCooldownCoroutine()
    {
        yield return new WaitForSeconds(parameter.dodgeCooldown);// 等待闪避冷却时间
        parameter.isDodgeOnCooldown = false;// 闪避技能冷却结束,设置为不在冷却状态
    }
}

创建玩家不同的状态脚本

在这里插入图片描述
待机状态

using UnityEngine;
/// <summary>
/// 待机状态
/// </summary>

public class IdleState : IState
{
    public IdleState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Idle");
    }
    public override void OnUpdate()
    {
        // parameter.anim.SetFloat("speed", parameter.rb.velocity.magnitude);
        // 如果受伤
        if (parameter.isHurt)
        {
            manager.TransitionState(StateType.Hit);
        }

        //如果输入的移动方向不为0
        if (parameter.inputDirection != Vector2.zero)
        {
            manager.TransitionState(StateType.Move);
        }
        //闪避
        if (parameter.isDodging)
        {
            manager.TransitionState(StateType.Dodge);
        }
        //真正近战攻击
        if (parameter.isMeleeAttack)
        {
            manager.TransitionState(StateType.MeleeAttack);
        }
    }

    public override void OnFixedUpdate() { }

    public override void OnExit() { }
}

移动状态


/// <summary>
/// 移动状态
/// </summary>
public class MoveState : IState
{
    public MoveState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override  void OnEnter()
    {
        parameter.animator.Play("Run");
    }

    public override  void OnUpdate()
    {
        // 如果想按速度切换移动或奔跑动画
        // parameter.animator.SetFloat("speed", player.rb.velocity.magnitude);
        //受伤
        if (parameter.isHurt)
        {
            manager.TransitionState(StateType.Hit);
        }
        //速度为0
        if (parameter.rb.velocity.magnitude < 0.01f)
        {
            manager.TransitionState(StateType.Idle);
        }
        //闪避
        if (parameter.isDodging)
        {
            manager.TransitionState(StateType.Dodge);
        }
        //近战攻击
        if (parameter.isMeleeAttack)
        {
            manager.TransitionState(StateType.MeleeAttack);
        }

    }
    public override  void OnFixedUpdate()
    {
        manager.Move();
    }
    public override  void OnExit() { }
}

近战攻击状态

/// <summary>
/// 近战攻击状态
/// </summary>
public class MeleeAttackState : IState
{
    public MeleeAttackState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override  void OnEnter()
    {
        parameter.animator.SetTrigger("MeleeAttack");
    }
    public override  void OnUpdate()
    {
        //受伤
        if (parameter.isHurt)
        {
            manager.TransitionState(StateType.Hit);
        }

        // 动画播放95%切换到待机状态
        if (parameter.animatorStateInfo.normalizedTime >= .95f)
        {
            manager.TransitionState(StateType.Idle);
        }
    }
    public override  void OnFixedUpdate()
    {
        manager.Move();
    }
    public override  void OnExit()
    {
        parameter.isMeleeAttack = false;
    }
}

闪避状态

using System.Collections;
using UnityEngine;
/// <summary>
/// 闪避状态
/// </summary>
public class DodgeState : IState
{
    public DodgeState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Dodge");
    }
    public override void OnUpdate()
    {
        //受击
        if (parameter.isHurt)
        {
            manager.TransitionState(StateType.Hit);
        }
        //动画播放95%切换到待机状态
        if (parameter.animatorStateInfo.normalizedTime >= .95f)
        {
            manager.TransitionState(StateType.Idle);
        }
    }

    public override void OnFixedUpdate()
    {
        manager.Move();
        if(parameter.isDodging) Dodge();
    }

    public override void OnExit() {
        parameter.isDodging = false;
    }

    // 进行闪避操作的方法
    public void Dodge()
    {
        // 施加闪避力量,根据输入方向和设定的闪避力量
        parameter.rb.AddForce(parameter.inputDirection * parameter.dodgeForce, ForceMode2D.Impulse);
        parameter.isDodgeOnCooldown = true;
        manager.DodgeOnCooldown();// 开始闪避冷却
    }
}

受击状态

/// <summary>
/// 受击状态
/// </summary>
public class HitState : IState
{
    public HitState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.animator.Play("Hit");
        //TODO:仅用于测试
        parameter.health --;    // 减少角色生命值
    }
    public override  void OnUpdate()
    {
        //TODO:仅用于测试,如果角色生命值小于等于0,转换到死亡状态
        if (parameter.health <= 0)
        {
            manager.TransitionState(StateType.Death);
        }

        //动画播放95%切换到待机状态
        if (parameter.animatorStateInfo.normalizedTime >= .95f)
        {
            manager.TransitionState(StateType.Idle);
        }
    }
    public override  void OnFixedUpdate()
    {
        manager.Move();
    }

    public override  void OnExit()
    {
        parameter.isHurt = false;
    }
}

死亡状态

/// <summary>
/// 死亡状态
/// </summary>
public class DeathState : IState
{
    public DeathState(FSM manager)
    {
        this.manager = manager;
        this.parameter = manager.parameter;
    }

    public override void OnEnter()
    {
        parameter.isDead = true;
        manager.SwitchActionMap(parameter.inputSystem.UI);//切换为UI输入
        parameter.animator.Play("Dead");
    }

    public override void OnUpdate() { }

    public override void OnFixedUpdate() { }

    public override void OnExit() { }
}

玩家控制

新增PlayerController,获取玩家的输入

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : FSM {
    public override void Awake() {
        base.Awake();

        // 初始化输入控制
        parameter.inputSystem = new PlayerSystem();
        parameter.inputSystem.Player.Move.performed += Move;
        parameter.inputSystem.Player.Move.canceled += StopMove;
        parameter.inputSystem.Player.Dodge.started += Dodge;
        parameter.inputSystem.Player.MeleeAttack.started += MeleeAttack;
        
        SwitchActionMap(parameter.inputSystem.Player); // 切换到游戏操作的输入映射
    }

    void OnDisable()
    {
        // 禁用所有输入
        parameter.inputSystem.Disable();
    }

    public override void Update()
    {
        //TODO:用于测试 如果按下回车键,设置被击中状态为true
        if (Input.GetKeyDown(KeyCode.Return))
        {
            // PlayerHurt();
            parameter.isHurt = true;
        }

        base.Update();
    }

    public void Move(InputAction.CallbackContext context)
    {
        parameter.inputDirection = parameter.inputSystem.Player.Move.ReadValue<Vector2>();   
    }

    // 停止移动,将输入方向设为零向量
    public void StopMove(InputAction.CallbackContext context)
    {
        parameter.inputDirection = Vector2.zero;
    }

    // 触发闪避的方法
    public void Dodge(InputAction.CallbackContext context)
    {
        //如果当前不在冷却中,则开始闪避
       if(!parameter.isDodgeOnCooldown) parameter.isDodging = true;
    }

    // 近战攻击
    public void MeleeAttack(InputAction.CallbackContext context)
    {
        parameter.isMeleeAttack = true;
    }
}

效果
在这里插入图片描述

源码

整理好了我会放上来

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

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

相关文章

[吃瓜教程]南瓜书第5章神经网络

1.M-P神经元 M-P神经元&#xff0c;全称为McCulloch-Pitts神经元&#xff0c;是一种数学模型&#xff0c;用于模拟生物神经元的功能。这个模型是由Warren McCulloch和Walter Pitts在1943年提出的。它是人工智能和计算神经科学领域中非常重要的早期模型。 M-P神经元接收n个输入…

成都晨持绪:开一家抖音网店到底能不能赚钱

在数字化时代的浪潮中&#xff0c;抖音以其独特的魅力迅速占领了社交媒体的舞台。众多创业者纷纷把目光投向这个新兴平台&#xff0c;企图在短视频的海洋里找到属于自己的财富岛屿。但是&#xff0c;开一家抖音网店到底能不能赚钱呢? 我们要认识到&#xff0c;抖音作为一个流量…

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本

Excel为数据绘制拆线图,并将均值线叠加在图上,以及整个过程的区域录屏python脚本 1.演示动画A.视频B.gif动画 2.跟踪鼠标区域的录屏脚本 Excel中有一组数据,希望画出曲线,并且能把均值线也绘制在图上,以下动画演示了整个过程,并且提供了区域录屏脚本,原理如下: 为节约空间,避免…

九、函数的声明和定义

函数声明&#xff1a; 1. 告诉编译器有一个函数叫什么&#xff0c;参数是什么&#xff0c;返回类型是什么。但是具体是不是存在&#xff0c;函数 声明决定不了。 2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。 3. 函数的声明一般要放在头文件中的。 定义的函…

ffmpeg下载/配置环境/测试

一、下载 1、访问FFmpeg官方网站下载页面&#xff1a;FFmpeg Download Page&#xff1b; 2、选择适合Windows的版本&#xff08;将鼠标移动到windows端&#xff09;。通常&#xff0c;你会找到“Windows builds from gyan.dev”或者“BtbN GitHub Releases”等选项&#xff0…

【高阶数据结构】图的应用--最短路径算法

文章目录 一、最短路径二、单源最短路径--Dijkstra算法三、单源最短路径--Bellman-Ford算法四、多源最短路径--Floyd-Warshall算法 一、最短路径 最短路径问题&#xff1a;从在带权有向图G中的某一顶点出发&#xff0c;找出一条通往另一顶点的最短路径&#xff0c;最短也就是沿…

把前端打包放到Eladmin框架中运行

再resuorces目录创建static文件夹&#xff0c;然后把前端文件放进来 然后修改 ConfigurerAdapter文件&#xff0c;如下图所示 这样就可以通过ip端口/index.html 这样访问啦&#xff01;

vue3 滚动条滑动到元素位置时,元素加载

水个文 效果 要实现的思路就是&#xff0c;使用IntersectionObserver 检测元素是否在视口中显示&#xff0c;然后在通过css来进行动画载入。 1.监控元素是否视口中显示 const observer new IntersectionObserver((entries) > {entries.forEach((entry) > {if (entry.i…

【网络安全学习】漏洞利用:BurpSuite的使用-03-枚举攻击案例

如何使用BurpSuite进行枚举攻击 1.靶场选择 BurpSuite官方也是有渗透的教学与靶场的&#xff0c;这次就使用BurpSuite的靶场进行练习。 靶场地址&#xff1a;https://portswigger.net/web-security 登录后如下图所示&#xff0c;选择**【VIEW ALL PATHS】**&#xff1a; 找…

树状数组基础知识

lowbit: lowbit(x)x&(-x) 树状数组&#xff1a; 树状数组的功能&#xff1a; 数组 在O(1)的时间复杂度实现单点加&#xff1a; 在O(lng n)的时间复杂度实现查询前缀和&#xff1a; 树状数组的定义&#xff1a; 查询前x项的和操作&#xff1a; ll query(int x){ll s0;f…

单例模式详解:概念与实用技巧

目录 单例模式单例模式结构单例模式适用场景单例模式优缺点练手题目题目描述输入描述输出描述输入示例输出示例提示信息题解 单例模式 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 只有一个实例的…

LLM - 词表示和语言模型

一. 词的相似度表示 (1): 用一系列与该词相关的词来表示 (2): 把每个词表示一个独立的符号(one hot) (3): 利用该词上下文的词来表示该词 (3): 建立一个低维度的向量空间&#xff0c;用深度学习方法将该词映射到这个空间里(Word Embedding) 二&#xff1a;语言模型 (1): 根…

jsqlparse工具拦截sql处理和拓展

前置知识 访问者模式 &#xff08;Visitor Pattern&#xff09;是一种行为设计模式&#xff0c;它允许你定义在不改变被访问元素的类的前提下&#xff0c;扩展其功能。通过将操作&#xff08;操作或算法&#xff09;从对象结构中提取出来&#xff0c;可以在不修改这些对象的前…

MCU中如何利用串口通信,增加AT指令框架

第一步&#xff0c;通过串口与PC端建立通信第二步&#xff0c;根据PC端发来的AT指令&#xff0c;MCU执行相应代码 主要是解析PC端发来的字符串&#xff0c;也就是获取字符串、处理字符串、以及分析字符串。 1. 串口通信 用到的是DMA串口通信&#xff0c;收发字符串数据时&…

AGI系列(7)Reflection 在 AI agent 中的应用实例

斯坦福大学教授吴恩达一直非常推崇AI Agent,之前他提出过AI Agent的四种工作模式,分别是Reflection(反思)、Tool use(工具使用)、Planning(规划)和Multi-agent collaboration(多智能体协同)。 近日,他又开源了一个翻译 AI Agent, 他认为 AI 智能体机器翻译对改进传…

spring6框架解析(by尚硅谷)

文章目录 spring61. 一些基本的概念、优势2. 入门案例实现maven聚合工程创建步骤分析实现过程 3. IoC&#xff08;Inversion of Control&#xff09;基于xml的bean环境搭建获取bean获取接口创建实现类依赖注入 setter注入 和 构造器注入原生方式的setter注入原生方式的构造器注…

electron-vue自定义标题

1.在主进程background.js或者main.js中主窗口配置frame: false async function createWindow() {Menu.setApplicationMenu(null);// Create the browser window.const win new BrowserWindow({width: 1000,height: 600,resizable: false,frame: false,webPreferences: {nodeI…

Python基础语法(与C++对比)(持续更新ing)

代码块 Python在统一缩进体系内&#xff0c;为同一代码块C{...}内部的为同一代码块 注释 Python 单行注释&#xff1a;#... 多行注释&#xff1a;... C 单行注释&#xff1a;//... 多行注释: /*...*/ 数据类型 1. Python数据类型 Python中支持数字之间使用下划线 _ 分割…

docker容器技术、k8s的原理和常见命令、用k8s部署应用步骤

容器技术 容器借鉴了集装箱的概念&#xff0c;集装箱解决了什么问题呢&#xff1f;无论形状各异的货物&#xff0c;都可以装入集装箱&#xff0c;集装箱与集装箱之间不会互相影响。由于集装箱是标准化的&#xff0c;就可以把集装箱整齐摆放起来&#xff0c;装在一艘大船把他们…

昇思学习打卡-5-基于Mindspore实现BERT对话情绪识别

本章节学习一个基本实践–基于Mindspore实现BERT对话情绪识别 自然语言处理任务的应用很广泛&#xff0c;如预训练语言模型例如问答、自然语言推理、命名实体识别与文本分类、搜索引擎优化、机器翻译、语音识别与合成、情感分析、聊天机器人与虚拟助手、文本摘要与生成、信息抽…