什么是FSM

FSM 即有限状态机,它是一个状态管理系统,表示一个对象的几种状态在指定条件下转移行为,即随着条件的不断改变内部状态不断地切换。

FSM用处或者使用背景

通常使用FSM去实现一些简单的AI逻辑,对于游戏中的每个对象都可以在其生命周期中分出一些状态,比如一个小兵,他可能在休息,或者巡逻,当敌人出现时,他的状态可能切换为追逐敌人或者攻击敌人,当某些条件成立时,状态机从当前状态转移到下一状态,在不同状态下有不同的任务,所以要使用有限状态机去实现。

FSM使用的必要性

当需要实现角色的状态时以及状态间的切换时,相信第一时间想到的应该是if-else,但是如果状态的切换条件表达式过于复杂,if-else就显得臃肿麻烦了。再加上所有条件的判断全在一起,状态一多也容易出现Bug,扩展也不好,使用if-else就会相当臃肿,所以在这种条件下,有限状态机的使用很有必要,当然如果相对简单的状态切换,使用有限状态机就没必要了,需要根据个人需求。

FSM使用注意点

FSM有两个重要的概念:状态和转移,转移是切换条件,必须有一个初始状态,并保存当前状态,以及注意每种状态的转移的必要条件。

FSM优点

使整个状态切换逻辑比较清晰,增强代码的可扩展性,也便于后期维护。

具体实现代码

下面就实现一下FSM,使用怪物AI的例子

1.创建状态基类FSMstate  ,此类负责处理一个状态的周期,状态的进入前,状态中,离开状态等。以及状态切换条件的增删。具体如下:

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 状态ID
  6. /// </summary>
  7. public enum StateID
  8. {
  9. NoneStateID,
  10. Parol,//巡逻状态
  11. Chase,//追逐状态
  12. }
  13. /// <summary>
  14. /// 状态切换条件
  15. /// </summary>
  16. public enum Transition
  17. {
  18. NoneTransition,
  19. SeePlayer,//看到玩家
  20. LosePlayer,//看不到玩家
  21. }
  22.  
  23. /// <summary>
  24. /// FSM中状态基类(实现状态的基本方法)
  25. /// </summary>
  26. public abstract class FSMstate {
  27.  
  28. protected StateID stateID;//状态对应的ID
  29. public StateID ID { get { return stateID; } }
  30. protected Dictionary<Transition, StateID> Transition_StateIDDic = new Dictionary<Transition, StateID>();//存储转换条件和状态的ID
  31. protected FSMsystem fsmSystem;//管理状态对象(因为要状态更新需要通过FSMsystem去管理实现的,所以需要一个管理对象)
  32.  
  33. public FSMstate(FSMsystem fsm)
  34. {
  35. this.fsmSystem = fsm;
  36. }
  37. /// <summary>
  38. ///增加转条件
  39. /// </summary>
  40. /// <param name="trans"></param>
  41. /// <param name="id"></param>
  42. public void AddTransition(Transition trans,StateID id)
  43. {
  44. if (trans==Transition.NoneTransition)
  45. {
  46. Debug.Log("添加的转换条件不能为null");
  47. return;
  48. }
  49. if (id==StateID.NoneStateID)
  50. {
  51. Debug.Log("添加的状态ID不能为null");
  52. return;
  53. }
  54. if (Transition_StateIDDic.ContainsKey(trans))
  55. {
  56. Debug.Log("添加转换条件的时候," + trans + "已经存在于Transition_StateIDDic中");
  57. return;
  58. }
  59. Transition_StateIDDic.Add(trans,id);
  60. }
  61. /// <summary>
  62. /// 删除转换条件
  63. /// </summary>
  64. /// <param name="trans"></param>
  65. public void DeleteTransition(Transition trans)
  66. {
  67. if (trans == Transition.NoneTransition)
  68. {
  69. Debug.Log("删除的转换条件不能为null");
  70. return;
  71. }
  72. if (!Transition_StateIDDic.ContainsKey(trans))
  73. {
  74. Debug.Log("删除转换条件的时候," + trans + "不存在于Transition_StateIDDic中");
  75. return;
  76. }
  77. Transition_StateIDDic.Remove(trans);
  78. }
  79. /// <summary>
  80. /// 根据转换条件获得状态ID
  81. /// </summary>
  82. /// <param name="trans"></param>
  83. /// <returns></returns>
  84. public StateID GetStateID(Transition trans)
  85. {
  86. if (Transition_StateIDDic.ContainsKey(trans))
  87. {
  88. return Transition_StateIDDic[trans];
  89. }
  90. return StateID.NoneStateID;
  91. }
  92.  
  93. /// <summary>
  94. ///转换到此状态前要执行的逻辑
  95. /// </summary>
  96. public virtual void DoBeforeEnterAcion() { }
  97. /// <summary>
  98. /// 离开此状态前要执行的逻辑
  99. /// </summary>
  100. public virtual void DoAfterLevAction(){ }
  101. /// <summary>
  102. /// 处在本状态时要执行的逻辑
  103. /// </summary>
  104. /// <param name="TargetObj"></param>
  105. public abstract void CurrStateAction(GameObject TargetObj);
  106. /// <summary>
  107. /// 切换到下一状态需要执行的逻辑
  108. /// </summary>
  109. /// <param name="TargetObj"></param>
  110. public abstract void NextStateAction(GameObject TargetObj);
  111.  
  112. }

