51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2247|回复: 0
打印 上一主题 下一主题

状态驱动的游戏智能体设计(下)

[复制链接]
  • TA的每日心情
    慵懒
    2015-1-8 08:46
  • 签到天数: 2 天

    连续签到: 1 天

    [LV.1]测试小兵

    跳转到指定楼层
    1#
    发表于 2007-11-30 14:50:02 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    编写可重用的State基类

    译者并示取得中文版的翻译授权,翻译本文只是出于研究和学习目的。任何人不得在未经同意的情况下将英文版和中文版用于商业行为,转载本文产生的法律和道德责任由转载者承担,与译者无关。

    作为立足之本,有必要构造一个独立的State基类,以供每一个角色类类型获得自身的状态。我们可以通过类模板来使得它可重用:
    template <class entity_type>
    class State
    {
    public:

    virtual void Enter(entity_type*)=0;

    virtual void Execute(entity_type*)=0;

    virtual void Exit(entity_type*)=0;

    virtual ~State(){}
    };


    下面是Miner类的EnterMineAndDigForNugget状态:
    class EnterMineAndDigForNugget : public State<Miner>
    {

    public:

    /* OMITTED */
    };

    如你所见,它短小精悍。

    Global States and State Blips
    全局状态和状态闪动(诚心求更好的译法)

    通常当设计有限状态机的时候,你最后都会在所有状态中出现重复代码。例如,在Maxis开发的流行游戏《The Sims(第二人生)》中,Sim可以感受到内急等生理需要,必须去洗手间解决。无论Sim在哪里、在什么时间,内急都可能发生。根据当前的设计,给淘金者加上这样一种行为,重复的条件逻辑就可能增加到每一个状态,或者放到Miner::Update函数里。下面介绍一个可接受的解决方案,它增加了一个全局状态——供FSM更新的时候调用。这样,FSM的所有的逻辑都包含在状态内,而不在智能体类的FSM里。

    实现全局状态,需要增加一个成员变量:
    //notice how now that State is a class template we have to declare the entity type
    State<Miner>* m_pGlobalState;

    有时智能体从一个状态进入另一个状态,当它退出这个状态时需要回到它的前一个状态,我将之称为状态闪动。例如就像《The Sims》中你可能必须让你的智能体能够在任何时间进入洗手间,之后再回到之前的状态。要实现这样的功能,就必须记录前一个状态,以便在状态闪动时返回。这可以容易地通过增加成员变量和对Miner::ChangeState方法增加一些额外逻辑来实现。
    [译注:状态闪动这一概念的确比较难以理解。所以我画了下面这一张图来帮助理解]
    译注图1状态闪动示意图

    到现在,为了完成这些额外功能,Miner类已经增加了两个成员变量和一个方法。它最后看起来就像这样(忽略无关元素):
    class Miner : public BaseGameEntity
    {
    private:

    State<Miner>*   m_pCurrentState;
    State<Miner>*   m_pPreviousState;
    State<Miner>*   m_pGlobalState;
    ...

    public:

    void ChangeState(State<Miner>* pNewState);
    void RevertToPreviousState();
    ...
    };
    Hmm, looks like it’s time to tidy up a little.
    嗯,的确需要整理一下。
    创建一个状态机类
    把所有的状态有关的数据和方法封装到一个状态机类里有利于精简设计。这使智能体能够拥有一个状态机实例并委派它管理当前状态、全局状态和前一个状态。

    现在来看看StateMachine模板类。
    template <class entity_type>
    class StateMachine
    {
    private:

    //a pointer to the agent that owns this instance
    entity_type*          m_pOwner;

    State<entity_type>*   m_pCurrentState;

    //a record of the last state the agent was in
    State<entity_type>*   m_pPreviousState;

    //this state logic is called every time the FSM is updated
    State<entity_type>*   m_pGlobalState;

    public:

    StateMachine(entity_type* owner):m_pOwner(owner),
                                       m_pCurrentState(NULL),
                                       m_pPreviousState(NULL),
                                       m_pGlobalState(NULL)
    {}

    //use these methods to initialize the FSM
    void SetCurrentState(State<entity_type>* s){m_pCurrentState = s;}
    void SetGlobalState(State<entity_type>* s) {m_pGlobalState = s;}
    void SetPreviousState(State<entity_type>* s){m_pPreviousState = s;}

    //call this to update the FSM
    void Update()const
    {
        //if a global state exists, call its execute method
        if (m_pGlobalState)   m_pGlobalState->Execute(m_pOwner);

        //same for the current state
        if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner);
    }

    //change to a new state
    void ChangeState(State<entity_type>* pNewState)
    {
        assert(pNewState &&
               "<StateMachine::ChangeState>: trying to change to a null state");

        //keep a record of the previous state
        m_pPreviousState = m_pCurrentState;

        //call the exit method of the existing state
        m_pCurrentState->Exit(m_pOwner);

        //change state to the new state
        m_pCurrentState = pNewState;

        //call the entry method of the new state
        m_pCurrentState->Enter(m_pOwner);
    }

    //change state back to the previous state
    void RevertToPreviousState()
    {
        ChangeState(m_pPreviousState);
    }

    //accessors
    State<entity_type>* CurrentState() const{return m_pCurrentState;}
    State<entity_type>* GlobalState()   const{return m_pGlobalState;}
    State<entity_type>* PreviousState() const{return m_pPreviousState;}

    //returns true if the current state’s type is equal to the type of the
    //class passed as a parameter.
    bool isInState(const State<entity_type>& st)const;
    };
    现在所有的智能体都能够拥有一个StateMachine实例,需要做的就是实现一个方法来更新状态机以获得完整的FSM功能。

    新实现的Miner类看起来就是这样的:
    class Miner : public BaseGameEntity
    {
    private:

    //an instance of the state machine class
    StateMachine<Miner>* m_pStateMachine;

    /* EXTRANEOUS DETAIL OMITTED */

    public:

    Miner(int id):m_Location(shack),
                    m_iGoldCarried(0),
                    m_iMoneyInBank(0),
                    m_iThirst(0),
                    m_iFatigue(0),
                     BaseGameEntity(id)

    {
        //set up state machine
        m_pStateMachine = new StateMachine<Miner>(this);

        m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
        m_pStateMachine->SetGlobalState(MinerGlobalState::Instance());
    }

    ~Miner(){delete m_pStateMachine;}

    void Update()
    {
       ++m_iThirst;
        m_pStateMachine->Update();
    }

    StateMachine<Miner>* GetFSM()const{return m_pStateMachine;}

    /* EXTRANEOUS DETAIL OMITTED */
    };
    注意StateMachine实例化后如何正确设计当前和全局状态。图2.4是现在的类层次结构图。
    Figure 2.4. The updated design

    介绍Elsa

    为了验证这些改进,我创建了一个新的项目——WestWorldWithWoman。在这个项目里,WestWorld多了一个人物——Elsa,她是淘金者Bob的妻子。Elsa做的事不多,主要是打扫房子和上洗手间(她喝了太多咖啡)。图2.5Elsa的状态转换图。
    Figure 2.5. Elsa’s state transition diagram. The global state is not shown in the figure because its logic is effectively implemented in any state and never changed.

    当你把这个项目导入到你的IDE的时候,注意VisitBathroom状态是如何以状态闪动的形式实现的(也就是它如何返回到前一个状态),同样值得注意的是定义了一个全局状态——WifesGlobalState,它包含了Elsa上洗手间所需的逻辑。在全局状态中包含这一逻辑是因为Elsa可能在任何时候都会感到内急,这是天性,哈哈。

    这里是WestWorldWithWoman项目的输出示例。
    Miner Bob: Pickin' up a nugget
    Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
    Miner Bob: Goin' to the bank. Yes siree
    Elsa: Walkin' to the can. Need to powda mah pretty li'l nose
    Elsa: Ahhhhhh! Sweet relief!
    Elsa: Leavin' the john
    Miner Bob: Depositin' gold. Total savings now: 4
    Miner Bob: Leavin' the bank
    Miner Bob: Walkin' to the gold mine
    Elsa: Walkin' to the can. Need to powda mah pretty li'l nose
    Elsa: Ahhhhhh! Sweet relief!
    Elsa: Leavin' the john
    Miner Bob: Pickin' up a nugget
    Elsa: Moppin' the floor
    Miner Bob: Pickin' up a nugget
    Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
    Miner Bob: Boy, ah sure is thusty! Walkin' to the saloon
    Elsa: Moppin' the floor
    Miner Bob: That's mighty fine sippin' liquor
    Miner Bob: Leavin' the saloon, feelin' good
    Miner Bob: Walkin' to the gold mine
    Elsa: Makin' the bed
    Miner Bob: Pickin' up a nugget
    Miner Bob: Ah'm leavin' the gold mine with mah pockets full o' sweet gold
    Miner Bob: Goin' to the bank. Yes siree
    Elsa: Walkin' to the can. Need to powda mah pretty li'l nose
    Elsa: Ahhhhhh! Sweet relief!
    Elsa: Leavin' the john
    Miner Bob: Depositin' gold. Total savings now: 5
    Miner Bob: Woohoo! Rich enough for now. Back home to mah li'l lady
    Miner Bob: Leavin' the bank
    Miner Bob: Walkin' home
    Elsa: Walkin' to the can. Need to powda mah pretty li'l nose
    Elsa: Ahhhhhh! Sweet relief!
    Elsa: Leavin' the john
    Miner Bob: ZZZZ...


    夸张点说,这相当了不起啊。你能够用有限状态机创造非常复杂的行为,到底有多复杂仅受限于你的相像力。你无需限制你的游戏智能体只能有一个有限状态机,有时候你可以使用两个FSM来并行工作:一个控制角色的移动,而另一个控制武器选择、瞄准和开火。你甚至可以创造包含状态机的状态机,即分级状态机。例如你的游戏智能体可能有Explore(探测)、Combat(战斗)和Patrol(逻辑)等状态,而Combat(战斗)状态又可以拥有一个状态机来管理Dodge(躲避)、ChaseEnemy(追逃)和Shoot(射击)等战斗时需要的状态。
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-16 09:35 , Processed in 0.074871 second(s), 27 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表