Sofia IA

Inteligência artificial para jogos

Máquina de Estados

fazer um comentário »

Finalmente posso retomar o projeto! Depois de meses parado graças as aulas que dei na escola técnica, decidi voltar implementando uma classe para lidar com máquinas de estados. Na verdade, isso porque recentemente fiz uma muito parecida para Java, num jogo de pong, e vi o quão importante a classe é.

A classe segue a idéia do prof. Pozzer, e usa uma pilha para “memorizar” os estados passados. Assim, um estado pode solicitar o retorno ao estado anterior, o que simplifica drásticamente a construção de máquinas mais complexas, sobretudo de jogos RTS. Em jogos assim, retornar estados é muito comum. Um exemplo disso é o seguinte:

1. Madeireiro está no estado “CortandoLenha”. Chega um soldado inimigo, ele muda para o estado “Fugindo”;
2. Ele se afasta, até que o soldado para de persegui-lo e vai para uma batalha. Decide então voltar ao estado “CortandoLenha”;
3. Mas ele está longe das árvores! Então, empilha o estado “AndarAtéAFloresta”;
4. Após chegar na floresta, desempilha o estado e volta ao “CortandoLenha”.

A grande vantagem desse esquema é que cada estado pode ser modelado através de uma série de pré-condições e ações do estado. Se um estado não satisfaz a pré-condição, um estado que busca essa pré-condição é empilhado. Assim, a entidade pode apenas empilhar o estado final desejado, um “objetivo”. E a própria classe se encarrega de busca-lo.

A classe também segue a sugestão de Mat Buckland e permite a definição de um GlobalState. O GlobalState é processado antes de todos os estados.Alguns exemplos de ações globais são: “Verificar se tem vida, sono, fome, etc”.

#ifndef STATEMACHINE_H_INCLUDED
#define STATEMACHINE_H_INCLUDED

#include <queue>
#include "State.h"

namespace sofia
{
   namespace state
   {
      template <typename EntityType>
      class StateMachine
      {
         private:
            EntityType& owner;
            State<EntityType>* globalState;
            State<EntityType>* currentState;
            std::queue<EntityType*> previousStates;

            void doTheChange(State<EntityType>* nextState)
            {
               currentState->leave(*this);
               currentState = nextState;
               nextState->enter(*this);
            }

         public:
            StateMachine(EntityType& _owner,
                State<EntityType>* _globalState,
                State<EntityType*> _currentState)
            : owner(_owner), globalState(_globalState), currentState(_currentState)
            {
            }

            EntityType& getOwner() const
            {
               return owner;
            }

            void dropToState(State<EntityType>* nextState)
            {
               assert(nextState != NULL);
               previousStates.clear();
               doTheChange(nextState);
            }

            void changeState(State<EntityType>* nextState)
            {
               assert(nextState != NULL);
               previousStates.push(currentState);
               doTheChange(nextState);
            }

            void changeGlobalState(State<EntityType>* nextGlobalState)
            {
               globalState->leave(*this);
               globalState = nextGlobalState;
            }

            bool rewindState()
            {
               assert(!previousStates.isEmpty());

               doTheChange(previousStates->front());
               previousStates.pop();

               return !previousStates.isEmpty();
            }

            void process()
            {
               if (globalState != NULL)
                  globalState->process();
               if (currentState != NULL)
                  currentState->process();
            }

            bool isInState(const State<EntityType*>& state) const
            {
               return currentState == state;
            }

            State<EntityType>* getCurrentState() const
            {
               return currentState;
            }

            State<EntityType>* getPreviousState() const
            {
               return previousStates.front();
            }
      };
   }
}

#endif // STATEMACHINE_H_INCLUDED

Um estado, dentro desse contexto é uma interface bastante simples. Não vi muitas vantagens em modela-lo integralmente como um template. Aliás, parece-me vantagem que ele tenha uma estrutura formal. Se necessário for, pode-se criar um Adapter que receba três functors e mapeiem-nos para essa interface.

namespace sofia
{
   namespace state
   {
      template <typename EntityType>
      class State
      {
         public:
            virtual void enter(StateMachine<EntityType>& owner) = 0;
            virtual void process(StateMachine<EntityType>& owner) = 0;
            virtual void leave(StateMachine<EntityType>& owner) = 0;
            virtual ~State() {};
      };
   }
}

Agora, poderei aplicar essa classe juntamente com os steering behaviors já programador no Bola Gelada, conforme sugeriu o prof. Fábio Binder.

Uma das idéias é também criar uma classe ScriptedState, que automaticamente delega os métodos enter(), leave() e process() para um script, seja ele Lua ou Squirrel.

Escrito por vinigodoy

11 dUTC Julho dUTC 2008 às 21:21:41

Deixe uma resposta