unity进阶学习笔记:有限状态机

news2025/1/12 10:01:34

一般来说,每一个游戏物体会有多种状态,每一个状态会对应一个特定动画。如一个游戏角色可能有静止状态,移动状态,攻击状态。每一个状态里都有对应的动画。如果我们只是简单使用一个个if语句判断玩家进行哪个控制来切换动画会让程序可读性和可维护性很差。利用FSM(有限状态机)可以将每一个状态单独封装,使得系统结构更加清晰

我们首先搭建一个游戏场景用于示例
在这里插入图片描述
导入素材Character Pack: Free Sample,该素材里包括一个人物模型和一系列人物动画。这里我们使用其中idle,run,wave动画
在这里插入图片描述
给人物添加动画器组件,idle和run之间靠boolean值IsRun切换,idle和wave之间用trigger Wave切换

1 将状态封装为方法

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

public enum State {
    idle,
    run,
    wave
}

public class TestFSM_1 : MonoBehaviour
{

    private Animator ani;
    private State state = State.idle;
    // Start is called before the first frame update
    void Start()
    {
        ani = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        switch (state) {
            case State.idle:
                idle();
                break;
            case State.run:
                run();
                break;
            case State.wave:
                wave();
                break;
        }
    }

    void idle() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir == Vector3.zero) {
            ani.SetBool("IsRun", false);
        } else {
            state = State.run;
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
            state = State.wave;
        }
    }

    void run() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            ani.SetBool("IsRun", true);
            transform.rotation = Quaternion.LookRotation(dir);
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
            state = State.idle;
        }
    }

    void wave() {
        ani.SetTrigger("Wave");
        if (!ani.GetCurrentAnimatorStateInfo(0).IsName("wave")) {
            state = State.idle;
        }
    }
}

首先我们创建枚举State用以保存各个状态名称

这里我们创建idle() run() wave()方法。在idle()方法中如果发现玩家按下方向键将状态切换为State.run,在按下空格时将状态切换为State.wave

在run()方法里我们实现了角色移动,如果方向为(0,0,0)时说明玩家松开方向键,将状态切换为idle

在wave()方法里设置Trigger Wave。这里我们要在挥手动画退出时将状态切换回idle。我们可以使用GetCurrentAnimatorStateInfo方法判断

2 将状态封装为类

状态基类

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

public abstract class FSMState
{
    public int StateID;
    public MonoBehaviour Mono;
    public FSMManager Manager;

    public FSMState(int stateID, MonoBehaviour mono, FSMManager manager) {
        StateID = stateID;
        Mono = mono;
        Manager = manager;
    }

    public abstract void OnEnter();
    public abstract void OnUpdate();
}

状态基类保存了状态的ID(即在State枚举中的值),Mono包括了使用该状态的游戏物体,Manager为该状态的管理器(在后面代码实现)

在状态基类里定义了OnEnter (在进入该状态时执行的方法) OnUpdate (在该状态中每一帧调用方法)。这两个方法在子类中实现

FSMManager类

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

public class FSMManager
{

    public List<FSMState> StateList = new List<FSMState>();
    public int CurrentIndex = -1;

    public void ChangeState(int StateID) {
        CurrentIndex = StateID;
        StateList[CurrentIndex].OnEnter();
    }

    public void Update() {
        if (CurrentIndex != -1) {
            StateList[CurrentIndex].OnUpdate();
        }
    }
}

在该类中我们使用列表StateList保存各个状态。ChangeState方法用于改变状态,在改变状态后调用一次新状态的OnEnter方法。Update方法用于执行当前状态的OnUpdate

创建好这两个类后,我们开始使用该状态机框架实现游戏程序

1 IdleState

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


public class IdleState : FSMState
{

    public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    
    public override void OnEnter() {
        Mono.GetComponent<Animator>().SetBool("IsRun", false);
    }

    public override void OnUpdate() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            Manager.ChangeState((int)State.run);
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
            Manager.ChangeState((int)State.wave);
        }
    }

}

对于静止状态,在OnEnter中将IsRun参数设为false。在OnUpdate方法中检测如果按下方向键将状态切换为State.run,如果按下空格将状态切换为State.wave

2 RunState

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

public class RunState : FSMState
{

    public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    public override void OnEnter() {
        Mono.GetComponent<Animator>().SetBool("IsRun", true);
    }

    public override void OnUpdate() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            Mono.transform.rotation = Quaternion.LookRotation(dir);
            Mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
            Manager.ChangeState((int)State.idle);
        }
    }

}

