Curso completo de linguagem C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
track23.html

index << >>

23. Exemplo de aplicação da fila simples em videogames

23.1 Sistema de troca de mensagens numéricas entre objetos Se alguém pisar no seu pé, o seu sistema de percepção sensorial vai perceber a pressão e enviar para o seu cérebro a mensagem codificada em impulsos elétricos de que seu pé recebeu uma pressão, e desta forma você percebe que seu pé foi pisado e reage a esse evento. De maneira semelhante, você pode implementar esse mecanismo de troca de mensagens entre os objetos do seu jogo avisando-os dos eventos que ocorrem sobre eles e implementar um tratamento adequado de acordo com o evento ocorrido e os objetos afetados. Em um exemplo concreto, imagine seu personagem passando por uma certa localização vantajosa em uma tela; seu sistema de perceção implementado pode avisar que seu jogador passou por cima de um kit de saúde ou de uma vida extra, e como reação a esse evento, o motor do seu jogo soma uma certa quantidade de pontos ao seu número de vidas ou de energia do personagem que você está movendo na tela. Pode ocorrer vários outros eventos sobre seu personagem, ele pode ser atingido por um tiro, ele pode chegar a uma região gatilho da tela que pode acionar a exibição de um vídeo ou de uma música, seu jogador pode chegar na marca de final de estágio. Certos eventos podem estar condicionados ao alcance de certas posições do espaço ou decorrentes da passagem de certo período de tempo. Se seu jogador deu um tiro fatal em um inimigo, este inimigo deve ser avisado desse evento e reagir adequadamente rodando uma animação de morte e na sequência desaparecendo da tela. Você pode utilizar uma fila para implementar um subsistema semelhante no seu jogo. A fila é importante porque você deve reagir aos eventos por ordem de chegada dos mesmos. As mensagens de eventos que podem ocorrer ficam codificadas numa fila de número inteiros. Você desenha uma função para processar essa fila que despacha as mensagens para os objetos sobre os quais esses eventos ocorreram. Os objetos recebem essas mensagens e reagem adequadamente à elas. Os objetos podem ter um método para receber essas mensagens que pode simplesmente consistir de uma função que recebe um ou dois números inteiros. Veja um pequeno exemplo para você visualizar melhor essa concepção do sistema de troca de mensagens numéricas entre objetos. Bem, o personagem Rambo de nosso jogo vai andar por uma espaço aonde ele pode encontrar kits de munição que lhe aumentam o número de tiros, kits de saúde que lhe aumentam o número de energia, vidas extras, e inimigos que lhe atacam e que reduzem sua energia. Mensagens numéricas do jogo:
Valor da mensagem Evento ocorrido
1
#define msg_jogador_atacado
O jogador foi atingido pelo tiro inimigo
2
#define msg_vida_extra_encontrada
Kit de vida extra encontrado
3
#define msg_municao_encontrada
Kit de munição encontrado
4
#define msg_kitsaude_encontrado
Kit de saúde encontrado
5
#define msg_gatilho_video_encontrado
Zona de gatilho encontrado
Propriedades e métodos do objeto da classe Jogador.
Propriedades Métodos
m_vidas

número de vidas
.mostrar()

Mostra dados de status do jogador: vida, energia, munição

.ganhar_vida ( int nqtd)

Aumentar o número de vidas na ocorrência do evento msg_vida_extra_encontrada
m_energia

quantidade de energia (saúde)
.perder_energia(int nqtd)

Reduzir a energia na ocorrência do evento msg_jogador_atacado

.ganhar_energia( int nqtd)

Aumentar a energia na ocorrência do evento msg_kitsaude_encontrado
m_municao

quantidade de munição
.aumentar_municao( int nqtd )

Aumentar a quantidade de munição na ocorrência do evento msg_municao_encontrada
***
.tocar_video ( int nvideo)