2. 创建状态管理类FSMSystem的创建。用来管理所有的状态(状态的添加,删除,切换,更新等)。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 状态处理类(添加,删除,切换,更新等管理所有状态)
  6. /// </summary>
  7. public class FSMsystem {
  8.  
  9. private Dictionary<StateID, FSMstate> StateDic = new Dictionary<StateID, FSMstate>();//保存状态ID以及ID对应的状态
  10. private StateID _CurrentStateID;//当前处于的状态ID
  11. private FSMstate _CurrentState;//当前处于的状态
  12.  
  13. /// <summary>
  14. /// 添加状态
  15. /// </summary>
  16. /// <param name="state">需管理的状态</param>
  17. public void AddState(FSMstate state) {
  18. if (state==null)
  19. {
  20. Debug.Log("添加的状态"+state+"不能为null");
  21. return;
  22. }
  23. if (_CurrentState==null)
  24. {
  25. _CurrentState = state;
  26. _CurrentStateID = state.ID;
  27. }
  28. if (StateDic.ContainsKey(state.ID))
  29. {
  30. Debug.Log("状态机 "+state.ID+"已经存在,无法添加");
  31. return;
  32. }
  33. StateDic.Add(state.ID,state);
  34. }
  35. /// <summary>
  36. /// 删除状态
  37. /// </summary>
  38. /// <param name="stateID">删除要管理状态的ID</param>
  39. public void DeleteState(StateID stateID)
  40. {
  41. if (stateID==StateID.NoneStateID)
  42. {
  43. Debug.Log("无法删除Null的状态");
  44. return;
  45. }
  46. if (!StateDic.ContainsKey(stateID) )
  47. {
  48. Debug.Log("无法删除不存在的状态:" + stateID);
  49. return;
  50. }
  51. StateDic.Remove(stateID);
  52. }
  53. /// <summary>
  54. /// 状态转换(状态的切换是根据转换条件的变化)
  55. /// </summary>
  56. /// <param name="trans">转换条件</param>
  57. public void PerformTranstion(Transition trans)
  58. {
  59. if (trans == Transition.NoneTransition)
  60. {
  61. Debug.Log("无法执行NULL的转换条件");
  62. return;
  63. }
  64. StateID stateId = _CurrentState.GetStateID(trans);
  65. if (stateId==StateID.NoneStateID)
  66. {
  67. Debug.Log("要转换的状态ID为null");
  68. return;
  69. }
  70. if (!StateDic.ContainsKey(stateId))
  71. {
  72. Debug.Log("状态机中没找到状态ID "+stateId+" 无法转换状态");
  73. return;
  74. }
  75. FSMstate state = StateDic[stateId];//根据状态ID获取要转换的状态
  76. _CurrentState.DoAfterLevAction();//执行离开上一状态逻辑
  77. _CurrentState = state;//更新当前状态
  78. _CurrentStateID = stateId;//更新当前状态ID
  79. _CurrentState.DoBeforeEnterAcion();//执行进入当前状态前要执行的逻辑
  80. }
  81.  
  82. /// <summary>
  83. /// 更新当前状态行为
  84. /// </summary>
  85. /// <param name="TargetObj"></param>
  86. public void UpdateState(GameObject TargetObj)
  87. {
  88. _CurrentState.CurrStateAction(TargetObj);
  89. _CurrentState.NextStateAction(TargetObj);
  90. }
  91.  
  92. }

3.那我们用怪物巡逻,追逐玩家的例子来实现状态机。

