简单实用的 Unity 状态机设计

状态机是非常常用的游戏编程模式,状态机的设计也有简单或复杂的区别。

我脑海里的状态机

状态机是什么样的?这是一个非常典型的状态机设计(随手写的):

// 状态类
    class State
    {
        // 保存的状态机引用
        StateMachine _machine;

        // 构造状态,保存状态机引用
        public State(StateMachine machine) { _machine = machine; }

        // 进入状态
        public virtual void OnEnter() { }

        // 离开状态
        public virtual void OnLeave() { }

        // 更新状态
        public virtual void OnUpdate() { }
    }

    // 状态机类
    class StateMachine
    {
        // 当前状态
        State _state;

        // 改变状态
        public void ChangeState(State newState)
        {
            _state.OnLeave();
            _state = newState;
            _state.OnEnter();
        }

        // 更新当前状态
        void Update()
        {
            _state.OnUpdate();
        }
    }

这段代码主要表达了这样的思想:

1.状态含有进入、更新、离开 3 个可重写的方法;

2.状态机和状态的关系是 1 对多;

3.一个状态机内同一时间最多只有一个状态是“活动”的。

为什么没有看到 Transition 的存在?因为我认为添加这个类失去的比获得的更多。一方面状态转移应该存在于设计图上而不是代码里,另一方面转移的本质就是执行一个状态切换(可能还要做一些其他事情),这样的“一个过程”没有对象化的充足理由。这只是我的个人观点,如果你有想说的可以给我留言。

这个设计存在的问题

当你使用这个设计时,你需要继承状态类来实现自己的状态,重写 OnEnter,OnLeave 或 OnUpdate。想象一下为了执行一个简单的操作而定义一个新的类,然后使用这个类来创建一个状态实例,并且很可能这个类只需要用来创建一个状态实例。时间久了你可能会怀疑这一切的意义是什么。如果这还不算什么:你可能注意到了,状态保存了所属状态机的引用。在这些重写方法里,你不可避免的需要访问所属状态机的属性或方法,所以先保存状态机的引用是必要的。而且你可能还需要重新定义一个 get 属性,来将基本的 StateMachine 转换为自定义的状态机类型。想象一下每一次读取角色的生命值都要加一个“character.”的前缀。怎么样才能让事情变的更简单?

最后的解决办法

    // 状态
    public class State
    {
        // 进入
        public Action onEnter;

        // 离开
        public Action onLeave;

        // 更新
        public Action<float> onUpdate;
    }

    // 状态机
    public class StateMachine : MonoBehaviour
    {
        // 当前状态
        private State _state;
        public State state
        {
            // 获取当前状态
            get { return _state; }

            // 切换状态
            set
            {
                if (_state != null && _state.onLeave != null) _state.onLeave();
                _stateTime = 0;
                _state = value;
                if (_state != null && _state.onEnter != null) _state.onEnter();
            }
        }

        // 状态时间
        private float _stateTime;
        public float stateTime { get { return _stateTime; } }

        // 更新状态(在 FixedUpdate,Update 或 LateUpdate 中调用)
        protected void UpdateState(float deltaTime)
        {
            _stateTime += deltaTime;
            if (_state != null && _state.onUpdate != null) _state.onUpdate(deltaTime);
        }
    }

状态没有虚拟或抽象方法了,被替换为委托,并且不再保存状态机的引用。在实际使用中,大多数情况下都不需要继承 State 类了,转而为各个状态的委托初始化,用来初始化的所有方法可以直接写在状态机类里,因此这些方法可以直接访问状态机的所有成员。为了更方便在 Unity 中使用,状态机继承了 Monobehaviour,你可以根据实际需求选择不同的更新模式。另外为了方便编程添加了状态计时。

如果状态类还是需要多层次的继承呢?委托的好处之一就是自由。在继承的类里,你可以获得、替换或增加委托的方法,还有什么是不可实现的?

时间: 2024-10-29 04:58:43

简单实用的 Unity 状态机设计的相关文章

C# Ioc容器Unity,简单实用

原文:C# Ioc容器Unity,简单实用 开头先吐槽一下博客园超级不好用,添加图片后就写不动字了,难道是bug 好进入正题,先来说下依赖注入,简单来说就是定义好接口,上层代码调用接口,具体实现通过配置文件方式去指定具体实现类. 首先我们需要通过nuget来安装unity 安装好后就可以写一个方法类去实现依赖注入 public class UnityIocHelper : IServiceProvider { private readonly IUnityContainer _container

