游戏设计模式——状态机模式

前言:状态机模式是一个游戏常用的经典设计模式,常被用作处理一个生物的各种状态(例如行走,站立,跳跃等)。

假如我们正在开发一款动作游戏,当前的任务是实现根据输入来控制主角的行为——当按下B键时,他应该跳跃。

直观的代码:

    if (input == PRESS_B) {
        if (!m_isJumping) {
            m_isJumping = true;
            Jump();//跳跃的代码
        }
    }

后来我们需要添加更多行为了,所有行为如下:

站立时按下 ↓ 键 =》 蹲下。

蹲下时按下 ↓ 键 =》 站立。

站立时按下 B 键 =》 跳跃。

跳跃时按下 ↓ 键 =》触发 俯冲。

    if (input == PRESS_B) {
        //如果在站立时且没在跳跃,则跳跃
        if (!m_isJumping && m_isStanding) {
            m_isJumping = true;
            player.jump();//跳跃的代码
        }
    }
    else if (input == PRESS_DOWN) {
        //如果在跳跃时且没在俯冲,则俯冲
        if (m_isJumping && !m_isDiving) {
            m_isDiving
            player.dive();//俯冲的代码
        }
        //如果没在跳跃
        else if (m_isJumping) {
            //如果站立时,则蹲下
            if (m_isStanding && !m_isJumping) {
                m_isStanding = false;
                player.sneak();//蹲下的代码
            }
            //如果蹲下时,则站立
            else {
                m_isStanding = true;
                player.stand();//站立的代码
            }
        }
    }

可以看到一堆if-else语句非常复杂,要是添加更多行为,其逻辑结构更加难以维护,而且主角的代码又得重新编译(耦合性大)

有限状态机

有限状态:有限数量的状态。

一个可行的办法是将这些 状态&&状态切换&&状态对应的行为 封装成类,

(如下图)

这时候可以借助状态机这个设计模式来美化这段代码。

上面的场景中,只有4个状态(跳跃/下蹲/站立/俯冲),这就是有限状态。

于是我们设计出下面4个状态类(加一个状态的接口类)

//状态接口类
class State {
public:
    //处理输入,然后根据输入转换相应的状态
    virtual void handleInput(Player& player,const Input& input) = 0;
};
//站立状态
class StandState : public State {
public:
    void handleInput(Player& player, const Input& input) override{
        if (input == PRESS_B) {
            player.jump();//角色跳跃的代码
            player.setState(JumpState());
        }
        else if (input == PRESS_DOWN) {
            player.sneak();//角色蹲下的代码
            player.setState(SneakState());
        }
    }
};
//跳跃状态
class JumpState : public State {
public:
    void handleInput(Player& player, const Input& input) override {
        if (input == PRESS_DOWN) {
            player.dive();//角色俯冲的代码
            player.setState(DiveState());
        }
    }
};
//下蹲状态
class SneakState : public State {
public:
    void handleInput(Player& player, const Input& input) override {
        if (input == PRESS_DOWN) {
            player.stand();//角色站立的代码
            player.setState(StandState());
        }
    }
};
//俯冲状态
class DiveState : public State {
public:
    void handleInput(Player& player, const Input& input) override {
    }
};

第一次进入游戏时,给角色一个初始状态

player.setState(StandState());

然后每次接受输入,让角色当前的状态对象去处理就可以了。

player.getState().handleInput(player,input);

简单小结:

可以看到利用状态类对象,我们把负责的条件逻辑封装到各个状态类里,让代码变得优雅,而且还减少了几个变量的使用(m_isJumping等)。

此外由于有限状态对象的属性是固定不变的,这意味着所有角色都能共享同一个状态(当同种状态时),

所以常见的状态对象存储方式是单例存储或者静态存储(每种状态只生成1个对象),避免了上文每次都要生成新状态对象的开销。

有限状态机的更多改良改进

平行的状态机

实际中,一些游戏的类可能需要多个状态(平行关系),于是可以写出以下代码

class Player{
    State* m_bodyState;//身体状态
    State* m_equipmentState;//装备状态
    //.....其它代码
};

然后便可以用下列方式处理状态了

void Player::handleInput(const Input& input) {
    m_bodyState->handleInput(*this,input);
    m_equipmentState->handleInput(*this,input);
}

层次状态机

把主角的行为更加具象化以后,可能会包含大量相似的状态,为了重用代码,便衍生层次状态机的概念。

层次状态主要思想是状态类继承,从而产生层次关系的状态。

例如,蹲下状态和站立状态 继承于 在地面状态。

class OnGroundState : public State {
    void handleInput(Player& player, const Input& input) override {
        if (input == PRESS_B){}//....跳跃
    }
};

class StandState : public OnGroundState {
    void handleInput(Player& player, const Input& input) override {
        //当松开↓键,才蹲下去
        if (input == RELEASE_DOWN) {}//...蹲下去的代码...
        else {OnGroundState::handleInput(player,input);}
    }
};

class SneakState : public OnGroundState {
    //当松开↓键,才站起来
    if (input == RELEASE_DOWN) {}//...站起来的代码...
    else {OnGroundState::handleInput(player, input);}
};

下推状态机

下推状态机,简单来说,就是用栈结构存储一系列状态对象。

一般来说,一个角色只需要一个状态对象,为什么要用栈结构存储一堆状态对象?

假设有一个射击游戏的角色,他现正在站立状态,执行栈顶状态中。

突然遇到敌人进行开火,于是入栈一个开火状态,并继续执行新的栈顶状态。

