Ponto V!

Home OpenGL Organizando a janela em classes
Vinícius Godoy de Mendonça
Organizando a janela em classesImprimir
Escrito por Vinícius Godoy de Mendonça

Até agora, vimos como inicializar a janela da nossa aplicação, como funciona o loop principal, como capturar eventos. Entretanto, embora didáticos, os exemplos anteriores são pouco reutilizáveis. Em primeiro lugar, não existe uma separação clara entre a lógica da janela e a lógica do “jogo” (que no nosso caso resume-se a mover o triângulo. Um jogo não muito divertido, mas talvez o triângulo colorido agrade várias crianças de 3 anos).

Neste artigo, damos uma pausa na OpenGL e na SDL para voltar ao bom e velho C++ e organizar tudo em classes reutilizáveis.

A estrutura até agora

Se observarmos bem o código dos artigos anteriores, poderemos notar o seguinte:

  1. Existe uma janela principal. Essa janela possui alguns atributos como um título, altura, largura, bits por pixel, etc. O loop principal está associado a lógica da janela.
  2. Todos os jogos devem ser capazes de: preparar seu ambiente, processar eventos, processar a lógica, desenhar e limpar o seu ambiente. Esses são passos de qualquer entidade que queira fazer parte do loop principal deve implementar;
  3. Nosso triângulo rodando é um jogo, ou pelo menos, segue sua estrutura.

Isso nos leva até a seguinte estrutura:

uml

A interface GameSteps

A interface GameSteps define o contrato básico que qualquer classe que queira ser submetida ao loop principal deve obedecer. Como já constatamos, a classe que roda ali deve ser capaz de processar os eventos (processEvents), sua lógica (processLogics) e se desenhar (draw). Também foram adicionados métodos para inicialização (setup) e finalização (teardown), que serão executados uma única vez, antes e depois do loop. Por fim, o método ended() deve retornar verdadeiro, sempre que a classe quiser que o loop termine.

Em C++, interfaces são modeladas através de classes onde todos os seus métodos são virtuais puros (sem implementação) e seu destrutor é virtual e vazio. Essas classes também são conhecidas como Abstract Base Classes ou, simplesmente, ABCs.

No nosso caso, nossa ABC GameSteps seria:

union SDL_Event;

class GameSteps
{
  public:
    virtual void setup() = 0;

    virtual void processEvents(const SDL_Event& event) = 0;
    virtual void processLogics() = 0;
    virtual void draw() const = 0;

    virtual bool ended() = 0;

    virtual void teardown() = 0;
    virtual ~GameSteps() {};
};

Um detalhe importante dessa implementação: note que definimos o método draw() como const. Isso significa, que o método draw() não deve alterar o estado do jogo. Assim, garantimos que o processamento da lógica está restrito aos métodos “process”. Embora isso pareça purista, essa forma ajuda muito a avaliar o desempenho de sua aplicação usando um profiler.

A classe GameWindow

A classe GameWindow é praticamente idêntica ao nosso antigo main, mudanças foram feitas apenas para delegar parte das tarefas ao GameSteps, seja ele qual for.

Primeiramente, alteramos o método processEvents, para apenas capturar os eventos e delegar o processamento propriamente dito para o GameSteps:

void GameWindow::processEvents(GameSteps* game)
{
    SDL_Event event;

    while (SDL_PollEvent(&event))
        game->processEvents(event);
}

Note a simplicidade do método, já que a janela não é mais responsável por sequer tratar o evento de SDL_Quit. Em segundo lugar, alteramos o loop principal para também delegar atividades ao GameSteps.

void GameWindow::show(GameSteps* game)
{
    lastTicks = SDL_GetTicks();

    try
    {
        game->setup();
        while (!game->ended())
        {
            Uint32 thisTicks = SDL_GetTicks();
            ticks = thisTicks - lastTicks;
            lastTicks = thisTicks;

            processEvents(game);
            game->processLogics();
            game->draw();

            SDL_GL_SwapBuffers();
        }
    }
    catch (std::exception &e)
    {
        std::cout << "Problems while running game logic: ";
        std::cout << std::endl << e.what();
        exit(2);
    }

    game->teardown();
    delete game;
}