Exibir um video especificado na ocorrência do evento msg_gatilho_video_encontrado
Roteador dos eventos // --------------------- tratar_evento() ------------------------------------- void tratar_evento ( int nEvento) { switch (nEvento) { case msg_jogador_atacado: rambo.perder_energia(10); break; case msg_vida_extra_encontrada: rambo.ganhar_vida(3); break; // (...) Veja o código completo abaixo } // fim do switch nEvento } // ------------------ fim da funcao tratar_evento() Acima está um exemplo rústico de função roteadora que recebe o tipo de evento ocorrido e despacha ela para o destino correto, isto é, chama o código que vai tratar adequadamente o evento ocorrido. Pensando em um desenho melhor dessa função ela deveria ter outros argumentos como o tipo de objeto que sofreu o evento, o objeto que causou o evento e dados extras da mensagem em casos de mensagens mais complexas. Esse mecanismo de troca de mensagens entre objetos é o núcleo de funcionamento do sistema Windows. Por exemplo, quando você clica em uma janela com o botão direito, o sistema Windows envia uma mensagem à janela, dizendo que ela foi clicada com o botão direito e manda como informação extra as coordenadas (x,y) aonde ocorreu o click. Conceito de gatilho (triggers) Gatilhos ou triggers como são chamados em inglês são condições que disparam eventos. Como exemplo de gatilhos podemos citar a passagem do jogador por certas coordenadas do espaço ou passando perto de certos itens, ainda os eventos podem ser disparados por um timer, por certos períodos decorridos a partir do início do jogo ou de uma tela. Em nosso rústico programa exemplo, sorteamos os eventos, mas em um jogo completo certamente você vai utilizar o conceito de gatilhos. Agora dê uma olhada em nosso programa exemplo completo:
// Programa: queue_tst.cpp // Este programa ilustra o conceito de trocas de mensagens numéricas entre // objetos utilizando filas para armazenar eventos ocorridos. #include <iostream> #include <string> #include <ctime> #include <cstdlib> #include <queue> using namespace std; // Eventos de videogame #define msg_jogador_atacado 1 #define msg_vida_extra_encontrada 2 #define msg_municao_encontrada 3 #define msg_kitsaude_encontrado 4 #define msg_gatilho_video_encontrado 5 void tratar_evento ( int nEvento); // -------------------------- Classe Jogador --------------------------------- class Jogador { public: int m_vidas; int m_energia; int m_municao; Jogador(int vidas = 5, int energia = 99, int municao = 50) { m_vidas = vidas; m_energia = energia; m_municao = municao; } // fim do construtor void mostrar() { char txt[255]; sprintf (txt,"Situacao do jogador: ( vidas:%d energia:%d municao:%d )", m_vidas, m_energia, m_municao); cout << "\t" << txt << "\n"; } // fim do método mostrar() void perder_energia( int nqtd) { m_energia = m_energia - nqtd; cout << "\t*** Vc perdeu " << nqtd << " pontos energia *** \n"; } // fim do perder_energia() void ganhar_energia( int nqtd) { m_energia = m_energia + nqtd; cout << "\t*** Vc ganhou " << nqtd << " pontos de energia ***\n"; } // fim do ganhar_energia() void ganhar_vida( int nqtd) { m_vidas = m_vidas + nqtd; cout << "\t*** Vc ganhou " << nqtd << " vidas extras. ***\n"; } // fim do método ganhar_vida() void aumentar_municao( int nqtd) { m_municao = m_municao + nqtd; cout << "\t*** Vc ganhou " << nqtd << " cargas de municao extra ***\n"; } // fim do método aumentar_municao() void tocar_video ( int nvideo) { cout << "\t*** O video #" << nvideo << " foi tocado e mostrou uma cena...***\n"; } // fim do método tocar_video() }; // ------------------ fim da classe Jogador --------------------------------- // Vamos criar um jogador publico Jogador rambo; // ----------------- Nossa funcao principal comeca aqui ------------------------ int main() { system("color f0"); system("title queue_tst.cpp"); queue<int> msgEventos; string legenda[6]; legenda[0] = "0 - reservado "; legenda[1] = "1 - msg_jogador_atacado "; legenda[2] = "2 - msg_vida_extra_encontrada "; legenda[3] = "3 - msg_municao_encontrada "; legenda[4] = "4 - msg_kitsaude_encontrado "; legenda[5] = "5 - msg_gatilho_video_encontrado "; int evento_surpresa; int evento_ocorrido = 0; // Vamos sortear e mostrar os eventos srand ( time(0)); cout << "\n\t *** Eventos enfileirados nesta ordem: ***\n"; for (int ncx = 0; ncx < 4; ncx++) { evento_surpresa = (rand() % 5) + 1; msgEventos.push( evento_surpresa ); cout << "\t " << legenda[evento_surpresa] << "\n"; } // endfor cout << "\n"; // Mostrar o jogador antes dos eventos serem processados rambo.mostrar(); // Vamos processar os eventos ocorridos na sequência que aconteceram cout << "\t--------------- Processando eventos -------------------------- \n"; for (int ncx = 0; ncx < 4; ncx++) { evento_ocorrido = msgEventos.front(); cout << "\t"; cout << legenda [evento_ocorrido]; cout << "\t fila.tamanho:" << msgEventos.size() << "\n"; tratar_evento ( evento_ocorrido); msgEventos.pop(); } // endfor // Foi tratado todos os eventos? cout << "\t--------------------------------------------------------------- \n"; if (msgEventos.empty()) cout << "\t --- A fila agora esta' vazia! ---\n"; rambo.mostrar(); cout << "\n"; system("pause"); } // ---------------------- fim da funcao main() ------------------------------ // --------------------- tratar_evento() ------------------------------------- void tratar_evento ( int nEvento) { switch (nEvento) { case msg_jogador_atacado: rambo.perder_energia(10); break; case msg_vida_extra_encontrada: rambo.ganhar_vida(3); break; case msg_municao_encontrada: rambo.aumentar_municao(10); break; case msg_kitsaude_encontrado: rambo.ganhar_energia(15); break; case msg_gatilho_video_encontrado: rambo.tocar_video(7); break; default: cout << "Esse evento nao existe: #" << nEvento << "\n"; } // fim do switch nEvento } // ------------------ fim da função tratar_evento()

index << >>


Produzido por Gameprog: Jair Pereira - Fev/2006 - Junho/2013 © gameprog.br@gmail.com http://www.gameprog.com.br