3.1. 增加一个PartalState类,用怪物的巡逻。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. /// <summary>
  6. /// 怪物巡逻状态类
  7. /// </summary>
  8. public class PatrolState : FSMstate {
  9. private List<Transform> path = new List<Transform>();//巡逻点
  10. private int index = ;
  11. private Transform PlayerTrasform;
  12.  
  13. /// <summary>
  14. /// 初始化巡逻状态数据
  15. /// </summary>
  16. /// <param name="fsm"></param>
  17. public PatrolState(FSMsystem fsm):base(fsm)
  18. {
  19. stateID = StateID.Parol;
  20. //路点
  21. Transform pathTransform = GameObject.Find("Path").transform;
  22. Transform[] children = pathTransform.GetComponentsInChildren<Transform>();
  23. foreach (Transform child in children)
  24. {
  25. if (child != pathTransform)
  26. {
  27. path.Add(child);
  28. }
  29. }
  30. PlayerTrasform = GameObject.Find("Player").transform;
  31. }
  32.  
  33. /// <summary>
  34. /// 当前状态(巡逻状态)执行的逻辑
  35. /// </summary>
  36. /// <param name="TargetObj"></param>
  37. public override void CurrStateAction(GameObject TargetObj)
  38. {
  39. TargetObj.transform.LookAt(path[index].position);
  40. TargetObj.transform.Translate(Vector3.forward * Time.deltaTime * );
  41. if (Vector3.Distance(TargetObj.transform.position, path[index].position) < )
  42. {
  43. index++;
  44. index %= path.Count;
  45. }
  46. }
  47. /// <summary>
  48. /// 切换到追逐状态(下一状态)执行的的逻辑
  49. /// </summary>
  50. /// <param name="TargetObj"></param>
  51. public override void NextStateAction(GameObject TargetObj)
  52. {
  53. if (Vector3.Distance(PlayerTrasform.position, TargetObj.transform.position) < )
  54. {
  55. fsmSystem.PerformTranstion(Transition.SeePlayer);
  56. }
  57. }
  58.  
  59. }

3.2.增加一个ChaseState类,用怪物的追逐。

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5.  
  6. /// <summary>
  7. /// 追逐状态类
  8. /// </summary>
  9. public class ChaseState : FSMstate {
  10.  
  11. private Transform PlayerTransForm;//玩家位置信息
  12.  
  13. public ChaseState(FSMsystem fsm):base(fsm)
  14. {
  15. stateID = StateID.Chase;
  16. PlayerTransForm = GameObject.Find("Player").transform;
  17. }
  18. /// <summary>
  19. /// 追逐状态下执行的逻辑
  20. /// </summary>
  21. /// <param name="targrtObj"></param>
  22. public override void CurrStateAction(GameObject targrtObj)
  23. {
  24. targrtObj.transform.LookAt(PlayerTransForm.transform.position);
  25. targrtObj.transform.Translate(Vector3.forward**Time.deltaTime);
  26. }
  27. /// <summary>
  28. /// 切换到下一状态(巡逻)前要执行的逻辑
  29. /// </summary>
  30. /// <param name="targrtObj"></param>
  31. public override void NextStateAction(GameObject targrtObj)
  32. {
  33. if (Vector3.Distance(PlayerTransForm.position,targrtObj.transform.position)>)
  34. {
  35. fsmSystem.PerformTranstion(Transition.LosePlayer);
  36. }
  37. }
  38. }

3.3 创建一个控制怪物的脚本Enemy

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. /// <summary>
  5. /// 怪物类
  6. /// </summary>
  7. public class Enemy : MonoBehaviour {
  8.  
  9. private FSMsystem fsmsystem; //在Enemy类中,实例化FSMSystem对象,添加巡逻和追逐状态,还有之间的转换条件
  10. void Start () {
  11. InitFsm();//创建状态机
  12. }
  13.  
  14. void Update () {
  15. fsmsystem.UpdateState(this.gameObject);//检查更新状态
  16. }
  17. /// <summary>
  18. /// 创建状态机
  19. /// 怪物有两种状态分别是巡逻和追逐玩家
  20. /// 如果怪物初始状态(设置为Parol状态)一旦SeePlayer 切换状态被激活后,就切换到Chase状态
  21. /// 如果他在Chase状态一旦LosePlayer状态被激活了,它就转变到Parol状态
  22. /// </summary>
  23. void InitFsm()
  24. {
  25. fsmsystem = new FSMsystem();
  26. FSMstate PatrolState = new PatrolState(fsmsystem);
  27. PatrolState.AddTransition(Transition.SeePlayer, StateID.Chase);
  28. FSMstate ChaseState = new ChaseState(fsmsystem);
  29. ChaseState.AddTransition(Transition.LosePlayer, StateID.Parol);
  30. fsmsystem.AddState(PatrolState);//初始状态
  31. fsmsystem.AddState(ChaseState);
  32. }
  33. }

以上就用FSM有限状态机实现了怪物AI,具体代码中都有注释,有理解不到位的地方,望请指正。

