Curso completo de linguagem C++
Gameprog - Escola de programação de jogos digitais
Contato: gameprog.br@gmail.com
track23.html
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()