private keyStatus = 0;
/** * Called when a key is pressed. */ protected void keyPressed(int keyCode) { if (this.getGameAction(keyCode) == UP) { keyStatus = KEY_NUM2; } else if(this.getGameAction(keyCode) == DOWN){ keyStatus = KEY_NUM8; } else if(this.getGameAction(keyCode) == LEFT){ keyStatus = KEY_NUM4; } else if(this.getGameAction(keyCode) == RIGHT){ keyStatus = KEY_NUM6; } else if(this.getGameAction(keyCode) == FIRE){ keyStatus = KEY_NUM5; } else { keyStatus = keyCode; } }
/** * Called when a key is released. */ protected void keyReleased(int keyCode) { keyStatus = 0; }
|
Em geral,
este método atualiza o valor do
campo keyStatus.
Se nenhuma
tecla estiver sendo pressionada neste momento,
então o valor do keyStatus
é 0; se não, é algum valor inteiro. O
movimento
atual do jogador será explicado mais
tarde.
Algoritmo do jogo
Esta
classe inicializa uma classe interna TimerTask. A
iniciação real é feita por um Timer,
que invoca periódicamente o TimerTask
(por default, cada 50 milissegundos, mas estes são
ajustáveis pelo jogador humano na tela de
níveis). A
classe TimerTask
executa a função myMove2 ().
private Timer timer;
public void startTimer() { // this class is being executed periodically. TimerTask mover = new TimerTask() { public void run() { myMove2(); } };
timer = new Timer(); // invokes the mover class. try { // if anything is being set by the level // screen timer.schedule(mover, parent.getLevel(), parent.getLevel()); } catch (IllegalArgumentException e) { timer.schedule(mover, 50, 50); } }
|
O método myMove2() tem duas
regras:
1. Verifica o valor do keyStatus (que
é ajustado pelos métodos keyPressed() e keyReleased())
e move conforme
o ícone do
jogador humano.
2. Move o ícone do jogador do
computador de
acordo com a evolução da
situação no jogo.
Conforme explicado anteriormente, cada
tecla
pressionada (na seta e tecla de seleção) ajusta o
valor
do campo keyStatus.
De acordo com esse valor, calculamos as coordenadas do ícone
do jogador humano.
// coordinates of human player icon private int meX, meY; // coordinates of the ball private int xBall, freeBallY, yBall; // coordinates of the field's corner private int x1, x2, x3, x4, y1, y2, y3, y4;
private void myMove2() { // some code
// define me Coordinates switch(keyStatus) { // up case Canvas.KEY_NUM2: meY--; if (meY < y1) meY = y1; break; // down case Canvas.KEY_NUM8: meY++; if (meY > y4) meY = y4; break; // right case Canvas.KEY_NUM6: meX++; if ((x2 + x3) / 2 < meX) { meX = (x2 + x3) / 2; } if (ballOwner == 0 && compMode != 5) xBall = meX + 8; break; // left case Canvas.KEY_NUM4: meX--; if ((x1 + x4) / 2 > meX) { meX = (x1 + x4) / 2; } if (ballOwner == 0 && compMode != 5) xBall = meX + 8; break; // fire case Canvas.KEY_NUM5: if (compMode == 2) { originalY = freeBallY; compMode = 5; } break; default: ///// }
// some code... }
|
Como o
jogo roda, os controles
do computador dos ícones de jogadores opondo-se. As
ações da CPU
dos jogadores
variam de acordo com as situações que evoluem
durante o
jogo
- o computador se comporta de acordo com a
situação. O campo interno
compMode
armazena um código de situação (code
situation) para cada dado
momento. As situações são:
1. Salto
da bola: Esta situação ocorre
somente no começo do jogo. Os ícones de ambos os
jogadores estão no meio do campo
e o ícone da bola está a direita entre eles
caindo para
baixo. A mudança do status
da situação
é dada quando um ou outro
ícone do jogador trava a bola.
2. O
ícone do jogador humano tem a bola:
Quando o jogador humano se move, podemos ver a bola sendo driblada com
o jogador humano. O
computador move seu
jogador para o jogador humano e tenta roubar a bola
quando está bem
próximo a ele.
3. O ícone do jogador do
computador tem a bola: Nesta
situação, o computador tenta mover-se para a
cesta do
jogador humano. Se o ícone do jogador humano for na
frente dele, tentará
contorná-lo. Quando o jogador do computador está
bem
perto à cesta do jogador humano, tentará
arremessar na cesta.
4. Não
usado.
5.
O
jogador humano arremessa na cesta: O computador
muda esta situação quando o jogador pressiona
a tecla
de seleção (ou
disparar, em alguns dispositivos). Podemos ver a bola que
está
sendo jogada para a cesta. Se o arremesso
for bem sucedido, os dois jogadores voltam para o lado do campo
correspondente ao início do jogo e o computador muda a
situação
para 3 (compMode=3).
6. O
jogador do computador arremessa na bola: Quando
o jogador do computador tem a bola e o ícone do jogador do
computador
está bem perto da cesta
do jogador humano, tentará automaticamente arremessar na
cesta. Se o arremesso for bem
sucedido, os
dois jogadores voltam para
o lado do campo correspondente ao
início
do jogo e o computador muda a situação
para 2 (compMode=2).
// coordinates of human player icon private int meX, meY; // coordinates of the ball private int xBall, freeBallY, yBall; // coordinates of the field's corner private int x1, x2, x3, x4, y1, y2, y3, y4; // computer player situation state private int compMode;
private void myMove2() { // some code...
// switch by situation switch (compMode) { /* *free ball jumps */ case 1: // this method controls the jump // movement of the ball ballJump();
// decides who gets the ball
// computer gets the ball if (Math.abs(xBall - compX) <= 21 && Math.abs(freeBallY - compY) <= 5) { case3Mode = 1; compMode = 3; delay = 0; } // me kidnaps the ball if (Math.abs(meX - xBall) <= 21 && Math.abs(meY - freeBallY) <= 5) { compMode = 2; delay = 0; delay2 = 0; }
// calculate comp moves if (compY > freeBallY) { compY--; } if (compY < freeBallY) { compY++; } if (compX > xBall) { compX--; } if (compX < xBall) { compX++; } compCheckBorders(); break; /* *the ball is at me player */ case 2: xBall = meX + 6; freeBallY = meY; ballOwner = 0; delay2++; ballJump(); // computer steals the ball if (Math.abs(meX - compX) <= 21 && Math.abs(meY - compY) <= 5 && delay >= 10) { case3Mode = 1; compMode = 3; delay = 0; delay2 = 0; } if (delay2 > 30) { if (Math.abs(meX + 20 - compX) > Math.abs(meY - compY)) { if (compX > meX + 20) compX--; else compX++; } else { if (compY > meY) compY--; else compY++; } // check borders for comp players compCheckBorders(); } break; /* *the ball is at computer player */ case 3: xBall = compX - 3; freeBallY = compY; ballOwner = 1; ballJump(); // me kidnaps the ball if (Math.abs(meX - compX) <= 21 && Math.abs(meY - compY) <= 5 && delay >= 5) { compMode = 2; delay = 0; delay2 = 0; } /* here we compute how the computer player icon will move */ switch (case3Mode) { // go back from player case 1: compX++; if (compX > meX + 29) { case3Mode = 2; } break; // go side from player case 2: if (compY < myHeight * 3 / 4) { compY++; compYDirection = 1; case3Mode = 3; } compY--; compYDirection = 0; case3Mode = 3; break; // continue go side case 3: if (compYDirection == 1 && compY <= meY + 15) { compY++; if (compY > y4) compY = y4; } else if (compYDirection == 0 && compY >= meY - 15) { compY--; if (compY < y1) compY = y1; } else { case3Mode = 4; } break; // go forward case 4: if (compX > myWidth * 11 / 32) { compX--; } else if (compX < myWidth * 1 / 4 - 3) { compX++; } else { originalY = freeBallY; compMode = 6; } break; // finally throw the ball case 5: case3Mode = 1; originalY = freeBallY; compMode = 6; yBall = 9; deltaX = 1; break; default: // }
if (compX - meX <= 15 && Math.abs(compY - meY) <= 10) { case3Mode = 1; } // check borders for comp player compCheckBorders(); break; /* *not in use */ case 4: break; /* *me throws the ball */ case 5: freeBallY = originalY - deltaY[deltaX]; deltaX++; xBall++;
// checks if the ball hits the basket if (deltaX >= 29 && (xBall >= (x2 + x3) / 2 - 10 && xBall <= (x2 + x3) / 2 + 10)) { myScore += 2; oldY = 0; compMode = 3;
// reset player and the balls meX = myWidth * 3 / 16; meY = myHeight * 3 / 4; compX = myWidth * 13 / 16; compY = myHeight * 3 / 4; xBall = compX - 8; yBall = 3; deltaX = 0; } // if the ball reaches the floor else if (deltaX >= 29 && (xBall < (x2 + x3) / 2 - 10 || xBall > (x2 + x3) / 2 + 10)) { freeBallY = meY; compMode = 1; oldY = 0; deltaX = 0; } else { oldY = freeBallY; } break; /* *comp throws the ball */ case 6: freeBallY = originalY - deltaY[deltaX]; deltaX++; xBall--;
// checkes if the ball hits the basket if (deltaX >= 29) { compScore += 2; oldY = 0; compMode = 2;
// reset player and the balls meX = myWidth * 3 / 16; meY = myHeight * 3 / 4; compX = myWidth * 13 / 16; compY = myHeight * 3 / 4; xBall = meX + 8; yBall= 3; deltaX = 0; } else { oldY = freeBallY; } break; default: //sdfgsdfgsdgf }
/* after the coordinates of the human player icon, the computer player icon and the ball has been set we can go to the last stage, which is painting the screen. */ repaint(); }
/* this method controls the jump movement of the ball */ private void ballJump() { if (ballDir == 0) { yBall--; if (yBall < 3) { ballDir = 1; } } else { yBall++; if (yBall > 9) { ballDir = 0; } } }
/* this function checks if the computer passed the border of the field */ private void compCheckBorders() { if (compY < y1) { compY = y1; } if (compY > y4) { compY = y4; } if (compX > (x2 + x3) / 2) { compX = (x2 + x3) / 2; } if (compX < (x1 + x4) / 2) { compX = (x1 + x4) / 2; } }
|
Depois
que todas as
coordenadas foram ajustadas, podemos prosseguir ao estágio
final, que é
realmente pintar a tela.
Isto é feito
chamando o método paint().
Chamamos este método no fim da função move(),
chamando repaint().
/** * paints the screen */ public void paint(Graphics g) {
Graphics saved = g; // these fields show the clock in the game String clockMinuteStr = new String(); String clockSecondStr = new String();
// initialize a buffered image if (offscreen != null) { g = offscreen.getGraphics(); }
// cleans the screen g.setColor(255, 255, 255); g.fillRect(0, 0, this.getWidth(), this.getHeight());
// define corners of field x1 = myWidth*3/16; x2 = myWidth*13/16; x3 = myWidth - 2; x4 = 2; y1 = myHeight * 1 / 2; y4 = myHeight - 1;
// draw solid background g.setColor(255, 255, 255); g.fillRect(offsetWidth, offsetHeight, myWidth, myHeight); g.setColor(0, 0, 0); g.drawImage(screenShot, offsetWidth, offsetHeight, 0);
// draw Scores g.fillRect(offsetWidth + myWidth / 4, offsetHeight + myHeight / 8, myWidth / 2, myHeight / 4); g.setColor(255, 255, 255); g.drawRect(offsetWidth + myWidth / 4, offsetHeight + myHeight / 8, myWidth / 2, myHeight / 4); clockMinuteStr = String.valueOf(clockMinute); clockSecondStr = String.valueOf(clockSecond); if (clockMinuteStr.length() == 1) clockMinuteStr = "0" + clockMinuteStr; if (clockSecondStr.length() == 1) clockSecondStr = "0" + clockSecondStr; g.drawString(":", offsetWidth + myWidth / 2, offsetHeight + 15, Graphics.TOP|Graphics.LEFT); g.drawString(clockMinuteStr, offsetWidth + myWidth / 2 - 15, offsetHeight + 17, Graphics.TOP|Graphics.LEFT); g.drawString(clockSecondStr, offsetWidth + myWidth / 2 + 5, offsetHeight + 17, Graphics.TOP|Graphics.LEFT); g.drawString(String.valueOf(myScore), offsetWidth + myWidth / 2 - 25, offsetHeight + 32, Graphics.TOP|Graphics.LEFT); g.drawString(String.valueOf(compScore), offsetWidth + myWidth / 2 + 20, offsetHeight + 32, Graphics.TOP|Graphics.LEFT);
// paint player me g.drawImage(mePlayer, offsetWidth + meX, offsetHeight + meY - 19, 0);
// paint Computer Player g.drawImage(compPlayer, offsetWidth + compX, offsetHeight + compY - 19, 0);
//paintBall g.drawImage(tinyBall, offsetWidth + xBall, offsetHeight + freeBallY - yBall, 0);
// paints the buffered image if (g != saved) { saved.drawImage(offscreen, 0, 0, Graphics.LEFT | Graphics.TOP); }
}
|
Tratando
a interrupção
de chamada
Quando uma
chamada ocorrer no meio do jogo, a tela Canvas
pode
desaparecer, assim quiser congelar o estado
do jogo
(salvar todos os
dados a respeito das posições dos jogadores,
posição da bola, número de
pontos, etc.). Neste caso, o congelamento do jogo
é feito
parando o
temporizador. Existem duas funções relacionadas
à Canvas,
desaparecendo
e reaparecendo. hideNotify()
é chamado depois que Canvas
desaparece e
showNotify()
é
chamado quando Canvas
reaparece. Paramos o temporizador
no evento hideNotify()
e reativamos o
temporizador no evento
showNotify().
** * called when the screen disappears */ protected void hideNotify() { if (finishGame == 0) { parent.setCurrent("MainMenu"); } // stops the internal timer and thus // freezes the game. stopTimer(); }
/** * called when the screen reappears */ protected void showNotify() { // restarts the internal timers. startTimer(); }
|
Salvando
os dados persistentes
As aplicações de JME têm
um
método para armazenar dados mesmo depois que o
usuário
terminou a aaplicação. Controlamos estes dados
com
a classe RecordStore.
Nesta aplicação, necessitamos
armazenar dados persistentes, permitindo o
jogador parar o jogo em um dado momento, retiramos a
aplicação e retornamos o
jogo algumas horas mais tarde e de
continuar
exatamente do momento onde parou. Os dados que necessitamos
armazenar incluem: cronometrar o jogo,
coordenar de
dois ícones de jogador, coordenar a bola, etc.
Arranjamos todos estes dados em um byte[] array e
só então podemos armazená-lo.
/** * Writes all the game data into recordstore * @param rec */ public void writeRMS(byte[] rec) { try { rs = RecordStore.openRecordStore("pocket", true); if (rs.getNumRecords() > 0) rs.setRecord(1, rec, 0, 31); else rs.addRecord(rec, 0, 31); rs.closeRecordStore(); } catch (Exception e) {} }
/** * Reads the data from the recordstore * @return */ public byte[] readRMS() { byte[] rec = new byte[31]; try { rs = RecordStore.openRecordStore("pocket", true); rec = rs.getRecord(1); rs.closeRecordStore(); } catch (Exception e) {} return rec; }
/** * * Deletes all the record stores */ public void deleteRMS() { if (RecordStore.listRecordStores() != null) { try { RecordStore.deleteRecordStore ("pocket"); } catch (Exception e) {} } }
|
Outras telas
Duas outras telas nesta
aplicação são InstructionsForm
(mostrado na Figura 9) e AboutForm.
Estendemos estas duas classes da classe Form e
implementamos também
CommandListener
para segurar as teclas pressionadas. A classe Form é
parte da API
de alto
nível (high-level) e nos
permite introduzir facilmente o texto
liso, imagens e outros artigos a ser indicados.

Figura
9. Tela de instrução
public class InstructionsForm extends Form implements CommandListener { private TestMidletMIDlet parent; private Command mainMenu = new Command("Back", Command.BACK, 1);
public InstructionsForm(TestMidletMIDlet parent) { super("Instructions"); addCommand(mainMenu); setCommandListener(this);
// insert some text to be seen. this.append("The objective of this game is to shoot as many baskets as possible while preventing your opponent shoot to your basket.\n\n"); this.append("move your player using 4 for moving left, 2 for moving up, 6 for moving right and 8 for moving down.\n\n"); this.append("press 5 to throw the ball");
this.parent = parent; }
public void commandAction(Command c, Displayable d) { if (c == mainMenu) { parent.setCurrent("MainMenu2"); } } }
|
Conclusão
Neste
artigo, discutimos algumas características mais
comuns no ambiente JME.
Estas características incluem a classe MIDlet, que
é a classe base
para todas as aplicações JME, a classe de API's de
baixo-nível
(low-level)
Canvas e
classes de API
de alto-nível (high-level),
tais
como a List
e Form.
Cobrimos também a estrutura organizacional do jogo,
tal como as telas típicas da aplicação.
Como mencionado
anteriormente, esta é uma
descrição breve de um jogo típico em JME. Embora os
jogos sejam abundantes em ambiente já sustentados, existem
muitos outros usos para aplicações ME, tais como os
leitores com quotas de estoque, leitores RSS, etc.
Recursos
Kobi Krasnoff
é sério do programador e tem o computador como hobby.