简单实用的CSS网页布局中文排版技巧

由于汉字的特殊性,在css网页布局中,中文排版有别于英文排版.排版是一个麻烦的问题,小编认为,作为一个优秀的网页设计师和网页制作人员,掌握一些简单的中文排版技巧是不可或缺的,所以今天特意总结了几个简单实用的技巧,希望对大家有所帮助. 一.如何设定文字字体.颜色.大小等 font-style设定斜体,比如font-style:italic font-weight设定文字粗细,比如font-weight:bold font-size设定文字大小,比如font-size:12px line-heigh

MiS603 开发板2.3 状态机设计

作者:MiS603开发团队 日期:20150911 公司:南京米联电子科技有限公司 论坛:www.osrc.cn 网址:www.milinker.com 网店:http://osrc.taobao.com EAT博客:http://blog.chinaaet.com/whilebreak 博客园:http://www.cnblogs.com/milinker/ 2.3 状态机设计 状态机是许多数字系统的核心部件,是一类重要的时序逻辑电路.通常包括三个部分:一是下一个状态的逻辑电路,二是存储状态机

08-FPGA状态机设计实例——小梅哥FPGA设计思想与验证方法视频教程配套文档

芯航线--普利斯队长精心奉献 ? 实验目的:1.学习状态机的相关概念 2.理解一段式.两段式以及三段式状态机的区别以及优缺点 实验平台:芯航线FPGA核心板 实验原理: 状态机全称是有限状态机(finite-state machine,缩写:FSM)是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型. 状态机分为摩尔(Moore)型有限状态机与米利(Mealy)型有限状态机.摩尔状态机输出是只由输入确定的有限状态机(不直接依赖于当前状态).米利有限状态机的输出不止与其输入有关还于它的

简单实用的PHP防注入类实例

这篇文章主要介绍了简单实用的PHP防注入类实例,以两个简单的防注入类为例介绍了PHP防注入的原理与技巧,对网站安全建设来说非常具有实用价值,需要的朋友可以参考下 本文实例讲述了简单实用的PHP防注入类.分享给大家供大家参考.具体如下: PHP防注入注意要过滤的信息基本是get,post,然后对于sql就是我们常用的查询,插入等等sql命令了,下面我给各位整理两个简单的例子,希望这些例子能给你网站带来安全. PHP防注入类代码如下: 复制代码 代码如下: <?php /**  * 参数处理类  *

微信公众平台之超简单实用的天气预报后台实现

微信公众平台之超简单实用的天气预报后台实现 概述,前段时间我在开发一个自己的微信公众平台,需要实现天气预报功能,在网上度娘了下,实现天气预报的接口API还蛮多的,有:中国气象局.雅虎和新浪等,中国天气预报接口需要全国的编码,雅虎的有时候访问不了,研究了下还是新浪提供的接口比较简单实用.新浪天气预报API的URL是http://php.weather.sina.com.cn/xml.php?city=%B1%B1%BE%A9&password=DJOYnieT8234jlsK&day=0.其

FMDB数据库的简单实用

引入和FMDB第三方类库,  demo地址:http://pan.baidu.com/s/1c0pbfxA 1 #define dataBasePath [[(NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)) lastObject]stringByAppendingPathComponent:dataBaseName] 2 #define dataBaseName @"MyDatabase

Java对象简单实用(计算器案例)

Java对象简单实用(计算器案例) 对 Java中的对象与属性,方法的使用,简单写了个案例 1 import java.util.Scanner; 2 class Calculste 3 { 4 int a; //定义两个整数 5 int b; 6 String option; //定义接收操作符的字符串 7 public void count(){ 8 9 //对操作符进行判断 10 switch(option){ 11 case "+": 12 System.out.println

php简单实用的操作文件工具类(创建、移动、复制、删除)

php简单实用好用的文件及文件夹复制函数和工具类(创建.移动.复制.删除) function recurse_copy($src,$dst) {  // 原目录,复制到的目录 $dir = opendir($src); @mkdir($dst); while(false !== ( $file = readdir($dir)) ) { if (( $file != '.' ) && ( $file != '..' )) { if ( is_dir($src . '/' . $file) )