• 简述游戏开发中的状态机


    为什么我们需要状态机

    实行较多状态的角色,把动作全写在一个部分中会导致维护成本高,拓展性低
    例如:走路,跳跃,射击,躲避的相互转换,有些可以转换,有些不能,实现逻辑复杂
    (满屏幕都是if - else)

    状态模式switch实现

    //包含着所有的状态
    enum class State{StateA, StateB, StateC, ...} activeState;
    ...
    //通过switch语句切换状态,根据具体情况实现细节
    switch (activeState)
    {
        case State.StateA:
        	...
        	break;
        case State.StateB:
        	...
        	break;
        .......
    }
    ...
    

    状态机的原形,用一个枚举表示当前的状态,通过填充完善switch语句实现状态之间的切换,但是依然有维护成本高拓展低的缺点(虽然确实是比用if - else堆好)

    Finite State Machine(FSN)有限状态机

    最基本的状态机,一般来说其他状态机都是这种状态机的变体
    对于状态机的理解,最好就是画个图(如图结构,方框是状态,箭头是状态之间的联系)

    有限状态机强调的是状态之间的切换,以及对不同状态的封装,所以实现方法一般可以根据需求调整
    以下参考了《游戏人工智能》的实现
    首先需要个基类State和基类Translation

    //状态基类,所有状态都继承这个类
    class State
    {
    public:
    	virtual ~State(){}
    	virtual OnStateEnter(){}	//进入此状态执行一次
    	virtual OnUpdate(){}		//每一帧执行一次
    	virtual OnStateExit(){} 	//跳出状态时调用一次
    	list<Translation> translations;	//状态迁移列表
    };
    
    //状态迁移
    class Translation
    {
    public:
    	virtual ~Translation(){}
    	virtual bool isValid() = 0;			//用于判定迁移,可切换返回true
    	virtual State* getNextState() = 0;	//进入下一个状态
    	virtual void onTransition(){}		//迁移时调用
    };
    

    State是每一个状态都会继承的基类,translations中存着他指向其他状态的Translation,通过每一帧遍历所有Translation的isValid()来判定是否可以跳转,若可以跳转则执行自身的OnStateExit(),并将当前的状态设置为getNextState()获得的状态

    然后还有状态机类,用来管理所有状态:

    //状态机类
    class FiniteStateMachine
    {
    public:
    	void Update();			//每一帧运行一次
    	State* initialState;	//初始状态
    	State* activeState;		//正在运行的状态
    	
    protected:
    	list<State> states;		//所有状态的实例
    };
    
    void FiniteStateMachine::Update()
    {
        //遍历活动状态的所有迁移,若有可用的,则切换状态
        list<Translation>::iterator itr = activeState->translations.begin();
        for (int i = 0; i < activeState->translations.size(); ++i, ++itr)
        {
            if (itr->isValid())
            {
                activeState->OnStateExit();		//退出调用
                activeState = itr->getNextState();	//切换活动状态
                itr->onTransition();                //切换调用
                activeState->OnStateEnter();	//进入调用
                return;			        //直接返回
            }
        }
        //如果没有状态切换,运行一次update
        activeState->OnUpdate();
    }
    
    

    接下来具体的状态细节就要具体继承,具体实现

    Hierarchical Finite State Machine (HFSM) 分层状态机

    分层状态机相当于对有限状态机的进一步封装,将复数个状态封装成一个大的状态,再用一个“历史状态”记录切出此状态时运行的子状态,就可以在大状态间切换(结构如图)

    图中将StateA和StateB加入到一个更高层的状态StateD中,只需退出时记录状态,就可以在CD状态间切换,增加了状态C的复用性(省略了AC,BC间的联系)
    主要思想一是包装更高的层次,二是高层的切换放到更高层来觉得以提高复用
    分层状态机的实现主要分两种:
    一、父状态就是一个状态机,可以往里面添加状态
    二、添加一个状态栈储存父子状态,每一个状态都是栈下一个状态的子状态,进入状态时入栈,结束状态时出栈并发送一个消息,新栈顶状态不能处理就再出栈,直到栈顶状态能处理这个消息为止。

    并发状态机

    并发状态机可以理解为一个对象拥有两个同时运作的状态机,两个状态机相互独立(但可以通过修改对象的状态进行通信),例如我们可以将腿部动作(站立,下蹲,奔跑)和手部动作(持枪,空手,瞄准)分离。

    下推状态机

    下推状态机的核心是他有一个状态栈(但和层次状态机实现的栈完全不同,层次状态机的栈是为了记录父状态,这个栈是为了纪录上一个状态)。
    状态栈主要解决的是状态机无法得到上一轮执行的状态。
    实现时,在状态机中加一个栈,在进入状态时将状态入栈,退出状态时将状态出栈,运行时则运行栈顶的状态。
    例如,我在做作业的时候饿了,想去吃东西。饿了去吃东西是多数状态都能进入的状态,并且结束“吃东西”的状态后我要回复原来的状态。

    在运行时,状态机已经将“做作业“入栈
    然后“我饿了”,将“吃东西”入栈
    “吃东西”结束,将“吃东西”出栈
    继续运行栈顶的“做作业”(事实上不可能)

    若有错误,欢迎指出

  • 相关阅读:
    功夫世界外挂发布测试
    k8s和docker日常使用命令
    notepad添加JSON插件菜单栏不显示的问题
    k8s中的网络
    MySQL日志系统之redo log和bin log
    OCR文字识别开源方案本地部署 hello
    .NET Core WebAPI post参数传递时后端的接收方式
    sqlserver 数据库自动备份
    对EF中5种实体状态System.Data.EntityState的一点理解
    造成跨域的原因和解决方法
  • 原文地址:https://www.cnblogs.com/yasoudream/p/11802866.html
Copyright © 2020-2023  润新知