Criando jogos simples com SimpleJava

Este documento mostra os passos necessários para a criação de jogos simples utilizando o framework SimpleJava. Para poder executá-lo, é necessário que o framework esteja corretamente instalado e configurado.

Modelo de aplicação gráfica

O modelo de aplicação gráfica definida pelo SimpleJava é semelhante a aplicações desenvolvidas para o [Arduíno], consistindo de um métdodo de configuração da aplicação (setup()) e um método executado repetidas vezes (loop()), até que seja indicado que a aplicação deve ser encerrada.

A configuração da aplicação deve ser realizada no método setup(). Neste método, devem ser inicializados os objetos que tem tempo de vida igual à aplicação, a tela da aplicação deve ser configurada, e outras ações que devem ser executadas ao inicializar a aplicação.

O laço infinito é iniciado após a configuração, será executado, no máximo, na freqüência definida pelo método setFramesPerSecond(int). Para terminar a aplicação, deve ser executado o método endLoop().

O método draw(Canvas) será executado uma vez após a primeira execução do loop(), e após isso, será executado apenas quando requisitado que a tela seja redesenhada. Para forçar a execução do método draw(Canvas) deve ser invocado o método redraw().

O Código 1 mostra um exemplo de implementação para um "Hello World!" gráfico, utilizando o framework.

Hello World Gráfico

package com.senac.SimpleJava.Graphics.examples;

import com.senac.SimpleJava.Graphics.Canvas;
import com.senac.SimpleJava.Graphics.GraphicApplication;
import com.senac.SimpleJava.Graphics.Resolution;

public class HelloWorld extends GraphicApplication {

    @Override
    protected void draw(Canvas canvas) {
        /* desenho dos objetos do jogo */
        canvas.putText(45, 25, 60, "Hello");
        canvas.putText(25, 90, 60, "World!");
    }

    @Override
    protected void setup() {
        /* configuração da tela do jogo */
        this.setResolution(Resolution.MSX);
    }

    @Override
    protected void loop() {
        /* lógica do jogo */
    }

}

A tela da aplicação

A tela da aplicação é composta de uma janela com resolução de 800 x 600 pixels. As características desta janela são definidas a partir da classe GraphicApplication. Três parâmetros que são normalmente configurados para a tela da aplicação são o título da janela (através do método setTitle(String)), a freqüência máxima de execução do método do loop (via setFramesPerSecond(int)) e a área de desenho.

Configurando a aplicação

Todo a configuração e inicialização da aplicação deve ser realizada no método setup(). Este método é invocado no início da aplicação, uma úncia vez, antes da execução do loop principal.

Configurando a área de desenho

A área de desenho apresenta ao programador uma resolução diferente da resolução real da tela, permitindo simular outras resoluções além da resolução original da janela.

Para ajustar a resolução da área de desenho, deve ser utilizado o método setResolution, passando como parâmetro um dos objetos Resolution, que representam as resoluções disponíveis:

HIGHRES
Define a resolução da área de desenho para 800 x 600 pixels.
LOWRES
Define a resolução da área de desenho para 96 x 72 pixels.
MIDRES
Define a resolução da área de desenho para 400 x 300 pixels.
MODE_X
Define a resolução da área de desenho para 320 x 200 pixels, como nos antigos jogos para MS-DOS.
MSX
Define a resolução da área de desenho para 256 x 192 pixels, como nos antigos computadores e videogames de 8-bits, como MSX e Sega Master System.

Inicializando a aplicação

A inicialização da aplicação consiste, normalmente, na criação e configuração dos objetos que serão utilizos na aplicação. Variáveis necessárias também podem ser inicializadas neste momento.

@Override
protected void setup() {
    res = Resolution.LOWRES;
    this.setResolution(res);
    this.setFramesPerSecond(30);

    paddle1 = new Sprite(2, PADDLE_SIZE, Color.BLACK);
    paddle1.setPosition(1, res.height/2 - 5);
    paddle2 = new Sprite(2, PADDLE_SIZE, Color.BLACK);
    paddle2.setPosition(res.width-3, res.height/2 - 5);

    ball = new Sprite(2,2,Color.BLACK);
    ballRestart();

    deltaX = 1;
    deltaY = -1;

    delta1 = 0;
    delta2 = 0;

    score1 = 0;
    score2 = 0;

    keyboardConfiguration();
}

Configuração do teclado