Perceba que o objeto do game é deletado ao final do loop, foi por isso que no diagrama UML definimos a relação como composição e não como uma relação de dependência simples.Em terceiro lugar, definimos a classe GameWindow como um Singleton. Para implementarmos o Singleton, colocamos o construtor, o construtor de cópia e o operador de atribuição na sessão privada da classe. Isso impede que novas instâncias sejam criadas externamente à classe. Depois, criamos uma única variável estática que define um ponteiro para nossa classe (chamada nesse caso de myInstance) e a inicializamos com NULL. Finalmente, criamos o método estático getInstance(), que cria uma única instância da classe e a retorna.Veja os trechos de código relevantes:

 

//GameWindow.h

class GameWindow
{
    public:
        //Obtém a única instância da classe
        static GameWindow& getInstance();

        //RESTO DA SESSÃO PUBLICA OMITIDO
    private:
        static GameWindow* myInstance;

        //Desabilita criação, cópia e assinalação
        GameWindow(const GameWindow& other);
        GameWindow& operator=(const GameWindow& other);

        GameWindow() : window(NULL), ticks(0) {}

        //RESTO DA SESSÃO PRIVADA OMITIDO
};

//GameWindow.cpp
//Ponteiro para a instância única
GameWindow* GameWindow::myInstance = NULL;

GameWindow& GameWindow::getInstance()
{
    //Cria a instância, se ainda não foi criada
    if (myInstance == NULL)
        myInstance = new GameWindow();
    //Retorna a única instância
    return *myInstance;
}

Alguns atributos dessa classe certamente serão usados em vários pontos do programa, tais como o tempo entre duas iterações do loop (fornecido pelo método getTicks() ou getSecs()), a altura e a largura da janela ou mesmo a razão entre os dois (fornecida por getRatio()). O fato da classe ser Singleton nos ajuda muito nesse ponto. Afinal, ela não precisa ser passada como parâmetro, simplificando as interfaces, e nem dependências dela precisam ser incluídas em qualquer arquivo .h, o que poupa tempo de compilação. Acessar qualquer atributo da classe então é uma questão usar o método estático getInstance(), como no exemplo:

cout << "Largura: " << GameWindow::getInstance().getWidth();

Como a idéia é poupar trabalho, e também por preguiça, também criamos o seguinte define:

#define GAMEWINDOW GameWindow::getInstance()

Assim, podemos simplificar o código acima para:

cout << "Largura: " << GAMEWINDOW.getWidth();

A classe RotatingTriangle

Finalmente, podemos implementar a nossa aplicação, que faz o triângulo girar. Embora tudo tenha parecido um pouco complicado até aqui, pense que futuras aplicações precisarão apenas realizar esse passo. Toda complicação da janela já está pronta e a ABC GameSteps já até dá uma pequena dica de como implementar a estrutura principal do nosso jogo.

A classe RotatingTriangle implementa a interface GameSteps e contém a lógica que faz o triângulo girar. Ela define apenas dois atributos privados:

  • degreesToRotate: Usado no mesmo contexto anterior, para indicar quantos graus o triângulo deve ser rotacionado;
  • exit: Que indica se a aplicação terminou ou não.

O .h dela é praticamente uma cópia do GameSteps.h, apenas com esses dois atributos declarados na sessão private e sem nenhum método definido como virtual puro (ou seja nenhum método =0). A implementação contém o código específico do desenho do triângulo e do processamento de teclas e finalização da aplicação:

#include "RotatingTriangle.h"
#include "SDL.h"
#include "GL/GL.h"
#include "GameWindow.h"

RotatingTriangle::RotatingTriangle()
: exit(false), degreesToRotate(0) {}

void RotatingTriangle::setup() {}

void RotatingTriangle::processEvents(const SDL_Event& event)
{
    switch (event.type)
    {
        case SDL_QUIT:
            exit = true;
            break;
    }
}

void RotatingTriangle::processLogics()
{
    //Distância para girar (em graus) =
    //velocidade (0.180f) * tempo (ticks)
    float distance = 0.180f * GAMEWINDOW.getTicks();

    //Lemos o estado das teclas
    Uint8* keys = SDL_GetKeyState(NULL);

    //Está com a seta esquerda pressionada?
    if (keys[SDLK_LEFT])
        degreesToRotate += distance;

    //Está com a seta direita pressionada?
    else if (keys[SDLK_RIGHT])
        degreesToRotate -= distance;
}

