卅川的状态机之路(创作中,不定时上传)

川的第一篇干货,将从讲述FSM(有限状态机)开始。

川第一次接触状态机这种东西,还得追溯到刚到畅游工作,破解了别的游戏的代码(游戏程序就是这么没节操和底线,嗯!)才知道有这么个东西的。虽然大学学习过相关思想,可是第一次见了真材实料还是很震撼的(请原谅我用"震撼"这个词,因为我理想中的程序,就应该和机械一样各司其职分工明确,然后才能正确运行得到结果)。

现在有很多论调,当然集中的也只是一些特定的环境下,有限状态机已经不合时宜了,觉得低效且无法完整实现功能(比如最近就看过几篇文章,关于游戏AI,大家似乎对之前使用FSM去实现很有些看法)。可是正如排序算法一样,快排再快,也一样有去写选择排序的人(仅仅是打比方,非要跟我掰扯快排和选择的速度和应用场景我也没办法)。FSM并不一定执行效率最高,但是以工程的角度来看,FSM却可以比较清晰地阐述程序运行流程(请注意"比较",因为我也见过一个总逻辑分成2-30个状态的,那就简直是天书了)。

什么是FSM

所以,来看一个核心问题:什么是有限状态机?川不想去网上搬一堆关于有限状态机的定义,他们都对,但是并不是我的思想。

FSMFinite-state machine)是有限状态机的缩写,全称中的machine,那可是机器啊喂!所以还想什么呢,FSM就是一台用代码构建的机器呀(不得不说,我到现在都对工程制图学念念不忘,漂亮机械图纸在我眼中就是艺术品)!既然是机器,它就会运转,去完成它在被设计制造出来之后被赋予的任务。举个栗子:城市道路都愿意开自动挡(因为省事儿呀,面对堵车时候那一脚又一脚的离合绝对是对左腿强有力的锻炼),它的变速箱就是一台机器,它需要在我们开车时根据我们行驶的路况、速度变更不同的挡位(否则要么发动机带不动汽车,要么就等着烧机油吧,哈哈)。

好吧,扯了一堆有的没的。状态机就是在不同的状态不同的条件下,去执行对应状态或条件下代码的机器(如果你不同意我说的,那我也没办法,摊手)。

Unity开发中,Mecanim使用的就是状态机结构。这一点其实很方便初学者理解,而且创作者使用时也对动作可以有更清晰的认识。简单的说,一个模型同一时间肯定是在做一件事情,它可没那精力边打篮球边坐着看书(这么说有点绝对,毕竟从开发的角度,动作融合也是可以做到的,否则怎么边移动边攻击呢)。所以用状态的思路去解释,就可以很明确的知道当前需要做什么,甚至刚进入的时候需要做什么,做完了这件事需要做什么都一清二楚(比如看书前先找到书,篮球打完后要洗澡,这些都属于当前状态的一部分)。不过也就是在这里,川见识了一组状态机几十个状态跳转,我不得不说那个样子很丑(我不确定逻辑丑不丑,但是Unity在状态太多的情况下的显示实在不敢让人恭维),可是实现的功能确实很多,可以让一个角色模型如同活人一般做出各种各样的姿势(模型不是女的,此处也没有相机)。

说到这里,其实连我自己都想吐槽:这也太片面了!事实也确实如此,状态机能做的事情太多了,硬件底层通讯的实现流程、语义识别、游戏AI、工程项目逻辑,这些都有状态机出过力,有些甚至离开还不行。不过那也就不是川自己的东西了,川只从做游戏这里讲起(毕竟不是那一行,我怎么知道人家核心逻辑怎么回事,只好先学好自己的内容喽)。

游戏中FSM的本质是什么?

如果我们的程序永远和学校学习时一样,就是一条单线程在有限的几个时钟周期内运行得到结果,就比如求一串斐波那契数列,那你用状态机思想简直就是烧包,我们没有必要将简单的问题复杂化(不过不得不承认,在写到这里的时候,我真的在想如果用FSM实现这些算法会是什么样子,但我相信我写出来的去比赛一定会超时)。

套用Unity底层的设计思想,其实游戏也是单线程,但是最大的不同就在于:游戏有心跳!你没看错,游戏也是有生命的,它也有自己的心跳,甚至心律不齐(帧不稳定)都是容易出问题的。生物的心脏究竟在做什么我就不说了,可是游戏要心跳干嘛?当然是渲染啦!游戏的一切都在围绕渲染执行,无论你用了多么牛的技术,用了多么棒的架构,想了多么超前的玩法,你终究只是为了渲染而存在。你需要在最短的时间内知道现在应该展示什么,你需要在最短的时间内将需要展示的事物渲染成一张图片供盯着屏幕的那双眼睛接收,而且一定要快,否则那不是游戏,那只是快速翻页的连环画。

好吧,扯远了。。。

游戏的心跳其核心就是实时计算,以展示没有延时的图像信息。最直观的就是倒计时功能。心跳计算当前倒计时还有多久并显示,每一次心跳都在更新这一值以及该值的显示。那么如果我们的倒计时有多个,每个需要在特定的情况下计时呢?就好比围棋选手下棋有时间限制需要计时,下棋的一方倒计时,另一方不论正在干什么都不计时。让我们看一段小代码

bool leftPlaying;
float leftTime, rightTime;

void Tick(float deltaTime)
{
    if (leftPlaying) {
        leftTime -= deltaTime; 
    } else { 
        rightTime -= deltaTime; 
    }
}


所以这段代码究竟做了什么?我们可以看到这段代码的核心就是根据那个bool值判断是不是左手选手下棋,是则给左手选手进行倒计时,否则就是右手选手倒计时。

那么我们可以再想一个问题,如果我们要来个6人跳棋计时器,肿么搞?6bool值?或者枚举定义6名选手?随意啦,反正这样的写法你只是需要有东西能区分是谁在下棋而已。不过从人类偷懒的天性来说,一个enum变量总好过6bool变量好管理。所以你的代码又会如何?

(假装此处有6人跳棋计时器的初版代码)

所以依稀可以看出,这个计时器代码所做的事情其实就是通过区分当前是谁在下棋,就对谁进行倒计时。而这个恰恰就是FSM的本质:在对应的状态做该状态应该做的事。所以我们简单点理解,FSM就是一个大switch代码块。

为什么要用FSM

当我们回头审视计时器的代码,其实真的是再简陋不过了,否则我们只有减时间没有重置时间。我想没有谁会蠢到最先几步棋就花掉个把小时,规则也不会允许这样的事情发生。

好吧,麻烦来了,我们需要在轮到某人开始下棋的时候对上一位选手的倒计时进行重置。或者也可以轮到谁就重置谁,然后再对他进行倒计时。这只是区分的不同罢了,具体决定权在你(开发游戏有时候可不这么好说话,有时候仅仅是这么一个先手顺序的区分,时间就可能因为差出去几个时钟周期而导致游戏运行结果的面目全非)。

 

(撰写中,未完待续)

posted @ 2017-06-12 07:27  卅川  阅读(218)  评论(0)    收藏  举报