敌人被击中死亡,开火状态结束。为了恢复到开火前的上一个状态,于是去掉栈顶状态。

这样我们利用栈就完美模拟了一个人开火之后恢复成站立状态的过程。

简单来说,

下推自动机适用于需要记忆状态的状态机,这在一些游戏AI是常用的手法。(不过现在更流行的游戏AI是用行为树实现)

原文地址:https://www.cnblogs.com/KillerAery/p/9680303.html

时间: 2024-08-02 17:47:11

游戏设计模式——状态机模式的相关文章

游戏设计模式——黑板模式

"黑板"(Blackboard)在人工智能领域已经是一个很古老的东西了.它基于一种很直观的概念,就是一群人为了解决一个问题,在黑板前聚集, 每个人都可以发表自己的意见,然后在黑板上写下自己的看法,当然你也可以基于别人记录在黑板上的看法, 来发表和更新自己的看法,在这样不断的意见交换,看法更新的过程中,越来越趋向于对于问题的最终解答. 一开始的黑板模式就是这样一个由多个子系统来共同协作的人工智能解决方案. 定义 基于上面的描述,我们可以看到黑板有几个功能: 记录:每个人可以写下自己的看法

【游戏设计模式】之四 《游戏编程模式》读书笔记:全书内容梗概总结

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/53240330 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文的Github版本:QianMo/Reading-Notes/<游戏编程模式>读书笔记 这是一篇超过万字读书笔记,总结了<游戏编程模式>一书中所有章节与内容的知识梗概. 我们知道,游戏行业其实一直很缺一本系

状态机模式

现在需要你做一个简单是视频播放器的APP,主要有播放,暂停,停止三个功能,在没学状态机模式之前,你可能会这样来实现: 现抽象个IPlayer接口,定义好你的播放器需要实现的动作和可能的状态字段: 01.1 public interface IPlayer { 02.2     public static final int STATE_PLAYING = 1; 03.3     public static final int STATE_PAUSED = 2; 04.4     public s

23种设计模式----------代理模式(二)

(上一篇)23种设计模式----------代理模式(一) 之前说了基本的代理模式和普通代理模式.接下来开始看下强制代理模式和虚拟代理模式 三,强制代理模式: 一般的代理模式都是通过代理类找到被代理的对象,从而调用被代理类中的方法(即完成被代理类中的任务). 而,强制代理模式则是先找到被代理类自己去完成事情,然后被代理类又将该做的事情转交到代理类中,让代理类来完成. 假如:你有事求助于某位名人. 你告诉名人说有事想请他帮忙,然后他说最近一段时间比较忙,要不你找我的经纪人来办吧. (本来找名人办事

23种设计模式----------代理模式(三) 之 动态代理模式

(上一篇)种设计模式----------代理模式(二) 当然代理模式中,用的最广泛的,用的最多的是  动态代理模式. 动态代理:就是实现阶段不用关系代理是哪个,而在运行阶段指定具体哪个代理. 抽象接口的类图如下: --图来自设计模式之禅 所以动态代理模式要有一个InvocationHandler接口 和 GamePlayerIH实现类.其中 InvocationHandler是JD提供的动态代理接口,对被代理类的方法进行代理. 代码实现如下 抽象主题类或者接口: 1 package com.ye

23种设计模式----------代理模式(一)

代理模式也叫委托模式. 代理模式定义:对其他对象提供一种代理从而控制对这个对象的访问.就是,代理类 代理 被代理类,来执行被代理类里的方法. 一般情况下,代理模式化有三个角色. 1,抽象的主题类(或者接口) IGamePlayer 2,代理类. 3,被代理类. 下面以游戏玩家代理为例. 一,先来看下最基本的代理模式. 代码如下: 主题接口: 1 package com.yemaozi.proxy.base; 2 3 //游戏玩家主题接口 4 public interface IGamePlaye

16. 星际争霸之php设计模式--组合模式

题记==============================================================================本php设计模式专辑来源于博客(jymoz.com),现在已经访问不了了,这一系列文章是我找了很久才找到完整的,感谢作者jymoz的辛苦付出哦! 本文地址:http://www.cnblogs.com/davidhhuan/p/4248201.html============================================

设计模式-Decorator模式

目录 一个例子(贪玩蓝月) 传统继承实现 装饰器模式实现 对比 总结 Decorator(装饰器)模式属于结构型模式. 比如当其需要三种不同的附加特性,可以为其创建三个派生类.但是若它还需要同时具有其中两种特性或者是各种特性的任意组合的时候,类继承的方法就不再适合了. 它允许向一个现有的对象不通过继承来添加新的功能,同时又不改变其结构. 一个例子(贪玩蓝月) 前一阵子张家辉代言的<贪玩蓝月>广告火了,"我系喳喳辉,是兄弟就来砍我-"被洗脑到现在,正好用这个游戏来解释一下装饰

游戏设计模式——面向数据编程(新)

目录 面向数据编程是什么? 单指令流多数据流(SIMD) 什么是SIMD 为什么需要SIMD 支持SIMD技术的指令集 使用SIMD编程 使用汇编内联 使用指令集库 使用ISPC语言 并行循环 避免Gather行为 CPU缓存(CPU cache) 什么是CPU缓存 为什么需要CPU缓存 CPU缓存预先存的是什么 CPU缓存命中/未命中 提高CPU缓存命中率 使用连续数组存储要批处理的对象 避免无效数据夹杂在连续内存区域 冷数据/热数据分割 频繁调用的函数尽可能不要做成虚函数 重新认识C++ S