Desenvolvendo jogos para celular utilizando Java ME
Este artigo focaliza em jogos 2D por que um grande número de telefones celular, em uso, tem hoje recursos muito limitados (tela pequena, pouca memória e suporte gráfico).
Os jogos para celulares ganharam popularidade por fornecer o entretenimento pessoal móvel. Esta popularidade faz com que os jogos desempenhem um papel fundamental na geração de renda para as operadoras de celulares, desenvolvedores de jogos e fabricantes de aparelhos telefônicos. Com o número de jogadores móveis previsto para 220 milhões em todo o mundo até 2009, é esperada uma expansão da atividade de jogos para celular a níveis mais elevados e a conseqüente participação numa fatia maior dos lucros para as operadoras de celular e fabricantes de aparelhos telefônicos.
Os jogos móveis podem ser classificados em três categorias:
- Jogos embutidos: jogos que são codificados no sistema do aparelho celular e são disponibilizados com ele.
- Jogos de SMS: os jogos que utilizam o envio de mensagens de texto, freqüentemente, em forma de concursos e pesquisas ao vivo. Não muito popular, pois o custo do jogo aumenta com cada SMS enviada.
- Jogos de Navegador: estes jogos utilizam um micro navegador embutido no telefone celular. As pessoas podem jogar tais jogos conectados através da sua operadora de celular ou do site de jogos de um fornecedor terceirizado ou descarregá-los para jogarem quando estiverem desconectados. Esta categoria inclui uma ampla escala de jogos, tais como jogos individuais e multiplayer.
Dentre estas três categorias, a dos jogos de navegador é, hoje, o tipo mais popular de jogos para celulares pelo seu conteúdo inovador e rico em multimídia, apresentação atraente, e um custo mais baixo de jogar comparado aos jogos de SMS. Este artigo examina o desenvolvimento de jogos de navegador. A partir deste ponto, neste artigo, o termo “jogo de celular” se referirá aos “aos jogos de navegador".
Observação: este artigo focaliza em jogos 2D por que um grande número de telefones celular, em uso, tem hoje recursos muito limitados (tela pequena, pouca memória e suporte gráfico). Neste contexto, os jogos mais apropriados e comercialmente factíveis são jogos 2D. Porém, vale destacar que como as capacidades dos telefones celulares certamente aumentarão com o tempo, os jogos 3D se tornarão comum num futuro próximo.
Os jogos para celulares podem ser desenvolvidos usando C++, Java ME e o BREW (Binary Runtime Environment for Wireless) para plataforma sem fio da Qualcomm.
Por que escolher Java para o desenvolvimento de jogos para celular?
Embora C++ tenha a vantagem de ser compilado no código nativo com acesso direto aos recursos do sistema e com BREW a plataforma forneça soluções de ponta a ponta aos desenvolvedores de jogos para celulares, Java é a escolha mais popular para o desenvolvimento de jogos. O Java ME é identificado como a linguagem mais conveniente para desenvolver jogos para celulares. As características que levam o Java ME a ser a tecnologia de desenvolvimento para dispositivo móvel mais popular são:
- Java ME desfruta do status de um padrão da indústria apoiado por todos os principais fabricantes de aparelhos de telefone, com a maioria dos telefones celulares da atualidade sendo habilitados para Java.
- Java ME é uma plataforma livre e aberta. Isto ajuda a manter o baixo custo de desenvolvimento e proporciona a flexibilidade necessária com amplo apoio disponível para os desenvolvedores.
- Sua natureza portável (“escreva uma vez, execute em qualquer lugar”) permite que um aplicativo escrito para uma marca/modelo de aparelho de telefone funcione em outras marcas/modelos habilitadas para Java.
- Ele é especialmente otimizado para dispositivos pequenos, é leve, e é altamente segura, pois os aplicativos nele escritos não podem alcançar ou afetar outros aplicativos sendo executados no telefone/dispositivo.
- Java ME é aderente ao MIDP (Mobile Information Device Profile), que é projetado especificamente para o desenvolvimento de aplicativos para dispositivos móveis, considerando sempre suas limitações e restrições. Além disso, a própria versão 2.0 do MIDP dedica uma API inteira para o desenvolvimento dos jogos visando simplificar e agilizar seu desenvolvimento.
O papel do MIDP 2.0 no desenvolvimento de jogos
O MIDP 2.0 é um conjunto de API’s carregadas de características que podem ser utilizadas para desenvolver aplicativos seguros, de rico conteúdo multimídia, para dispositivos móveis. O MIDP 2.0 foi planejado a partir do seu antecessor, MIDP 1.0, com o intuito de prover uma melhor plataforma de desenvolvimento para construção de aplicativos móveis.
O MIDP 2.0 refina ainda mais as características e as funcionalidades fornecidas no MIDP 1.0. Uma das adições importantes feitas ao MIDP é a API Game, ou, para ser preciso, o pacote javax.microedition.lcdui.game. Através desta API, MIDP 2.0 provê aos desenvolvedores de jogos blocos de construção prontos que teriam que ser desenvolvidos do nada utilizando o MIDP 1.0. Estes blocos de construção são classes para criar e controlar vários elementos do jogo, tais como a tela, camadas, e assim por diante. Com isto, o MIDP 2.0 reduz significativamente o tempo gasto no desenvolvimento do jogo.
Os outros dois pacotes de API do MIDP 2.0 essenciais para o desenvolvimento do jogo, também examinados por este artigo, são javax.microedition.midlet e javax.microedition.lcdui.
O pacote javax.microedition.midlet fornece uma base para o desenvolvimento de todos os aplicativos móveis. Nele está presente a classe javax.microedition.midlet., que é a classe base de todos os aplicativos móveis baseados em Java ME (também conhecidas como midlets) e deve ser estendido pelas classes principais de todos os aplicativos móveis. Bastante similar à classe java.applet.Applet, a classe MIDlet proporciona os recursos necessários para criar os midlets.
O pacote javax.microedition.lcdui é necessário para desenvolver a interface gráfica de usuário (UI) para todos os tipos de aplicativos móveis. Esta API fornece classes para criar e controlar componentes de UI (tais como tela, formulário, caixa de texto, e assim por diante) e processar a entrada de dados para os aplicativos móveis. Programadores que tenham experiência de desenvolvimento de GUI com AWT e Swing descobrirão que os elementos do pacote javax.microedition.lcdui são similares aos elementos destas API’s.
Desenvolvendo um jogo para celular
Para compreender as APIs e suas respectivas classes, você começará desenvolvendo um jogo simples, onde o carro deve se movimentar através de uma pista com obstáculos. O jogador usa as teclas da esquerda e da direita do teclado para movimentar o carro evitando a colisão com os obstáculos. O jogo termina quando uma colisão acontece e em seguida a pontuação é exibida. Eu vou chamá-lo de HardDrive.
Observação: o jogo de exemplo é desenvolvido utilizando o J2ME Wireless Toolkit 2.1_01 e J2SE 1.4.2_07 SDK na plataforma Windows 2000.
Como visto na seção anterior, a primeira coisa que você necessita desenvolver é HardDriveMIDlet (Listagem 1) que estende a classe javax.microedition.midlet.MIDlet.
/* HardDriveMIDlet.java : Container MIDlet */
import java.io.IOException;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
public class HardDriveMIDlet extends MIDlet implements CommandListener
{
private Display dgDisplay;
private HardDriveCanvas hdCanvas;
private GameOverCanvas goCanvas;
static final Command ExitCommand = new Command("Exit", Command.EXIT, 0);
public HardDriveMIDlet( )
{ // Cria o Display principal
dgDisplay = Display.getDisplay(this);
}
protected void startApp() //throws MIDletStateChangeException
{
try{
hdCanvas = new HardDriveCanvas(this, "/car.png", "/obstacle.png");
hdCanvas.start();
hdCanvas.addCommand(ExitCommand);
hdCanvas.setCommandListener(this);
}
catch (IOException ioe)
{
System.err.println("Problem loading image "+ioe);
}
dgDisplay.setCurrent(hdCanvas);
}
public void pauseApp()
{
}
public void destroyApp(boolean unconditional)
{
hdCanvas.stop();
}
public void commandAction(Command c, Displayable s)
{
if (c.getCommandType( ) == Command.EXIT)
{
destroyApp(true);
notifyDestroyed( );
}
}
public void HardDriveCanvasGameOver(long time, int score)
{
hdCanvas.stop();
try
{
goCanvas = new GameOverCanvas(this, time, score);
goCanvas.start();
//Command quitCommand = new Command("Quit", Command.EXIT, 1);
goCanvas.addCommand(ExitCommand);
goCanvas.setCommandListener(this);
}
catch (Exception exp)
{
System.err.println("Problem loading image "+exp);
}
dgDisplay.setCurrent(goCanvas);
}
} //fim da classe HardDriveMIDlet
/*Criando o projeto "HardDriveGame".
Lugar dos arquivos fonte Java "C:\WTK21\apps\HardDriveGame\src"
Lugar dos arquivos da aplicação "C:\WTK21\apps\HardDriveGame\res"
Lugar dos arquivos da biblioteca "C:\WTK21\apps\HardDriveGame\lib"
*/
HardDriveMIDlet implementa a interface javax.microedition.lcdui.CommandListener para receber e para processar os eventos de comando gerados durante a execução do aplicativo. Os eventos de comando ocorrem quando comandos como SAIR, CANCELAR, VOLTAR, OK, PARAR e semelhantes são emitidos usando teclas de atalho e são controlados pelo método commandAction() do HardDriveMIDlet.
O HardDriveMIDlet funciona como um recipiente para todas as telas, que são objetos que representam a área disponível para desenhar na tela do dispositivo celular. Neste caso, o midlet contém HardDriveCanvas, que estende a classe javax.microedition.lcdui.game.GameCanvas. O GameCanvas é uma tela especial destinada ao desenho de gráficos animados para o jogo.
Outra tela que o HardDriveMIDlet contém é GameOverCanvas, que estende a classe javax.microedition.lcdui.Canvas. Canvas é uma tela simples destinada para o desenho de texto, linhas e formas simples. Este tipo de facilidade deve ser utilizado quando se necessita fazer desenhos simples na tela, ao invés de gráficos pesados; por exemplo, para exibir splash screen, telas de final e de instruções do jogo. Um jogo pode conter uma quantidade indeterminada de telas, mas somente uma tela é exibida de cada vez, usando-se o método setCurrent() da classe javax.microedition.lcdui.Display.
O HardDriveMIDlet contém também três outros métodos importantes, também conhecidos como métodos do ciclo de vida. Eles são startApp(), pauseApp() e destroyApp(), correspondendo aos estados Ativo, Pausado e Destruído do midlet. No método startApp() de HardDriveMIDlet, HardDriveCanvas é instanciado e o comando SAIR é adicionado a ele usando o método addCommand() de HardDriveCanvas (Listagem 2).
import java.io.IOException;
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
class HardDriveCanvas extends GameCanvas implements Runnable
{
private HardDriveMIDlet midlet;
private Sprite carSprite;
private LayerManager layerManager;
private ObstacleManager obsManager;
private boolean gameRunning;
private boolean collision = false;
private int width;
private int height;
private long gameDuration;
public HardDriveCanvas(HardDriveMIDlet hdmidlet, String carImageName, String obsImageName)
throws IOException
{
super(true);
this.midlet = hdmidlet;
layerManager = new LayerManager();
width = getWidth();
height = getHeight();
layerManager.setViewWindow(1,1,width - 2, height -2);
createCar(carImageName);
obsManager = new ObstacleManager( this, layerManager, obsImageName);
}
private void createCar(String carImageName) throws IOException
{
Image carImage = Image.createImage(carImageName);
carSprite = new Sprite(carImage);
carSprite.setPosition(width/2,height - 30);
layerManager.append(carSprite);
}
public void start()
{
gameRunning = true;
Thread gameThread = new Thread(this);
gameThread.start();
}
public void stop() { gameRunning = false; }
public void run()
{
Graphics g = getGraphics();
int timeStep = 300;
obsManager.renderObstacles();
long startTime = System.currentTimeMillis();
while (gameRunning)
{
tick();
input();
render(g);
long endTime = System.currentTimeMillis();
long duration = (int)(endTime - startTime);
gameDuration = duration / 1000; //tempo do jogo em segundos
try
{
Thread.sleep(timeStep );//- duração);
obsManager.MoveObstacles();
}
catch (InterruptedException ie) { stop(); }
}
}
private void tick()
{
if(!collision)
checkCollision();
if (collision)
{
//Fim do jogo
int score = obsManager.getScore();
midlet.HardDriveCanvasGameOver(gameDuration,score);
//stop();
}
}
private void input()
{
int keyStates = getKeyStates();
int currentY = carSprite.getY();
int currentX = carSprite.getX( );
if ((keyStates & LEFT_PRESSED) != 0)
carSprite.setPosition (Math.max(0, currentX-5), currentY);
if ((keyStates & RIGHT_PRESSED) != 0)
carSprite.setPosition(Math.min(170, currentX + 5), currentY);
}
private void render(Graphics g)
{
g.setColor(255, 255, 255);
g.fillRect(0, 0,getWidth()-3, getHeight()-3);
layerManager.paint(g,0,0);
flushGraphics();
}
private void checkCollision( )
{
if (obsManager.hitTest(carSprite) )
collision = true;
}
}
O HardDriveCanvas implementa uma interface java.lang.Runnable para poder funcionar como uma thread, o que é necessário para executar independentemente da estrutura de repetição existente no jogo. A repetição do jogo é executada continuamente até que as circunstâncias necessárias para pará-lo se tornem verdadeiras (neste exemplo, quando o carro colide com um obstáculo ou quando o jogador sai do jogo em qualquer momento usando a tecla da saída).
Na Listagem 2 a estrutura de repetição do jogo HardDriveCanvas.java está no método run(). Esta é uma típica repetição de jogo que consiste em chamadas aos seguintes métodos em uma determinada ordem: tick(), input(), e render(). O método tick() verifica se as circunstâncias necessárias para parar o jogo se tornaram verdadeiras e muda o estado do jogo caso positivo. O método input() controla a tecla do jogo (teclas atribuídas para movimentar o carro) e executa as ações necessárias para responder ao usuário quando este pressionar as teclas. A renderização do jogo é controlada com o método render().
HardDriveCanvas usa também uma instância de javax.microedition.lcdui.game.LayerManager, que adiciona e gerencia múltiplas camadas do HardDriveCanvas, cada camada representando uma sprite. Uma sprite é um elemento visual básico de um jogo, por exemplo, um personagem que se movimenta em uma tela do jogo e interage com outros sprites (no nosso caso, o carro ou os obstáculos são os sprites que podem colidir um com o outro). Cada sprite forma uma camada visual, que pode ser inteira ou parcialmente transparente, assentada na tela do jogo; estas camadas são empilhadas. Uma sprite é um objeto da classe javax.microedition.lcdui.game.Sprite que fornece a funcionalidade de exibir, transformar e girar a sprite junto com a detecção de colisão para a sprite. O método tick(), internamente, usa o método collidesWith() da classe da sprite para verificar a colisão (ver Listagem 3).
/*ObstacleManager.java : cria e gerencia obstáculos */
import java.io.IOException;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
public class ObstacleManager
{
private Sprite [] obstacles;
//private boolean [] active;
//private int [] xpos;
//private int [] ypos;
private LayerManager layerManager;
private GameCanvas hdCanvas;
private static final int MAX_OBS = 10; //número máximo de obstáculos aparecendo no carro, pode ser alterado para qualquer número
private static int Score = 0;
/** Cria uma nova instância de ObstacleManager */
public ObstacleManager(GameCanvas canvas, LayerManager lm, String obsFileName)
throws IOException
{
hdCanvas = canvas;
layerManager = lm;
createObstacles(obsFileName);
}
private void createObstacles(String obsImageName)
throws IOException
{
Image obsImage = Image.createImage(obsImageName);
obstacles = new Sprite[MAX_OBS];
//active = new boolean[MAX_OBS];
//xpos = new int[MAX_OBS];
//ypos = new int[MAX_OBS];
for (int i=0; i < MAX_OBS; i++)
{
obstacles[i] = new Sprite(obsImage);
obstacles[i].setVisible(false);
layerManager.append(obstacles[i]);
}
}
//private int lastRendered = 0;
public void renderObstacles()
{
for (int i=0; i < MAX_OBS; i++)
{
//obstacles[i].setPosition(locations[i], 0);
ReSetObstaclesPosition();
obstacles[i].setVisible(true);
}
}
public void MoveObstacles()
{
for (int i=0; i < obstacles.length; i++)
{
int currentX = obstacles[i].getX( );
int newY = obstacles[i].getY( ) + 14; // 14 é a altura da imagem
if (newY >= 177)
{
//obstacles[i].setVisible(false);
Score+=1;
java.util.Random random = new java.util.Random();
int yPos = (random.nextInt()>>>1) % 170;
int xPos = (random.nextInt()>>>1) % 170;
//obstacles[i].setPosition(currentX , - yPos);
obstacles[i].setPosition(xPos , - yPos);
}
else
{
obstacles[i].setPosition(currentX, newY);
}
}
}
public void ReSetObstaclesPosition()
{
java.util.Random random = new java.util.Random();
for( int i = 0; i < 10 ; i++)
{
int nyPos = (random.nextInt()>>>1) % 170;
int nxPos = (random.nextInt()>>>1) % 170;
obstacles[i].setPosition(nxPos, -nyPos );
}
}
public boolean hitTest(Sprite carSprite)
{
boolean retval = false;
for (int i=0; i < obstacles.length; i++)
{
if (carSprite.collidesWith(obstacles[i], true))
{
retval = true;
break;
}
}
return retval;
}
public int getScore()
{
return Score;
}
}
O HardDriveCanvas instancia um objeto da classe ObstacleManager (Listagem 3) que converte e move as sprites de obstáculo na tela do jogo e verifica se elas colidiram com a sprite do carro.
Um ObstacleManager cria e gerencia os obstáculos que aparecem aleatoriamente na trajetória do carro. Em nome da simplicidade e da conveniência, ObstacleManager.java tem valores codificados de um número máximo de obstáculos que aparecem de cada vez na trajetória do carro. Este valor pode ser modificado da forma que o desenvolver desejar.
Para criar e exibir obstáculos em posições aleatórias, ObstacleManager emprega uma estratégia simples. Ele cria o lote inicial de 10 sprites de obstáculo e usa LayerManager para adicionar estes sprites à tela do jogo. Então, ela ajusta as suas posições usando valores de coordenadas x e y gerados aleatoriamente e começa a movê-los para o fundo da tela do jogo. Assim que cada um destes obstáculos alcança o fundo da tela sem colidir com o carro, sua posição é novamente reinicializada usando a mesma técnica, enquanto a pontuação do jogo é acrescida de um. Consequentemente, ObstacleManager reutiliza as mesmas sprites de obstáculo e as exibe aleatoriamente.
Por fim, GameOverCanvas, apresentado na Listagem 4, é uma tela simples que recebe a duração do jogo e a informação da pontuação de jogo de HardDriveMIDlet e a exibe.
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
class GameOverCanvas
extends Canvas
{
private HardDriveMIDlet midlet;
private long time;
private int score;
GameOverCanvas(HardDriveMIDlet hdmidlet, long gtime, int gscore)
{
super();
this.midlet = hdmidlet;
this.time = gtime;
this.score = gscore;
setFullScreenMode(true);
}
public void start() { }
public void paint(Graphics g)
{
int width = getWidth();
int height = getHeight();
g.setColor(0,0,0);
g.fillRect(0, 0, width, height);
g.setColor(255,100,0);
g.fillRect(0, 0, width - 3, height - 3);
g.setFont(Font.getFont(Font.FACE_PROPORTIONAL,
Font.STYLE_BOLD,
Font.SIZE_LARGE));
int centerX = width / 2;
int centerY = height / 2;
g.setColor(0x00FFFFFF); // white
drawText(g, centerX, centerY - 1);
drawText(g, centerX, centerY + 1);
drawText(g, centerX - 1, centerY);
drawText(g, centerX + 1, centerY);
g.setColor(0x00000000); // black
drawText(g, centerX, centerY);
}
private void drawText(Graphics g, int centerX, int centerY)
{
int fontHeight = g.getFont().getHeight();
int textHeight = 3 * fontHeight;
int topY = centerY - textHeight / 2;
g.drawString("GAME OVER",
centerX,
topY,
Graphics.HCENTER | Graphics.TOP);
g.drawString("Time: " + time + " s",
centerX,
topY + fontHeight,
Graphics.HCENTER | Graphics.TOP);
g.drawString(("Score: " + score),
centerX,
topY + 2 * fontHeight,
Graphics.HCENTER | Graphics.TOP);
}
}
Configurando o jogo de exemplo para distribuição
O J2ME Wireless Toolkit fornece o KToolbar, uma ferramenta útil para compilar, pré-verificar, empacotar e testar um aplicativo móvel. Esta ferramenta simplifica e automatiza a maioria destas tarefas.
Agora que o código do jogo está pronto, ele necessita ser organizado na estrutura de diretório fornecida pela ferramenta, apresentada na Listagem 5. Para conseguir isto, inicie KToolbar e crie um novo projeto, HardDriveGame, que conterá o jogo HardDrive que foi desenvolvido na pasta de aplicações do diretório de instalação do J2ME Wireless Toolkit's.
HardDriveGame (game project name, user-defined)
|___src
|
|___bin
|
|___classes
|
|___res
|
|___lib
|
|___tmpclasses
|
|___tmplib
Copie todos os quatro arquivos fonte do jogo para a pasta src e os arquivos de imagens da pasta res. Agora, as seguintes ações abrirão o projeto do jogo HardDriveGame, efetuará o build do projeto e executará a pré-verificação do aplicativo.
Open project button -> choose HardDriveGame -> Build button
Se alguns erros ocorrerem durante o processo de construção, eles serão exibidos na janela de KToolbar. Modifique o código fonte do jogo na pasta src para corrigi-los; a depuração tem que ser feita manualmente porque KToolbar não fornece um depurador. Do contrário, se nenhum erro ocorrer, a mensagem “Construção completa” é exibida na janela de KToolbar.
Uma vez que o build do projeto do jogo é feito com sucesso, este pode ser executado no emulador ara testar o aplicativo.
O jogo está agora completo e pronto para ser distribuído. Para compactar o jogo em um arquivo .jar para distribuição usando KToolbar, as seguintes ações devem ser executadas:
Open project button -> choose HardDriveGame -> Project menu -> Package -> Create package / Create obfuscated package
A opção do menu “Create Package” criará um arquivo padrão .jar. Uma vez que a compactação esteja completa, a localização do arquivo .jar é exibida na janela de KToolbar junto com um arquivo .jad (Descritor de Aplicativo Java) criado automaticamente durante a compactação e usado pelo emulador durante a execução do jogo (Figura 1).
Opcionalmente, o midlet do jogo é assinado após a compactação usando o seguinte comando:
Project menu -> Sign
Isto cria uma assinatura digital para o arquivo .jar, e o adiciona ao arquivo .jad.
Agora, os arquivos .jar e .jad do jogo, junto com o arquivo MANIFEST.MF, criado por KToolbar e agrupada com a midlet suite, estão prontos para serem distribuídos.
Esta é uma das formas de como os jogos 2D podem ser desenvolvidos utilizando Java. Agora você já é capaz de desenvolver seus próprios jogos.
Conclusão
O desenvolvimento de jogos para celular se transformou em uma indústria lucrativa. Java ME e MIDP 2.0 auxiliam os desenvolvedores de jogos a ter acesso a todo o potencial desta indústria, fornecendo uma plataforma para o desenvolvimento de jogos para celulares de forma conveniente, rápida e eficiente. MIDP 2.0 destina um pacote de API inteiro ao desenvolvimento de jogos que fornece blocos de desenvolvimento pré-construídos para simplificar e agilizar o desenvolvimento de jogos para celulares.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo