手写FSM实现小怪状态管理
简单的前言
在平时我们会玩到一些RPG游戏,其中的角色能走能跑能飞能打架,打架的时候还能释放各种眼花缭乱的技能,我们为这个角色赋予了很多特性,这个时候就需要编写一套状态管理系统来对不同状态之间的切换进行统一的管理。
由于角色在某一时间只会处于一种状态,不可能边飞边站立嘛!,因此适合用有限状态机(FSM)进行管理 ,这篇文章将会以小怪状态管理为例,简单介绍FSM在Unity中的实现。寒假写的代码可以拿来摸鱼啦。
写代码前的构思
首先先,我们需要理清楚不同状态之间的逻辑联系,画好图,才能条理清晰地打出好康的代码。
我们的小怪,设定是在没探测到角色时巡逻,探测到角色时追击,接近敌人时攻击,HP为0时死亡。巡逻的设定是到达巡逻点时站着不动一段时间,然后前往下个巡逻点。
能想到的状态有:
- 站着不动 Idle
- 走向下个巡逻点 Patrol
- 追击 Chase
- 攻击 Attack
- 死亡 Die
还不算很复杂,下面画出好丑好丑好丑的关系图。
下面来写代码啦。
代码展示
接口定义
每种状态独立创建为一个类,都有状态进入,状态进行,状态退出三个的方法(也就是函数,C#中称之为方法),这样能使得写出来的代码比较具有扩展性。(比如扩展成多层状态机?)
既然每种状态写成类,而且每种状态的结构大致相同,那么就需要定义一个接口,来实现对不同状态的调用。
public interface EState
{void StateIn();void StateStay();void StateExit();
}
有了接口,我们就可以写出各个状态的框架啦!
不过先不用着急写各个状态的内容,框架搭好后,就来写SFM了。
搭建FSM
封装小怪的各种参数
public class EParam
{public int HP;public float moveSpeed; //巡逻速度public float chaseSpeed; //追击速度public float idleTime; //站立时间public Transform[] partolPoint; //巡逻点public float chaseRange; //追击范围public Animator animator; //小怪的动画,如果有的话public Transform player; //如果检测到玩家,则不为nullpublic EState current; //当前状态
}
检索状态
由于我们将状态写成类,直接调用费时费力,因此利用到Dictionary来实现不同状态的调用。
//先定义枚举类型StateType作为Dictionary的Key
public enum StateType
{Idle,Patrol,Chase,Attack,Die
}
public class FSM : MonoBehaviour
{//以StateType为Key,Estate为value,创建Dictionary对象statesprivate Dictionary<StateType, EState> states = new Dictionary<StateType, EState>();public EParam param;private EState currentState;//在Start函数中注册状态,并将初始状态设置为Idleprivate void Start(){states.Add(StateType.Idle, new IdleState(this));states.Add(StateType.Patrol, new PatrolState(this));states.Add(StateType.Attack, new AttackState(this));states.Add(StateType.Die, new DieState(this));states.Add(StateType.Chase, new ChaseState(this));TransformState(StateType.Idle);}
}
切换状态
//切换状态要先退出再进入public void TransformState(StateType type){if (currentState != null)currentState.StateExit();currentState = states[type];currentState.StateIn();}
保持状态
private void Update(){currentState.StateStay();param.current = currentState;//无论哪种状态都要判断是否死亡,所幸直接Update里进行判断if (param.HP <= 0)TransformState(StateType.Die);}
角色探测
这里调用了Unity的触发检测的接口
private void OnTriggerEnter(Collider other){if (other.tag == "Character"){param.player = other.transform;other.GetComponent<DectiveEnermy>().Enermys.Add(this.transform);}}private void OnTriggerExit(Collider other){if (other.tag == "Character"){transform.GetComponent<MeshRenderer>().enabled = false;other.GetComponent<DectiveEnermy>().Enermys.Remove(this.transform);param.player = null;}}
左右转向
public void Filpto(Transform target){if (transform.position.x > target.position.x)transform.localScale = new Vector3(-1, 1, 1);elsetransform.localScale = new Vector3(1, 1, 1);}
OK,这就是挂载在小怪身上的全部代码啦,接下来我们来分别完善各个不同的状态。并实现不同状态之间的切换。
编写各状态
IdleState
由图可知,IdleState在超过指定时间后会切换为PatrolState,探测到角色时切换为ChaseState。
public class IdleState : EState
{private FSM manager;private EParam param;private float timer;//计时器//初始化public IdleState(FSM manager){this.manager = manager;param = manager.param;}public void StateIn(){}public void StateStay(){timer += Time.deltaTime;if(timer>param.idleTime){manager.TransformState(StateType.Patrol);}if (param.player!=null)manager.TransformState(StateType.Chase);}public void StateExit(){timer = 0;}
}
PatrolState
PatrolState在到达巡逻点时切换为IdleState,检测到角色时切换为ChaseState。
public class PatrolState : EState
{private FSM manager;private EParam param;private int num;//记录巡逻点public PatrolState(FSM manager){this.manager = manager;param = manager.param;}public void StateIn(){}public void StateStay(){//实现转向。manager.Filpto(param.partolPoint[num]);manager.transform.position = Vector3.MoveTowards(manager.transform.position,param.partolPoint[num].position, param.moveSpeed * Time.deltaTime);//如果角色与巡逻点之间的距离<0.01,可认为角色到达巡逻点if (Vector3.Distance(manager.transform.position, param.partolPoint[num].position) < 0.1)manager.TransformState(StateType.Idle);if (param.player)manager.TransformState(StateType.Chase);}public void StateExit(){num++;//如果到达最后一个巡逻点,则回归第一个巡逻点if (num >= param.partolPoint.Length)num = 0;}
}
ChaseState
ChaseState在角色出探测范围时转换为PatrolState,在接近角色时转换为AttackState。
public class ChaseState : EState
{private FSM manager;private EParam param;public ChaseState(FSM manager){this.manager = manager;param = manager.param;}public void StateIn(){}public void StateStay(){if (param.player){manager.Filpto(param.player);manager.transform.position = Vector3.MoveTowards(manager.transform.position,param.player.position, param.chaseSpeed * Time.deltaTime);if (Vector3.Distance(manager.transform.position, param.player.position) < 0.5){manager.TransformState(StateType.Attack);}}if (param.player == null || Vector3.Distance(manager.transform.position, param.player.position) > param.chaseRange)manager.TransformState(StateType.Patrol);}public void StateExit(){}
}
AttackState
AttackState在角色远离时转换为ChaseState
public class AttackState : EState
{private FSM manager;private EParam param;private float timer;public AttackState(FSM manager){this.manager = manager;param = manager.param;}public void StateIn(){}public void StateStay(){timer += Time.deltaTime;//攻击效果有些复杂,于是用靠近时主角受击来代替。if (timer < 0.25)param.player.GetChild(0).transform.GetComponent<SpriteRenderer>().material.color = Color.red;else{if (timer < 1.5)param.player.GetChild(0).transform.GetComponent<SpriteRenderer>().material.color = Color.white;elsetimer = 0;}if(Vector3.Distance(manager.transform.position,param.player.transform.position)>0.5)manager.TransformState(StateType.Chase);}public void StateExit(){}
}
DieStates
public class DieState : EState
{private FSM manager;private EParam param;public DieState(FSM manager){this.manager = manager;param = manager.param;}public void StateIn(){GameObject.Destroy(manager.transform.gameObject);}public void StateStay(){}public void StateExit(){}
}
OK,这样所有的状态都写好了。
接下来看看效果吧
巡逻状态转追击状态
脱离追击
追击状态转攻击状态
手写FSM实现小怪状态管理相关推荐
- 纯手写原生PHP网站管理后台系统 网站管理系统
一.源码简介 一套纯手写原生的PHP网站管理后台,前端利用LayUI实现,实现PHP初学者专研学习使用,对于PHP学习的人,只有熟悉了原生的PHP开发,才适合利用其它框架搭建自己的网站平台.封城期间, ...
- Flutter - flutter_bloc状态管理
继上一篇写了Flutter - GetX状态管理,会发现其实Flutter的状态管理的框架还是比较多的,用的比较多的有flutter_bloc.MobX.GetX等,今天我就来谈一谈我学习Flutte ...
- 教你写一个 React 状态管理库
自从 React Hooks 推行后,Redux 作为状态管理方案就显得格格不入了.Dan Abramov 很早就提到过 "You might not need Redux",开发 ...
- 灵魂拷问:我们该如何写一个适合自己的状态管理库?
作者|李骏(涅尘) 来源|尔达Erda公众号 引言 大家好,这里是 Erda 开源项目前端技术团队,今天聊一聊前端的状态管理. 说到状态管理库,想必前端同学随口都能说出好几个来,社区里的轮子一个接 ...
- uni-app store 状态管理学习,多写几遍就会了
uni-app使用了一段时间了,一直没有用到store 状态管理,还是应该学习一下,以后会用到的 1.使用hbuiderx创建uni-app项目 2.与static同级创建store文件夹,store ...
- react 日期怎么格式化_手写React的Fiber架构,深入理解其原理
熟悉React的朋友都知道,React支持jsx语法,我们可以直接将HTML代码写到JS中间,然后渲染到页面上,我们写的HTML如果有更新的话,React还有虚拟DOM的对比,只更新变化的部分,而不重 ...
- 手写一个简单的线程池MyThreadPool
说明 手写的一个简单的线程池,旨在帮助了解线程池的工作原理. 核心内容 核心工作线程 任务阻塞队列 定义一个内部类去实现核心工作线程 /*** 内部类:工作的核心线程*/private final c ...
- druid连接池初始化慢_从零开始手写 mybatis (三)jdbc pool 从零实现数据库连接池
前景回顾 第一节 从零开始手写 mybatis(一)MVP 版本 中我们实现了一个最基本的可以运行的 mybatis. 第二节 从零开始手写 mybatis(二)mybatis interceptor ...
- 手写spring编程事务
使用事务注意事项事务是程序运行如果没有错误,会自动提交事物,如果程序运行发生异常,则会自动回滚. 如果使用了try捕获异常时.一定要在catch里面手动回滚. 事务手动回滚代码 Transaction ...
最新文章
- Spring处理器(Controller)全局建言
- Spring+ActiveMQ配置
- 第九步:仓库管理(成品)
- Typora操作指南
- JAVA多线程和并发
- Zabbix监控agent
- 生效linux内核,Linux内核
- 对已有文件进行既读又写的操作时关于文件位置注意事项(适用于Python和C/C++)
- 关于Java的一些句子
- python tqdm自定义更新进度条
- 05-BIO,NIO,AIO几种通讯模式的比较
- jmeter+ant+jenkins接口自动环境搭建
- pandas将字符串转换成时间_数据处理利器 pandas 实例详解 (下)
- .net byte转java byte_「Java知识收集整理」Java语法的基础
- 无敌论坛_无敌分享网_无敌资源网|专注CG教程和素材分享
- 搭建web项目常见错误
- 使用OpenSSL库函数测试AES-CCM加密算法
- 域名解析中TTL是什么意思
- 在wget中指定代理服务器
- 28 诊断偏差和方差:学习曲线 (28 Diagnosing bias and variance_ Learning curves)