A fim de facilitar a manipulação de eventos em um jogo J2ME eu implementei um “KeyListener” para usar com o GameCanvas, que usa o Pattern Observer e facilita muito a propagação de eventos pelos objetos do jogo!
Ficou muito prático!
Basta colocar as classes no seu projeto para utilizá-las. Estou anexando os códigos-fonte para o caso de distorções no código causadas pelo editor de texto. O próprio fonte anexado pode servir como framework base para o desenvolvimento de qualquer jogo 2D. Se compilado e executado ele exibe uma tela preta com a contagem de fps em um canto.
Isso é parte de um framework simples que eu desenvolvi e utilizo para desenvolver jogos de forma prática.
Abaixo as classes criadas para tal finalidade, algumas das quais eu simplesmente modifiquei da JRE:
Interface Observer inalterada:
[code]/*
- @(#)Observer.java 1.20 05/11/17
- Copyright 2006 Sun Microsystems, Inc. All rights reserved.
- SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package controller.pattern;
/**
- A class can implement the <code>Observer</code> interface when it
- wants to be informed of changes in observable objects.
- @author Chris Warth
- @version 1.20, 11/17/05
- @see java.util.Observable
-
@since JDK1.0
/
public interface Observer {
/*- This method is called whenever the observed object is changed. An
- application calls an <tt>Observable</tt> object’s
- <code>notifyObservers</code> method to have all the object’s
- observers notified of the change.
- @param o the observable object.
- @param arg an argument passed to the <code>notifyObservers</code>
-
method.
void changeNotified(Observable o, Object arg);
}[/code]
Classe Observable - A original da JRE utiliza Vector, mas não temos esse recurso em J2ME, então eu modifiquei essa classe para utilizar uma SimpleList que eu implementei, cujo código está mais abaixo:
[code]/*
- @(#)Observable.java 1.39 05/11/17
- Copyright 2006 Sun Microsystems, Inc. All rights reserved.
- SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package controller.pattern;
import model.util.SimpleList;
/**
-
This class represents an observable object, or “data” in the model-view
-
paradigm. It can be subclassed to represent an object that the application
-
wants to have observed.
-
-
An observable object can have one or more observers. An observer may be any
-
object that implements interface <tt>Observer</tt>. After an observable
-
instance changes, an application calling the <code>Observable</code>'s
-
<code>notifyObservers</code> method causes all of its observers to be
-
notified of the change by a call to their <code>update</code> method.
-
-
The order in which notifications will be delivered is unspecified. The
-
default implementation provided in the Observable class will notify Observers
-
in the order in which they registered interest, but subclasses may change
-
this order, use no guaranteed order, deliver notifications on separate
-
threads, or may guarantee that their subclass follows this order, as they
-
choose.
-
-
Note that this notification mechanism is has nothing to do with threads and
-
is completely separate from the <tt>wait</tt> and <tt>notify</tt> mechanism
-
of class <tt>Object</tt>.
-
-
When an observable object is newly created, its set of observers is empty.
-
Two observers are considered the same if and only if the <tt>equals</tt>
-
method returns true for them.
-
@author Chris Warth
-
@version 1.39, 11/17/05
-
@see java.util.Observable#notifyObservers()
-
@see java.util.Observable#notifyObservers(java.lang.Object)
-
@see java.util.Observer
-
@see java.util.Observer#changeNotified(java.util.Observable, java.lang.Object)
-
@since JDK1.0
*/
public class Observable {
private boolean changed = false;
private SimpleList obs;/** Construct an Observable with zero Observers. */
public Observable() {
obs = new SimpleList();
}/**
- Adds an observer to the set of observers for this object, provided that
- it is not the same as some observer already in the set. The order in
- which notifications will be delivered to multiple observers is not
- specified. See the class comment.
- @param o
-
an observer to be added. - @throws NullPointerException
-
if the parameter o is null.
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}/**
- Deletes an observer from the set of observers of this object. Passing
- <CODE>null</CODE> to this method will have no effect.
- @param o
-
the observer to be deleted.
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}/**
- If this object has changed, as indicated by the <code>hasChanged</code>
- method, then notify all of its observers and then call the
- <code>clearChanged</code> method to indicate that this object has no
- longer changed.
-
- Each observer has its <code>update</code> method called with two
- arguments: this observable object and <code>null</code>. In other words,
- this method is equivalent to: <blockquote><tt>
- notifyObservers(null)</tt>
- </blockquote>
- @see java.util.Observable#clearChanged()
- @see java.util.Observable#hasChanged()
-
@see java.util.Observer#changeNotified(java.util.Observable, java.lang.Object)
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
-
If this object has changed, as indicated by the <code>hasChanged</code>
-
method, then notify all of its observers and then call the
-
<code>clearChanged</code> method to indicate that this object has no
-
longer changed.
-
-
Each observer has its <code>update</code> method called with two
-
arguments: this observable object and the <code>arg</code> argument.
-
@param arg
-
any object. -
@see java.util.Observable#clearChanged()
-
@see java.util.Observable#hasChanged()
-
@see java.util.Observer#changeNotified(java.util.Observable, java.lang.Object)
/
public void notifyObservers(Object arg) {
/- a temporary array buffer, used as a snapshot of the state of current
- Observers.
*/
Object[] arrLocal;
synchronized (this) {
/*
* We don’t want the Observer doing callbacks into arbitrary code
* while holding its own Monitor. The code where we extract each
* Observable from the Vector and store the state of the Observer
* needs synchronization, but notifying observers does not (should
* not). The worst result of any potential race-condition here is
* that: 1) a newly-added Observer will miss a notification in
* progress 2) a recently unregistered Observer will be wrongly
* notified when it doesn’t care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}for (int i = arrLocal.length - 1; i >= 0; i–)
((Observer) arrLocal[i]).changeNotified(this, arg);
}
/**
- Clears the observer list so that this object no longer has any observers.
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
- Marks this <tt>Observable</tt> object as having been changed; the
- <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
/**
- Indicates that this object has no longer changed, or that it has already
- notified all of its observers of its most recent change, so that the
- <tt>hasChanged</tt> method will now return <tt>false</tt>. This method is
- called automatically by the <code>notifyObservers</code> methods.
- @see java.util.Observable#notifyObservers()
-
@see java.util.Observable#notifyObservers(java.lang.Object)
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
- Tests if this object has changed.
- @return <code>true</code> if and only if the <code>setChanged</code>
-
method has been called more recently than the -
<code>clearChanged</code> method on this object; -
<code>false</code> otherwise. - @see java.util.Observable#clearChanged()
-
@see java.util.Observable#setChanged()
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
- Returns the number of observers of this <tt>Observable</tt> object.
-
@return the number of observers of this object.
*/
public synchronized int countObservers() {
return obs.size();
}
}[/code]
Classe SimpleList - Não temos List nem ArrayList em J2ME. Utilizando conceito de estruturas de dados (lista simplesmente encadeada) eu implementei essa classe SimpleList, que a classe Observable utiliza. Eu implementei apenas os métodos utilizados pela classe Observable, mas essa classe SimpleList pode ser complementada e utilizada se você precisar de um recurso parecido com um java.util.ArrayList:
[code]package model.util;
public class SimpleList {
private Node initialNode;
private Node finalNode;
private int size;
public void addElement(Object object) {
if(object == null) {
return;
}
Node node = new Node(object);
if(size == 0) {
initialNode = finalNode = node;
} else {
finalNode.setNextNode(node);
finalNode = node;
}
size++;
}
public Object removeElement(int position) {
if(size == 0 || position < 0 || position >= size) {
return null;
}
Node previousNode = null;
Node currentNode = initialNode;
for(int i=1; i<=position; i++) {
previousNode = currentNode;
currentNode = currentNode.getNextNode();
}
Object value = currentNode.getValue();
if(initialNode.equals(finalNode)) {
initialNode = finalNode = null;
} else if(currentNode.equals(initialNode)) {
initialNode = currentNode.getNextNode();
currentNode.setNextNode(null);
} else if(currentNode.equals(finalNode)) {
finalNode = previousNode;
finalNode.setNextNode(null);
} else {
previousNode.setNextNode(currentNode.getNextNode());
}
size--;
return value;
}
public Object removeElement(Object object) {
if(object == null || size == 0) {
return null;
}
Node previousNode = null;
Node currentNode = initialNode;
while(!currentNode.getValue().equals(object)) {
previousNode = currentNode;
currentNode = currentNode.getNextNode();
if(currentNode == null) {
break;
}
}
if(currentNode != null) {
Object value = currentNode.getValue();
if(initialNode.equals(finalNode)) {
initialNode = finalNode = null;
} else if(currentNode.equals(initialNode)) {
initialNode = currentNode.getNextNode();
currentNode.setNextNode(null);
} else if(currentNode.equals(finalNode)) {
finalNode = previousNode;
finalNode.setNextNode(null);
} else {
previousNode.setNextNode(currentNode.getNextNode());
}
size--;
return value;
}
return null;
}
public Object[] removeAllElements(){
Object[] elements = toArray();
initialNode = finalNode = null;
size = 0;
return elements;
}
public boolean contains(Object object) {
if(object == null || size == 0) {
return false;
}
Node currentNode = initialNode;
while(!currentNode.getValue().equals(object)) {
currentNode = currentNode.getNextNode();
if(currentNode == null) {
break;
}
}
return currentNode != null;
}
public Object[] toArray(){
if(size == 0) {
return null;
}
Object[] elements = new Object[size];
Node currentNode = initialNode;
for(int i=0; i<size; i++) {
elements[i] = currentNode.getValue();
currentNode = currentNode.getNextNode();
}
return elements;
}
public int size(){
return size;
}
private class Node {
private Object value;
private Node nextNode;
public Node(Object value) {
setValue(value);
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public Node getNextNode() {
return nextNode;
}
public void setNextNode(Node nextNode) {
this.nextNode = nextNode;
}
}
}[/code]
Com isso em mãos eu implementei uma classe abstrata KeyObserver, que implementa a interface Observer e traduz o keyState chamando métodos abstratos próprios (upPressed, downPressed, leftPressed, rightPressed, firePressed…). As classes que extenderem KeyObserver e forem registradas no Observer terão esses métodos automaticamente chamados, o que encapsula todo o mecanismo de manipulação de teclas deixando o desenvolvedor livre para se concentrar nas especificidades dos objetos de jogo:
[code]package controller.base;
import javax.microedition.lcdui.game.GameCanvas;
import controller.pattern.Observable;
import controller.pattern.Observer;
public abstract class KeyObserver implements Observer {
public void changeNotified(Observable observable, Object keyStateWrapper) {
if(!(keyStateWrapper instanceof Integer)) {
return;
}
int keyState = ((Integer)keyStateWrapper).intValue();
if ((keyState & GameCanvas.UP_PRESSED) != 0) {
upPressed();
}
if ((keyState & GameCanvas.DOWN_PRESSED) != 0) {
downPressed();
}
if ((keyState & GameCanvas.LEFT_PRESSED) != 0) {
leftPressed();
}
if ((keyState & GameCanvas.RIGHT_PRESSED) != 0) {
rightPressed();
}
if ((keyState & GameCanvas.FIRE_PRESSED) != 0) {
firePressed();
}
if ((keyState & GameCanvas.GAME_A_PRESSED) != 0) {
aPressed();
}
if ((keyState & GameCanvas.GAME_B_PRESSED) != 0) {
bPressed();
}
if ((keyState & GameCanvas.GAME_C_PRESSED) != 0) {
cPressed();
}
if ((keyState & GameCanvas.GAME_D_PRESSED) != 0) {
dPressed();
}
}
public abstract void upPressed();
public abstract void downPressed();
public abstract void leftPressed();
public abstract void rightPressed();
public abstract void firePressed();
public abstract void aPressed();
public abstract void bPressed();
public abstract void cPressed();
public abstract void dPressed();
}[/code]
Exemplos de classes que extendem o KeyObserver são as classes abstratas Sprite e EnJine, que eu implementei.
Notem que nessas classes abstratas eu coloquei implementações vazias dos métodos upPressed, downPressed, etc… de forma similar aos Adapters da JRE, então dentro de um Sprite, por exemplo, o desenvolvedor pode sobrescrever apenas os métodos de que vai precisar (firePressed, por exemplo).
[code]package controller.base;
import controller.pattern.Observable;
import model.base.Animated;
public abstract class EnJine extends KeyObserver implements Animated {
public abstract void initSprites();
public abstract void registerObservers(Observable keyObservable);
public void upPressed(){}
public void downPressed(){}
public void leftPressed(){}
public void rightPressed(){}
public void firePressed(){}
public void aPressed(){}
public void bPressed(){}
public void cPressed(){}
public void dPressed(){}
}[/code]
[code]package model.base;
import controller.base.KeyObserver;
public abstract class Sprite extends KeyObserver implements Animated {
protected double posX;
protected double posY;
protected double velX;
protected double velY;
protected int width;
protected int height;
protected boolean active;
protected boolean visible;
public Sprite() {
restore();
}
public void suspend() {
setActive(false);
setVisible(false);
}
public void restore() {
setActive(true);
setVisible(true);
}
public double getPosX() {
return posX;
}
public void setPosX(double posX) {
this.posX = posX;
}
public double getPosY() {
return posY;
}
public void setPosY(double posY) {
this.posY = posY;
}
public double getVelX() {
return velX;
}
public void setVelX(double velX) {
this.velX = velX;
}
public double getVelY() {
return velY;
}
public void setVelY(double velY) {
this.velY = velY;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public void upPressed(){}
public void downPressed(){}
public void leftPressed(){}
public void rightPressed(){}
public void firePressed(){}
public void aPressed(){}
public void bPressed(){}
public void cPressed(){}
public void dPressed(){}
}[/code]
Finalmente vou mostrar como eu criei o MIDlet para ilustrar de forma mais concreta a utilização do KeyObserver. Não vou postar o código completo do jogo aqui por dois motivos: não está completo e com esse MIDLet em mãos você pode desenvolver o jogo que quiser! Notem a chamada a mainEnJine.registerObservers(keyObservable);, que permite que o engine principal registre quantos outros "listeners" quiser, propagando as ações do usuário facilmente!
[code]package view;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import controller.MainEnJine;
import controller.manager.FPSManager;
import controller.pattern.Observable;
public class TileGameME extends MIDlet {
protected void destroyApp(boolean restart) throws MIDletStateChangeException {
if(restart) {
this.notifyDestroyed();
} else {
startApp();
}
}
protected void pauseApp() {
this.notifyPaused();
}
protected void startApp() throws MIDletStateChangeException {
Object lock = new Object();
Display.getDisplay(this).setCurrent(Screen.getInstance().start(lock));
synchronized(lock) {
while(!Screen.getInstance().running) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
}
System.out.println("Thread Started");
synchronized(lock) {
while(Screen.getInstance().running) {
try {
lock.wait();
} catch (InterruptedException e) {}
}
}
System.out.println("Thread Stopped");
destroyApp(Screen.getInstance().restart);
}
public static class Screen extends GameCanvas implements Runnable, CommandListener {
private static final Screen instance = new Screen();
public static final Font BASE_FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
private MainEnJine mainEnJine;
private volatile boolean running;
private boolean restart;
private Object midletLock;
private long keyTime;
private Observable keyObservable;
protected Screen() {
super(true);
this.addCommand(new Command("Sair", Command.EXIT, 0));
this.addCommand(new Command("Reiniciar", Command.STOP, 0));
this.setCommandListener(this);
}
public Screen start(Object midletLock) {
this.midletLock = midletLock;
mainEnJine = new MainEnJine();
mainEnJine.initSprites();
registerObservers();
new Thread(this).start();
return this;
}
private void registerObservers() {
keyObservable = new Observable(){
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(arg);
}
};
keyObservable.addObserver(mainEnJine);
mainEnJine.registerObservers(keyObservable);
}
public void run() {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
running = true;
synchronized(midletLock) {
midletLock.notify(); // Notify started
}
while(running) {
long time = System.currentTimeMillis();
this.flushGraphics();
FPSManager.getInstance().update();
paint();
checkActions();
update();
sleep(time);
}
synchronized(midletLock) {
midletLock.notify(); // Notify stopped
}
}
private void paint() {
Graphics g = this.getGraphics();
g.setColor(0x000000);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
mainEnJine.paint(g);
FPSManager.getInstance().paint(g);
}
private void checkActions() {
int keyState = this.getKeyStates();
// Gerenciando atraso entre pressionamento de teclas
if(keyState != 0 && System.currentTimeMillis() - keyTime >= 250) {
keyTime = System.currentTimeMillis();
keyObservable.notifyObservers(new Integer(keyState));
}
}
private void update() {
mainEnJine.update();
}
private void sleep(long time) {
long pauseTime = 1000/30 - (System.currentTimeMillis() - time);
try {
Thread.sleep(pauseTime > 5 ? pauseTime : 5);
} catch (InterruptedException e) {}
}
public static Screen getInstance() {
return instance;
}
public void commandAction(Command comm, Displayable disp) {
if(!this.equals(disp))return;
switch(comm.getCommandType()) {
case Command.STOP:
restart = false;
running = false;
break;
case Command.EXIT:
restart = true;
running = false;
break;
}
}
}
}[/code]
E para facilitar a ilustração, vejam a minha classe MainEnJine, a partir da qual eu posso colocar outras implementações de engine (ex: PauseScreenEnJine) e sprites, tiles, etc… Vejam como o método registerObservers() adiciona sprites como listeners, de forma que as ações do usuário serão propagadas a todos os objetos do jogo que precisam dessas instruções!
[code]package controller;
import javax.microedition.lcdui.Graphics;
import model.TileGame;
import model.base.Sprite;
import controller.base.EnJine;
import controller.pattern.Observable;
public class MainEnJine extends EnJine {
private Sprite[] sprites;
public void initSprites() {
sprites = new Sprite[]{
new TileGame()
};
}
public void registerObservers(Observable keyObservable) {
for(int i=0; i<sprites.length; i++) {
keyObservable.addObserver(sprites[i]);
}
}
public void paint(Graphics g) {
for(int i=0; i><sprites.length; i++) {
sprites[i].paint(g);
}
}
public void update() {
for(int i=0; i><sprites.length; i++) {
sprites[i].update();
}
}
public void firePressed(){
System.out.println("MainEnJine - Fire");
}
}[/code]
Bom, agora vocês estão com a faca e o queijo na mão! Mais do que isso só postando um código completo de um jogo, mas isso só quando eu fizer o meu tutorial completo!
8)>