Para utilizar o teclado, devem ser criados eventos que serão associados às teclas que serão utilizadas. Os eventos disponíveis para o teclado podem ser de tecla pressionada (bindKeyPressed()), tecla liberada (bindKeyReleased()) ou qualquer um dos eventos (bindKey()). Para tratar um evento de teclado, é necessário implementar o método handleEvent() da interface KeyboardAction.

private void keyboardConfiguration() {
    bindKeyPressed("UP", new KeyboardAction() {
        public void handleEvent() { delta2 = -1; }
    });
    bindKeyReleased("UP", new KeyboardAction() {
        public void handleEvent() { delta2 = 0; }
    });
    bindKeyPressed("DOWN", new KeyboardAction() {
        public void handleEvent() { delta2 = +1; }
    });
    bindKeyReleased("DOWN", new KeyboardAction() {
        public void handleEvent() { delta2 = 0; }
    });
    bindKeyPressed("A", new KeyboardAction() {
        public void handleEvent() { delta1 = -1; }
    });
    bindKeyReleased("A", new KeyboardAction() {
        public void handleEvent() { delta1 = 0; }
    });
    bindKeyPressed("Z", new KeyboardAction() {
        public void handleEvent() { delta1 = +1; }
    });
    bindKeyReleased("Z", new KeyboardAction() {
        public void handleEvent() { delta1 = 0; }
    });
    // For Dvorak Keyboards
    bindKeyPressed("SEMICOLON", new KeyboardAction() {
        public void handleEvent() { delta1 = +1; }
    });
    bindKeyReleased("SEMICOLON", new KeyboardAction() {
        public void handleEvent() { delta1 = 0; }
    });
}

Desenhando na tela

Todo desenho deve ser realizado no método draw(Canvas). Para desenhar na tela, é disponibilizado um objeto da classe Canvas. Esta classe permite o desenho de linhas, pontos (pixels), imagens e texto. Objetos que implementam a interface [Drawable] podem ser desenhados invocando-se o método drow(Canvas) do objeto, passando como parâmetro o Canvas utilizado para desenhar na janela.

@Override
protected void draw(Canvas canvas) {
    // Limpa o canvas.
    canvas.clear();
    // Define a cor de desenho
    canvas.setForeground(Color.BLACK);
    // Desenha diretamente no Canvas.
    int mid = res.width / 2;
    canvas.drawLine(mid, 0, mid, res.height);
    canvas.putText(18, 1, 12, ((Integer)score1).toString());
    canvas.putText(70, 1, 12, ((Integer)score2).toString());
    // Desenha objetos da classe Sprite
    paddle1.draw(canvas);
    paddle2.draw(canvas);
    ball.draw(canvas);
}

Manipulando os dados do jogo

A manipulação dos dados do jogo deve ser realizada no método loop(). Este método é executada repetidamente enquanto a aplicação estiver sendo executada. O método endLoop() deve ser invocado para encerrar a aplicação após o término da execução do método. Uma vez invocado endLoop() o término da aplicação não pode ser cancelado.

Caso a tela precise ser redesenhada, deve ser invocado o método redraw(). Este método apenas notifica a janela que o conteúdo deve ser redesenhado, e a invoção deste método diversas vezes tem impacto mínimo no tempo de execução da aplicação.

@Override
protected void loop() {
    Point pos = ball.getPosition();
    // Verifica se a bola bateu nas paredes.
    if (pos.y <= 0 || pos.y >= res.height-1)
        deltaY *= -1;
    // Verifica se foi ponto do jogador 1
    if (pos.x > res.width) {
        score1++;
        ballRestart();
    }
    // Verifica se foi ponto do jogador 2
    if (pos.x < 0) {
        score2++;
        ballRestart();
    }
    // Verifica se bola bateu no paddel1
    Point bat = paddle1.getPosition();
    if (pos.x == 2 && pos.y >= bat.y && pos.y <= bat.y+PADDLE_SIZE)
        deltaX *= -1;
    // Verifica se bola bateu no paddel2
    bat = paddle2.getPosition();
    if (pos.x == res.width-4 && pos.y >= bat.y && pos.y <= bat.y+PADDLE_SIZE)
        deltaX *= -1;

    // Movimenta bola.
    ball.move(deltaX, deltaY);
    // Movimenta paddle1.
    paddle1.move(0, delta1);
    // Movimenta paddle2.
    paddle2.move(0, delta2);
    // requisita a tela para se desenhar.
    redraw();
}

Código completo do jogo "Pong"

Este jogo está disponível como exemplo do SimpleJava.

package com.senac.SimpleJava.Graphics.examples;

