quinta-feira, 3 de janeiro de 2008

Pilha de telas

Oi povo, desculpem a demora para responder, final do ano é sempre uma correria, e no trabalho não é diferente. Estou de volta com novos posts dicas e informações.

Sei que falei que iria falar da classe BaseGame da engine, mas decidi passar pra algo mais pratico. Várias pessoas nas ultimas semanas vieram conversar comigo sobre gerenciamento de telas em engines de jogos. Aqui na Tecnodata, desenvolvemos uma solução bem elegante e de conceito simples - uma pilha.

A engine tem, internamente, uma pilha controlada por um gerenciador de telas. Esse gerenciador contem os metodos de push() e pop(), assim como metodos para acessar a tela no topo da pilha ( topScreen() ), e delegar as chamadas dos metodos Update() e Draw() à tela mais visivel. Para que isso seja viável, uma tela é um conceito encapsulado em uma classe Tela. Vamos ver um exemplo de código de como isso poderia funcionar em C++. O esqueleto da classe Tela:
class Tela
{
public:
virtual void update(dword msTime) = 0;
virtual void draw(dword msTime) = 0;
};

A classe Tela é bem simples, basicamente apenas declarando uma interface minima que todas as telas devem ter para que possam ser gerenciadas. (nota: o tipo de dados dword é simplesmente um valor 32bits sem sinal - ou seja - um unsigned int numa maquina 32bits).

Agora vamos ver o esqueleto do gerenciador:

class GerenciadorTelas
{
private:
stack _pilha;

public:
// construtor/destrutor omitidos

void push(Tela *tela);
Tela* pop();
Tela* telaAtual();

void update(dword msTime);
void draw(dword msTime);
};
Como eu tinha dito, o gerenciador internamente trabalha com uma pilha de telas. Para adicionar uma nova tela visivel no jogo, basta criar o objeto dessa tela (ie. Tela *t = new TelaQualquer() ) e adicionar na pilha. Desse jeito, no proximo frame da engine, a tela irá trocar automaticamente da tela anterior para a nova, pois estará chamando update e draw na nova tela.

O interessante é que, como estamos falando de pilha, a tela anterior ainda existe, entao voltar para a tela anterior é tao simples quanto dar um pop() no gerenciador. Isso garante que voce possa ter uma hierarquia de telas no seu jogo faceis de gerenciar e navegar.

O exemplo mostrado aqui é extremamente simples, apenas para demonstrar a idea básica. Porém, no nosso sistema, telas incluem máquina de estados, serialização e transparência (caso uma tela tenha uma transparência inferior a 1.0, a tela abaixo dela na pilha também é visivel e, portanto, também recebe chamadas a update() e draw() ). O gerenciador de telas não apenas gerencia a pilha, como também tem opções específicas para transição entre-telas (não apenas troca), acesso a telas por índice e por tipo (não apenas ao topo da pilha), e tem todos seus métodos como virtuais, possibilitando sua extensão fácil caso necessário.

Leitura extra

Um artigo bem interessante sobre organização de telas em classes pode ser lido neste post do blog Ponto V. Nao deixem de visitar.

Um comentário:

Anônimo disse...

Muito interessante. Não é à toa que a sua interface de tela ficou muito similar a interface GameSteps, descrita no meu blog.

http://vinigodoy.wordpress.com/2007/12/24/organizando-a-janela-em-classes/

update e draw são padrões recorrentes em jogos. Vou recomendar aos meus leitores que dêem uma olhada na idéia da pilha também de telas também. :)

Eu também tenho o hábito de usar uma pilha em máquinas de estados. Assim, podemos voltar ao estado anterior, ou fazer uma máquina de estados baseada em metas. Um dia ainda vou abordar esse tema lá no Ponto V!

Abraços e parabéns pelo ótimo post!