Unity中FSM有限状态机的更多相关文章

  1. Unity——FSM有限状态机

    FSM有限状态机 一.设计思路 1.共同的状态父类,提供可重写的进入,保持,退出该状态的生命周期方法: 2.状态机,管理所有状态(增删查改),状态机运行方法(Run): 3.在角色控制器中,实例化状态 ...

  2. FSM有限状态机

    1.什么是有限状态机 有限状态机(Finite State Machine),简称FSM,它由一组有限个状态.输入和根据输入及现有状态转换为下一个状态的转换函数组成,当然,通常每个状态机都必须有一个初 ...

  3. Unity中使用Attribute

    Attribute是c#的语言特性 msdn说明如下: The Attribute class associates predefined system information or user-def ...

  4. 在Unity中使用UGUI修改Mesh绘制几何图形

    在商店看到这样一个例子,表示很有兴趣,他们说是用UGUI做的.我想,像这种可以随便变形的图形,我第一个就想到了网格变形. 做法1: 细心的朋友应该会发现,每个UGUI可见元素,都有一个‘Canvas ...

  5. Unity中使用WebView

    Unity中使用WebView @(设计) 需求,最近游戏中需要引入H5直播页面和更新比较频繁的赛事页面,需求包括:加密传参数.和Unity交互,在Unity框架下其实有几种方案: 内置函数Appli ...

  6. Unity中创建二维码

    在网络上发现了一个可以把字符串转换成二维码的dll,但是我们要怎么使用他呢.不废话,直接进入主题. 用到的引用 using UnityEngine;using ZXing;using ZXing.Qr ...

  7. 在Unity中创建可远程加载的.unity3d包

    在一个Unity项目中,发布包本身不一定要包括所有的Asset(译为资产或组件),其它的部分可以单独发布为.unity3d,再由程序从本地/远程加载执行,这部分不在本文讨论范围.虽然Unity并没有直 ...

  8. 【原创翻译】初识Unity中的Compute Shader

    一直以来都想试着自己翻译一些东西,现在发现翻译真的很不容易,如果你直接把作者的原文按照英文的思维翻译过来,你会发现中国人读起来很是别扭,但是如果你想完全利用中国人的语言方式来翻译,又怕自己理解的不到位 ...

  9. 【unity shaders】:Unity中的Shader及其基本框架

    shader和Material的基本关系 Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出.绘图单元可以依据这个输出来将图 ...

随机推荐

  1. Sql生成 Insert 语句

    declare @TableName sysname select @TableName = 'T_OOSOrder' declare @result varchar(max) = 'INSERT I ...

  2. Unity通过Jar包调用Android

    Unity通过Jar包调用Android 我们会需要面对下面几个问题,我们一个一个来解决: 怎样在Android Studio中打Jar包 怎样打一个Unity使用的Jar包 怎样在Unity工程中使 ...

  3. vue项目使用js-xlsx进行excel表格的导入和导出方法的简单原型封装

    前提:已经安装好 file-saver xlsx和 script-loader,如未安装,请查看 https://www.cnblogs.com/luyuefeng/p/8031597.html 新建 ...

  4. leetcode.哈希表.594最长和谐子序列-Java

    1. 具体题目: 和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1.现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度. 示例 1: 输入: [1,3,2,2,5 ...

  5. 天道神诀---FTP服务

    FTP 2种模式 主动模式(默认) 客户端以1024-65535之间某一端口发送指令到服务端的21端口,并建立连接.服务端接受到以后,以20端口去连接客户端,建立一条新的链接并传输数据 被动模式 客户 ...

  6. 总分 Score Inflation

    题目背景 学生在我们USACO的竞赛中的得分越多我们越高兴. 我们试着设计我们的竞赛以便人们能尽可能的多得分,这需要你的帮助 题目描述 我们可以从几个种类中选取竞赛的题目,这里的一个"种类& ...

  7. 5、如何快速找到多个字典中的公共键(key) 6 如何让字典保持有序 7 如何实现用户的历史记录功能(最多n条)

    5.如何快速找到多个字典中的公共键(key) from random import randint,sample #随机取数 # a = sample("ABCDEF",randi ...

  8. 我的vscode配置 利用Settings Sync一键安装

    { "prettier.eslintIntegration": true, // 点击保存时,根据 eslint 规则自定修复,同时集成 prettier 到 eslint 中 & ...

  9. 辞职信也要玩出高big

    辞职信尊敬的各位公司领导:值此用人之际,不期请辞,实属不敬.历经四季,余以凡才,承蒙殊待,幸受公司各位领导知遇之恩,得以与诸位贤达公事.时光荏苒,吾经竭力而为,以图报效,虽幸遇领导执手相教,然资质愚钝 ...

  10. PL SQL 存储过程 SQL SERVER创建存储过程及调用,Oracle创建存储过程及调用

    Oracle使用存储过程实例: 例1: //查出表字段赋值给存储过程变量 create proc proc_stu @sname varchar(20), //默认是输入参数(input),另外还有两 ...