void RotatingTriangle::draw() const
{
    glPushMatrix();
        //Limpa a tela
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClear(GL_COLOR_BUFFER_BIT);

        //Gira o triângulo
        glRotatef(degreesToRotate, 0,0,1);

        //Desenha o triângulo
        glBegin(GL_TRIANGLES);
            glColor3f(1,0,0);
            glVertex2f(0.0f,0.5f);
            glColor3f(0,1,0);
            glVertex2f(-0.5f, -0.5f);
            glColor3f(0,0,1);
            glVertex2f(0.5f, -0.5f);
        glEnd();
    glPopMatrix();
}

bool RotatingTriangle::ended()
{
    return exit;
}

void RotatingTriangle::teardown() {}

Note o uso da GameWindow no método processLogics(). Repare também que o código ficou muito mais coeso. A classe restringe-se apenas ao desenho do triângulo e por isso ficou muito mais fácil de entender. Da mesma forma, a classe GameWindow restringe-se ao que se espera da janela principal.Outro detalhe interessante é a forma como a classe trata eventos. Note que agora ela recebe os eventos prontos, já capturados, precisando apenas identifica-los e trata-los. Houve a efetiva separação entre as etapas de captura de eventos e o seu processamento.

Usando a solução

Com as classes prontas, nosso novo main se resume a:

#include "GameWindow.h"
#include "RotatingTriangle.h"

int main(int argc,char* argv[])
{
    GAMEWINDOW.setup("Rotating triangle", 800, 600);
    GAMEWINDOW.show(new RotatingTriangle);
}

Outras modificações interessantes

Você pode encontrar o código fonte completo neste link. Nesse fonte, também adicionei uma forma de alterar o título da janela. Isso é feito através da função SDL_WM_SetCaption encapsulada no método setCaption.

Também defini a taxa de repetição de teclas para zero, durante a fase de setup. Isso evita que eventos sejam disparados em falso, caso o usuário mantenha qualquer tecla pressionada. Podemos definir a quanto tempo levará para a repetição a ocorrer e o intervalo entre repetições através da função SDL_EnableKeyRepeat.

Finalmente, também durante o setup, ocultei o cursor do mouse através da função SDL_ShowCursor.

Como tarefa para você, fica baixar o código fonte, tentar compila-lo e estudar a solução completa. Não se assuste, não é assim tão diferente do que vimos até agora. Preste especial atenção no método setup, onde esses novos detalhes foram adicionados. Certifique-se também de ter entendido os conceitos.

Veja também

Um jogo não está restrito à uma só tela. A interface GameSteps pode ser usada para fazer telas além da principal. Nesse caso, leia o artigo Pilha de Telas, do Vertex Buffer, para ver uma forma muito interessante de gerenciar várias telas.


