此外,我们有时候需要在一种状态下显示另一种状态。比如在pause menu中显示暂停选项的时候仍然显示游戏背景(用某种颜色的全屏幕半透明矩形覆盖使其暗化,并且游戏逻辑此时不会更新)
这意味着给state增加一个parent pointer会很方便:
[cpp]
class GameState
{
// ...as above
public:
void setParent( GameState* state ) { m_parent = state; }
GameState* getParent() { return m_parent; }
private:
GameState* m_parent;
};
这样,我们可以这样实现pause menu的draw方法:
[cpp]
void GameState_PauseMenu::draw( GraphicsContext& g )
{
m_parent->draw( g );
draw the transparent mask layer...
draw pause menu items...
}
我们首先渲染parent,对于pause menu状态来说,它的parent就是in-game状态。然后渲染半透明覆盖层。最后渲染pause menu的选项。
此外,parent pointer对于状态的转换也是非常方便的。
为了能够方便地操纵游戏状态在状态树上进行转换,我们扩展manager类:
[cpp]
class StateMgr
{
// ...as above
public:
enum StateOP
{
STATE_OP_PUSH = 0,
STATE_OP_POP,
};
public:
void changeState( GameState* newState, int op )
{
if( op == STATE_OP_PUSH )
{
newState->setParent( m_curState );
m_curState = newState;
}
else if( op == STATE_OP_POP )
{
m_curState = m_curParent->getParent();
}
}
};
我们增加了state操作方法StateMgr::changeState并通过两个操作类型:push和pop,可以很方便地在状态树上移动,
Loading状态
以上设计有一个很大的问题,你能看出来吗?似乎所有的state同时存在,这将导致大量的资源存在于内存中。就算是当进入到main menu状态之后,我们再也无法返回trailer或者logo状态,它们的资源也还驻留在内存里。因此,我们需要把这些状态划分阶段(phase),只让当前一个phase内的所有state留在内存里。当游戏从一个phase转到另一个phase的时候,会释放旧phase资源,然后载入新phase资源。这通过一个叫做GameState_Loading的类来实现。在释放旧资源和载入新资源的过程中,GameState_Loading将接管局面,并显示载入进度界面。我们先把目前的状态树划分phase
整个状态树被划分为4个phase:
logo(logo)
trailer(trailer)
main menu(main menu, credits, settings menu)
in-game(in-game, pause menu)
括号里面的就是该phase所包含的状态,会在一个loading过程中全部驻留内存。每一个phase实际上都形成一个子树,通过一个stack结构和上面的push、pop操作进行转换。我们扩展上面的类
[cpp]
class GameState
{
// ...as above
public:
int getStateOP() const { return m_stateOP; }
int getNextPhase() const { return m_phaseToLoad; }
protected:
int m_stateOP;
int m_phaseToLoad;
};
class GameState_Loading : public GameState
{
public:
enum Phase
{
PHASE_LOGO = 0,
PHASE_TRAILER,
PHASE_MAIN_MENU,
PHASE_INGAME,
};
public:
void setNextPhase( int phase ) { m_phaseToLoad = phase; }
GameState* getNextState() { return m_nextState; }
virtual void cycle()
{
free the old phase...
init the new phase frame by frame...
save the new states to StateMgr::m_states...
if( initialization is completed )
{
m_nextState = default state of the phase
m_stateOP = StateMgr::STATE_OP_NEW_STACK;
}
}
virtual void draw( GraphicsContext& g )
{
draw the progress interface...
}
private:
int m_phaseToLoad;
GameState* m_nextState;
};
class StateMgr
{
// ...as above
public:
enum StateOP
{
STATE_OP_NONE = -1,
STATE_OP_PUSH = 0,
STATE_OP_POP,
STATE_OP_LOAD,
STATE_OP_NEW_STACK,
};
public:
void cycle()
{
// ...as above
leaveFrame();
}
private:
void leaveFrame()
{
if( m_curState->getStateOP() != STATE_OP_NONE )
{
if( m_curState->getStat