游戏状态管理技巧?


24

首先,我不是指场景管理;我将游戏状态大致定义为游戏中涉及到是否应启用用户输入或是否应暂时禁用某些参与者等方面的任何状态。

举一个具体的例子,假设它是经典《 Battlechess》的游戏。在我采取另一名球员的棋子之后,便进行了短暂的战斗。在此序列中,不应允许玩家移动棋子。那么,您将如何跟踪这种状态转换?有限状态机?一个简单的布尔检查?看起来后者只会对状态变化很少的游戏有效。

我可以想到许多使用有限状态机来处理此问题的简单方法,但我也可以看到它们很快就变得一发不可收拾。我只是想知道是否还有一种更优雅的方式来跟踪游戏状态/过渡。


您是否检查过gamedev.stackexchange.com/questions/1783/game-state-stackgamedev.stackexchange.com/questions/2423/…?有点类似,但是我想不出比游戏状态机更好的东西。
michael.bartnett

Answers:


18

我曾经见过一篇文章,非常优雅地解决了您的问题。这是一个基本的FSM实现,​​在您的主循环中调用。我已经在其余的答案中概述了本文的基本内容。

您的基本游戏状态如下所示:

class CGameState
{
    public:
        // Setup and destroy the state
        void Init();
        void Cleanup();

        // Used when temporarily transitioning to another state
        void Pause();
        void Resume();

        // The three important actions within a game loop
        void HandleEvents();
        void Update();
        void Draw();
};

每个游戏状态都由该接口的实现表示。对于您的Battlechess示例,这可能意味着以下状态:

  • 介绍动画
  • 主菜单
  • 棋盘设置动画
  • 玩家移动输入
  • 玩家移动动画
  • 对手移动动画
  • 暂停菜单
  • 残局屏幕

在您的状态引擎中管理状态:

class CGameEngine
{
    public:
        // Creating and destroying the state machine
        void Init();
        void Cleanup();

        // Transit between states
        void ChangeState(CGameState* state);
        void PushState(CGameState* state);
        void PopState();

        // The three important actions within a game loop
        // (these will be handled by the top state in the stack)
        void HandleEvents();
        void Update();
        void Draw();

        // ...
};

请注意,每个状态在某个时候都需要一个指向CGameEngine的指针,因此状态本身可以决定是否应输入新状态。本文建议传入CGameEngine作为HandleEvents,Update和Draw的参数。

最后,您的主循环仅处理状态引擎:

int main ( int argc, char *argv[] )
{
    CGameEngine game;

    // initialize the engine
    game.Init( "Engine Test v1.0" );

    // load the intro
    game.ChangeState( CIntroState::Instance() );

    // main loop
    while ( game.Running() )
    {
        game.HandleEvents();
        game.Update();
        game.Draw();
    }

    // cleanup the engine
    game.Cleanup();
    return 0;
}

17
C类?真是的 但是,这是一篇不错的文章-+1。
共产党鸭子

据我所知,这是问题明确而不是询问的问题。这并不是说您不能像您当然那样处理,但是如果您要做的就是暂时禁用输入,我认为派生CGameState的新子类既过高又不利于维护。与另一个子类99%相同。
Kylotan

我认为这很大程度上取决于代码如何耦合在一起。我可以想象一下,选择棋子和目的地(主要是UI指示器和输入处理)之间的清晰分隔,以及棋子朝该目的地的动画(整个棋盘动画,其中其他棋子移开并与移动互动)件等),使状态远非相同。这将责任分开,可以轻松维护甚至重复使用(演示,重播模式)。我认为这也显示了使用FSM不必麻烦,这也回答了这个问题。

真的很棒,谢谢。您提出的一个关键点是您的最新评论:“使用FSM不必麻烦。” 我曾经错误地想到,使用FSM会涉及使用switch语句,但这不一定是正确的。另一个关键的确认是,每个状态都需要引用游戏引擎。我想知道这将如何工作。
vargonian 2011年

2

我首先以最简单的方式处理这类事情。

bool isPieceMoving;

然后,在相关位置添加对该布尔标志的检查。

如果我以后发现我需要比这更多的特殊情况(而且只有它),我可以将其重构为更好的东西。我通常会采用3种方法:

  • 将所有排他的子状态表示标志重构为枚举。例如。enum { PRE_MOVE, MOVE, POST_MOVE }并在任何需要的地方添加过渡。然后,我可以对照过去用于检查布尔标志的该枚举。这是一个简单的更改,但它减少了您必须检查的内容数量,使您可以使用switch语句有效地管理行为,等等。
  • 根据需要关闭各个子系统。如果在战斗序列中唯一的区别是您不能移动棋子,则可以pieceSelectionManager->disable()在序列开始时调用或类似命令,然后按pieceSelectionManager->enable()。从本质上讲,您仍然具有标志,但是现在它们被存储在它们所控制的对象附近,并且您不需要在游戏代码中维护任何额外的状态。
  • 上一部分暗示存在PieceSelectionManager:更一般而言,您可以将游戏状态和行为的一部分分解为较小的对象,这些对象以连贯的方式处理整体状态的子集。这些对象中的每一个都有自己的状态,这些状态决定了其行为,但由于与其他对象隔离,因此易于管理。抵制让您的游戏状态对象或主循环成为伪全局变量的垃圾场并将其塞入其中的冲动!

一般来说,对于特殊情况的子状态,我再也不需要做任何事情了,因此,我认为它不会“迅速失控”。


1
是的,我认为在全力以赴与在适当时仅使用布尔/枚举之间存在界限。但是知道我的学风倾向,我可能最终将几乎每个州都变成自己的班级。
vargonian 2011年

您听起来像是一门课比其他方法更正确,但请记住这是主观的。如果您开始为太多可以由其他语言构造轻松表示的事物创建小型类,那么它可能会使代码的意图难以理解。
Kylotan


1

我尽量不要为此使用状态机和布尔值,因为它们都不具有可伸缩性。当国家数量增加时,两者都变成混乱。

我通常将游戏玩法设计为一系列动作和后果,任何游戏状态都是自然而然的,无需单独定义。

例如,在禁用玩家输入的情况下:您有一些用户输入处理程序和一些游戏中的视觉指示,表明输入已被禁用,您应该将它们设为一个对象或组件,因此要禁用输入,您只需禁用整个对象,而无需在某些状态机中同步它们,或对某些布尔指示符做出反应。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.