Comentários (19)
  • gokernel
    avatar

    Oi, olhei o codigo do GameSteps e o que me chamou mais atenção foi:
    ------------------------------------------------------------
    "
    A interface GameSteps pode ser usada para fazer telas além da principal.
    "
    ------------------------------------------------------------

    PERGUNTA:

    Usando SDL/OpenGL, é possível "simular" mais de um "view" de uma sena em OpenGL? Como?

    gokernel
    gokernel@hotmail.com

  • Vinícius Godoy de Mendonça  - GameSteps
    avatar

    Sim, o propósito da interface é esse: separar a lógica da tela do game loop. Você pode fazer cada fase ser um game step, empilhar game steps e controlar o fluxo de telas através disso.

    Não entendi o que você quis dizer com simular mais de um view. Pode explicar melhor?

  • Anônimo
    avatar

    Alguns programas usam para exibir senas em 3D em 4 janelas... tipo o 3D Game studio.

    1 - Visão da "camera" parte de cima.
    2 - Visão da "camera" parte lateral.
    3 - ...
    4 - ...

    Espero que tenha entendido.

  • Vinícius Godoy de Mendonça  - Várias janelas
    avatar

    Sim, isso é possível com o OpenGL. Você tem várias alternativas:

    a) Criar vários viewports: Um viewport pode ser menor do que as janela toda. E em cada viewport, você faria um desenho diferente;

    b) Lembra que eu falei que é importante que o método de pintura não altere o estado do game? Isso é importante pq vc poderá chama-lo várias vezes, e com certeza pintará a mesma cena. E isso te permite chamar uma vez para cada área que quiser, mudando as matrizes de translação, rotação e câmera, fazendo assim as várias janelas.

    c) Você pode abrir várias janelas com a OpenGL e cada uma também terá sua área de pintura.

  • Anônimo
    avatar

    Oi.

    Sei que usando outras API(Win32, Glut ... ) com o OpenGL pode-se criar outras "view".

    Minha pergunta foi especificamente para SDL/OpenGL.

    É possível criar "view" simulando por exemplo quatro janelas("view";) em uma única janela do SDL/OpenGL?

    Abraços.

    gokernel
    gokernel@hotmail.com

  • Bruno Crivelari Sanches
    avatar

    gokernel,

    A SDL que eu saiba não permite multiplas janelas, lembro que tinha um projeto para implementar isso mas não sei como anda.

    Nesse caso você vai ter que usar a primeira idéia do Vinícius que é criar multiplos viewports, isso até onde me lembro é feito inteiramente com comandos opengl.

  • gokernel
    avatar

    Oi Bruno.

    Sei que com SDL/OpenGL não é possível criar mais de uma janela, por isso usei as palavras "simular" "view".

    Se você lembrar sobre o projeto por gentileza informe o link.

    Abraços.

    gokernel
    gokernel@hotmail.com

  • Bruno Crivelari Sanches
    avatar

    Temos que separar os conceitos então. Multiplas janelas é diferente de multiplas view.

    Com o viewport você pode dividir uma janela existente em sub-regiões e desenhar em cada uma delas conforme a necessidade. Assim você pode ter um resultado similar ao que existe no Max ou outros editores 3d.

    Se for criar multiplas janelas, janelas do sistema de janelas, dai a SDL vai lhe deixar na mão.

    Pelo o que andei vendo, apenas a versão 1.3 vai suportar isso, mas esta em desenvolvimento ainda, mas existe um preview: http://www.libsdl.org/tmp/SDL-1.3.zip

  • Anônimo
    avatar

    Sim são multiplas "view" que não tenho ideia de como fazer pelo menos 2.

    T++.

    gokernel
    gokernel@hotmail.com

  • paulo  - error compilaçao
    avatar

    Ola eu estava tentando compilar o projeto mas apontou varios erros aqui, ser que alguem pode ajudar? vlw =]

  • Bruno Crivelari Sanches
    avatar

    Sem mensagens de erro e código fonte onde ocorreu o erro é impossivel ajudar.

    Coloque seu código em um serviço como http://notepad.cc/, informe o link e as mensgens de erro.

    T+

  • paulo  - hmm
    avatar

    ok ok =]
    vo fazer o que disse valeu

  • fredy  - Tecla mantida pressionada só detecta uma vez...
    avatar

    Segue o código no link: http://notepad.cc/share/rn95kVYiSj

    ele esta funcionando quando eu pressiono e solto a tecla "direcional para a direita". caso eu mantenha a tecla pressionada a imagem somente atualiza e desloca-se apenas uma vez, como se estivesse somente detectando o keyDown.

  • Vinícius Godoy
    avatar

    Se você vai desenhar usando OpenGL, esqueça o SDL_BlitSurface. Não vai funcionar mesmo.

  • fredy  - Sugestão
    avatar

    o que sugeres?

  • fredy  - modtrar mapa
    avatar

    srs(s),
    depois de carregar a imagem com IMG_load, como eu a desenho na tela usando OpenGL. Ou melhor, como eu implemento a função draw()?

    Obs: estou usando o código deste artigo disponível para download.

  • Vinícius Godoy
    avatar

    Você deve desenhar um GL_QUAD e texturiza-lo com a sua imagem.

  • Anônimo
    avatar

    Os próximos tutoriais da série mostram como fazer: http://www.pontov.com.br/site/index.php/opengl/204-texturas-parte-1

  • nilson lana machado  - Programa 3D?
    avatar

    Como fica a disposição dos códigos, janela principal, janela para se desenhar uma imagem e as ferramentas como da barra de desenhar???

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter