Sofia IA

Inteligência artificial para jogos

Máquina de Estados

with 4 comments

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.

Written by Vinícius Godoy

11 \11+00:00 julho \11+00:00 2008 às 21:21:41

4 Respostas

Subscribe to comments with RSS.

  1. Interessante.

    Estou a fim de programar uma MEF (Máquina de Estados Finitos) para minha própria personagem.

    Trata-se de um lagarto gigante (um dragão de Komodo), que se comporta como um chefão no projeto do game, interceptando e fazendo em pedaços todo aquele que se atreve a enfrentá-lo.

    Estados de Máquina:

    Este é o exemplo de Inteligência Artificial para um lagarto gigante e para outras personagens – com máquinas de estados finitos –, em que são apresentadas as situações possíveis no contexto do jogo. As situações mais comuns são as seguintes:
    •Parado: o lagarto gigante fica parado. Caso seja atacado pelo jogador – ainda que não possa encontrá-lo por perto –, passará a procurá-lo (até encontrá-lo) seguindo-o pelo caminho mais provável. Outra opção consiste em pedir ajuda a terceiros (outros lagartos gigantes que estejam por perto), o que pode dar início a uma busca pelo paradeiro do jogador; neste caso (como em muitos outros jogos do gênero), as criaturas simplesmente criam um perímetro – cercando o local –, e a criatura que foi atacada anteriormente reaparece com um parceiro.
    •Distraído: o lagarto gigante está distraído por algum motivo qualquer. O réptil pode estar ciente de tudo à sua volta, exceto à presença do jogador; neste caso o jogador pode atacá-lo ou pode passar despercebido, evitando-o e seguindo adiante.
    •Alerta: o lagarto gigante está atento qual uma sentinela. Pode estar patrulhando algum local, ou pode estar à procura de algo suspeito (a presença do jogador ou de qualquer outra personagem hostil). A única forma de passar pela criatura é com movimentação furtiva, em que se faz um teste de Furtividade contra Percepção da fera – um teste de Perícia versus Atributo –, quando praticável.
    •Busca: o lagarto gigante busca o jogador dentro de seu campo de visão, a fim de atacá-lo. Com este intento o réptil utiliza suas manobras de combate, suas armas e quaisquer outros recursos que possua – por vezes em somatória.
    •Ataque normal: o lagarto gigante ataca o jogador, aproximando-se de sua posição e desferindo o ataque propriamente dito. Convém ressaltar que a criatura pode atacar de várias formas, conforme os recursos que possua – o que pode incluir ataques de morte.
    •Ataque Crítico: o lagarto gigante desfere golpes “carregados”, com o valor de ataque incrementado (por conta da manobra de Especialização em Combate), desferindo desta forma acertos críticos em potencial. O resultado de um acerto crítico é potencialmente letal, pois envolve manobras tais como Decapitação ou Morte dos Inimigos (invariavelmente), a menos que o alvo seja imune a ataques de morte.
    •Ataque furtivo: o lagarto gigante se move furtivamente, evitando ser visto pelo jogador. Caso a criatura desfira um ataque bem sucedido – ao encontrar o jogador ou qualquer outra vítima em potencial –, o ataque resultará em um golpe de misericórdia (um ataque de morte), capaz de eliminar qualquer alvo humano com um único golpe – independentemente dos Pontos de Vida e de tudo o mais. Esta situação é mais comum no modo Target Team Deathmatch, em que grupos de jogadores atuam com personagens humanas; estas personagens enfrentam um monstro Reptiliano – sáurio ou crocodiliano –, que ganha pontos a cada alvo eliminado; por sua vez, os humanos ganham pontos a cada minuto que cada alvo sobrevive.
    •Golpe de Misericórdia: toda vez que encontra um alvo humano indefeso (conforme as regras do RPG original), o lagarto gigante desfere um golpe de misericórdia (sem pensar duas vezes), eliminando o alvo com um único golpe.
    •Furioso: toda vez que se enfurece (BERSERK) o lagarto desfere ataques muito mais agressivos, com dano maior e com bônus de ataque; como efeito colateral, esquiva e defesa se tornam impraticáveis.

    O que acha dos exemplos?

    Cleber Fernandes

    19 \19+00:00 junho \19+00:00 2009 at 22:37:50

    • Legal. 🙂

      vinigodoy

      11 \11+00:00 novembro \11+00:00 2009 at 10:50:28

      • Já estou com os projetos de alguns games dos lagartões, e gostaria de implementar todo o esquema da melhor forma possível.

        Cleber Fernandes

        27 \27+00:00 fevereiro \27+00:00 2010 at 21:58:09

  2. […] FSM (Máquina de Estados) em jogos RTS […]


Deixe um comentário