import com.senac.SimpleJava.Graphics.Canvas;
import com.senac.SimpleJava.Graphics.Color;
import com.senac.SimpleJava.Graphics.GraphicApplication;
import com.senac.SimpleJava.Graphics.Point;
import com.senac.SimpleJava.Graphics.Resolution;
import com.senac.SimpleJava.Graphics.Sprite;
import com.senac.SimpleJava.Graphics.events.KeyboardAction;

public class Pong2 extends GraphicApplication {

    private int PADDLE_SIZE = 10;

    private Resolution res;

    private Sprite paddle1, paddle2, ball;

    private int deltaX, deltaY, delta1, delta2, score1, score2;

        @Override
        protected void draw(Canvas canvas) {
            canvas.clear();

            canvas.setForeground(Color.BLACK);

            int mid = res.width / 2;
            canvas.drawLine(mid, 0, mid, res.height);
            canvas.putText(18, 1, 12, ((Integer)score1).toString());
            canvas.putText(70, 1, 12, ((Integer)score2).toString());

            paddle1.draw(canvas);
            paddle2.draw(canvas);
            ball.draw(canvas);
        }

    @Override
    protected void setup() {
        res = Resolution.LOWRES;
        this.setResolution(res);
        this.setFramesPerSecond(30);

        paddle1 = new Sprite(2, PADDLE_SIZE, Color.BLACK);
        paddle1.setPosition(1, res.height/2 - 5);
        paddle2 = new Sprite(2, PADDLE_SIZE, Color.BLACK);
        paddle2.setPosition(res.width-3, res.height/2 - 5);

        ball = new Sprite(2,2,Color.BLACK);
        ballRestart();

        deltaX = 1;
        deltaY = -1;

        delta1 = 0;
        delta2 = 0;

        score1 = 0;
        score2 = 0;

        keyboardConfiguration();
    }

    private void keyboardConfiguration() {
        bindKeyPressed("UP", new KeyboardAction() {
            public void handleEvent() { delta2 = -1; }
        });
        bindKeyReleased("UP", new KeyboardAction() {
            public void handleEvent() { delta2 = 0; }
        });
        bindKeyPressed("DOWN", new KeyboardAction() {
            public void handleEvent() { delta2 = +1; }
        });
        bindKeyReleased("DOWN", new KeyboardAction() {
            public void handleEvent() { delta2 = 0; }
        });
        bindKeyPressed("A", new KeyboardAction() {
            public void handleEvent() { delta1 = -1; }
        });
        bindKeyReleased("A", new KeyboardAction() {
            public void handleEvent() { delta1 = 0; }
        });
        bindKeyPressed("Z", new KeyboardAction() {
            public void handleEvent() { delta1 = +1; }
        });
        bindKeyReleased("Z", new KeyboardAction() {
            public void handleEvent() { delta1 = 0; }
        });
        // For Dvorak Keyboards
        bindKeyPressed("SEMICOLON", new KeyboardAction() {
            public void handleEvent() { delta1 = +1; }
        });
        bindKeyReleased("SEMICOLON", new KeyboardAction() {
            public void handleEvent() { delta1 = 0; }
        });
    }

    private void ballRestart() {
        deltaX *= -1;
        deltaY *= -1;
        ball.setPosition(res.width/2, res.height/2);
    }

    @Override
    protected void loop() {
        Point pos = ball.getPosition();
        // Verifica se a bola bateu nas paredes.
        if (pos.y <= 0 || pos.y >= res.height-1)
            deltaY *= -1;
        // Verifica se foi ponto do jogador 1
        if (pos.x > res.width) {
            score1++;
            ballRestart();
        }
        // Verifica se foi ponto do jogador 2
        if (pos.x < 0) {
            score2++;
            ballRestart();
        }
        // Verifica se bola bateu no paddel1
        Point bat = paddle1.getPosition();
        if (pos.x == 2 && pos.y >= bat.y && pos.y <= bat.y+PADDLE_SIZE)
            deltaX *= -1;
        // Verifica se bola bateu no paddel2
        bat = paddle2.getPosition();
        if (pos.x == res.width-4 && pos.y >= bat.y && pos.y <= bat.y+PADDLE_SIZE)
            deltaX *= -1;

        // Movimenta bola.
        ball.move(deltaX, deltaY);
        // Movimenta paddle1.
        paddle1.move(0, delta1);
        // Movimenta paddle2.
        paddle2.move(0, delta2);
        // requisita a tela para se desenhar.
        redraw();
    }

}