在OnEnter方法里,我们将动画器参数IsRun设为true。在OnUpdate方法中控制角色移动,如果玩家没有按着方向键将状态切换为idle

3 WaveState

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

public class WaveState : FSMState
{
    public WaveState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    public override void OnEnter() {
       Mono.GetComponent<Animator>().SetTrigger("Wave");
    }

    public override void OnUpdate() {
        if (!Mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("wave")) {
            Manager.ChangeState((int)State.idle);
        }
    }
}

在OnEnter方法中将Wave Trigger触发。在OnUpdate中进行检测当前动画并在wave动画播放完后将状态改为idle

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

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

相关文章

一文打尽目标检测NMS(2): 效率提升篇

文章来自于&#xff1a;曲終人不散丶知乎&#xff0c; 连接&#xff1a;https://zhuanlan.zhihu.com/p/157900024&#xff0c; 本文仅用于学术分享&#xff0c;如有侵权&#xff0c;前联系后台做删文处理。 在笔者上一篇文章《一文打尽目标检测NMS——精度提升篇》中&#xff0…

博客系统后端设计(三) - 实现获取博客列表页功能

文章目录 实现获取博客列表页功能1. 约定前后端交互接口2. 实现后端代码3. 实现前端代码4. 测试代码5. 涉及到的两个 Bug 实现获取博客列表页功能 当前的博客列表上的数据都是写死的&#xff0c;符合逻辑的做法是&#xff0c;通过数据库读取数据后显示到页面上。 此处就需要打…

【Linux】2.2 环境基础开发工具使用——vim

文章目录 什么是 vimvim 的基本操作vim 指令集Normal mode 指令集插入模式复制粘贴撤销剪切光标移动删除 last line mode 指令集列出行号跳到文件中的某一行查找字符保存文件 vim 的配置 什么是 vim Linux editor —— vim ——多模式的编辑器每种模式有差异&#xff0c;模式之…

人工智能洗稿-免费自媒体洗稿工具

文字洗稿 文字洗稿是指通过修改、重组、删除、替换等手段对文本进行优化、清理和调整&#xff0c;以达到改善文章质量、增加独特性和提高可读性的目的。文字洗稿是自媒体行业的一个重要需求&#xff0c;尤其是在批量撰写文章或需要大量修改文本的情况下。文字洗稿分为自动洗稿…

记一次springboot项目漏洞挖掘

前言 前段时间的比赛将该cms作为了题目考察&#xff0c;这个cms的洞也被大佬们吃的差不多了&#xff0c;自己也就借此机会来浅浅测试下这个cms残余漏洞&#xff0c;并记录下这一整个流程&#xff0c;谨以此记给小白师傅们分享下思路&#xff0c;有错误的地方还望大佬们请以指正…

云办公时代,企业如何保护数据资产安全?

云办公是一种基于云计算技术的办公方式&#xff0c;它将传统的办公软件和数据存储方式转移到了云端服务器上。用户可以通过互联网访问各种办公应用程序和数据&#xff0c;实现远程协作、移动化办公和信息共享等功能。 常见的云办公应用包括文档处理、电子邮件、日历、在线会议、…

ABAP 锁对象

需求场景 最近收到用户反馈&#xff0c;发现同一个托运单生成了两个不同的服务订单以及根据同一个送货单生成了两个托运单&#xff0c;经过排查&#xff0c;发现原因都是由同样的问题导致的&#xff0c;多窗口或者多用户同时对一条数据操作&#xff0c;就会出现这种现象。这个…

Learning C++ No.19【搜索二叉树实战】

引言&#xff1a; 北京时间&#xff1a;2023/5/2/9:18&#xff0c;五一放假第四天&#xff0c;昨天本来想要发奋图强将该篇博客写完&#xff0c;但是摆烂了一天&#xff0c;导致已经好几天没有码字&#xff0c;敲代码了&#xff0c;此时难受的感觉涌上心头&#xff0c;但是摆烂…

DNF类游戏动作实现(C语言)

没有接触制作小游戏前&#xff0c;感觉做游戏很不可思议&#xff0c;游戏里的人物是怎么移动的&#xff0c;怎么攻击&#xff0c;释放技能。。。。。。现在逐渐了解到之后&#xff0c;发现2d游戏人物的动作更多是图片的拼接&#xff0c;动作是否精细&#xff0c;由这个动作的帧…

鲲鹏展翅 信安高飞 | 鲲鹏开发者峰会2023-麒麟信安技术论坛成功举办!

2023年5月6日-7日&#xff0c;以“创未来 享非凡”为主题的鲲鹏开发者峰会2023在东莞松山湖举办。鲲鹏产业生态繁荣&#xff0c;稳步发展&#xff0c;正在成为行业核心场景及科研领域首选&#xff0c;加速推动数字化转型。 作为鲲鹏生态重要合作伙伴&#xff0c;麒麟信安受邀举…

企企通:B2B商城四种“玩法”,一站式解决端到端全链路需求!

商城系统在电商零售领域中&#xff0c;一直是助力商家搭建商城的核心工具&#xff0c;随着电商行业的发展&#xff0c;各种新模式随即出现&#xff0c;与此同时也出现了各种各样的商城系统&#xff0c;而B2B商城是这其中最为常见的商城系统。 近年来&#xff0c;由于电子商务的…

相遇于此,相交链表的解题心得

本篇博客会讲解力扣“160. 相交链表”的解题思路&#xff0c;这是题目链接。 老规矩&#xff0c;先来审题。这道题的题干有点长&#xff0c;简而言之&#xff0c;就是判断2个链表是否相交&#xff0c;如果相交就返回第一个相交结点&#xff0c;不相交就返回NULL。看看题目原文…

【C++中可调用对象和function】

C中有如下几种可调用对象&#xff1a;函数、函数指针、lambda表达式、bind对象、仿函数。其中&#xff0c;lambda表达式和bind对象是C11标准中提出的(bind机制并不是新标准中首次提出&#xff0c;而是对旧版本中bind1st和bind2st的合并)。个人认为五种可调用对象中&#xff0c;…

FM33A048B LPUART

概述 LPUART 是一个低功耗UART 接口&#xff0c;其工作仅需32768Hz 时钟&#xff0c;可以支持到最高9600 波特率的数据接收。LPUART 功耗极低&#xff0c;可以在Sleep/DeepSleep 模式下工作。 特点&#xff1a; ⚫ 异步数据收发 ⚫ 标准UART帧格式 ◼ 1bit起始位 ◼ 7或8bit数据…

【ChatGPT Prompt Engineering】面向Java开发者的ChatGPT提示词工程(1)

各位Java开发者们&#xff0c;欢迎来到万猫学社&#xff01;在这里&#xff0c;我将和大家分享ChatGPT提示词工程的系列文章&#xff0c;希望能够和大家一起学习和探讨提示词的最佳实践。 虽然互联网上已经有很多有关提示词的材料&#xff0c;比如那些“每个人都必须知道的30个…

lua是什么?lua的基本语法知识点

目录 一、lua是什么&#xff1f; 二、lua的基本语法 1.运行lua脚本文件 2.注释 3.标示符 4.关键词 5.全局变量 三、数据类型 8个基本类型 1.nil(空&#xff09; 2.boolean&#xff08;布尔&#xff09; 3.number(数字&#xff09; 4.string(字符串&#xff09; 5…

一图看懂 six 模块:最常见的 POSIX 系统调用, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 six 模块&#xff1a;最常见的 POSIX 系统调用, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【six】统计常量intboolstrtuplelist 模块24 fun…

电脑屏幕开机后一直闪不停怎么办?电脑屏幕闪烁的解决方法

不少电脑用户经常会遇到的一种情况&#xff0c;就是开机后&#xff0c;发现电脑屏幕一直闪不停&#xff0c;十分伤眼。驱动人生就为大家带来电脑屏幕闪烁的解决方法。 首先&#xff0c;驱动人生建议可以排查一下出现电脑屏幕闪烁的原因&#xff0c;从而更加针对性的解决故障。…

SpringBoot 整合第三方技术Junit+MyBatis+Druid

测试类中加两个注解就行 SpringBootTest(classes Application.class)//添加SpringBoot 的启动类&#xff0c;万无一失 RunWith(SpringJUnit4ClassRunner.class) public class SpringBootJunitTest {Testpublic void test(){System.out.println("ddddddddddddddddddd&quo…

四象限法则定量分析法,如何客观划分需求优先级?

四象限法按照重要和紧急程度&#xff0c;划分为4个象限&#xff1a;重要且紧急、重要不紧急、不重要但紧急、不重要不紧急。那么我们如何客观地对需求进行评估&#xff0c;并将其放到对应的象限&#xff1f; 我们可以使用定量分析方法对象限进行划分和定值。在横纵坐标中&#…