Parallax Scrolling in Java ME
Parallax Scrolling in Java ME.
This is what i use im my game. Guess Which game? I decided to release the code 'cause i couldn't find it in any web page. The net is an open book (or university) and i've taken too much from it....now it's my time to contribute... I still want to give back to the community for helping me so much when I was a novice, and as a result I decided to write this tutorial.
It also demonstrate how to:
- pause/unpause an app (RIGHT SOFT KEY / incoming phone call);
- get high frames por second (fps);
- get randomNumbers / randomBoolean;
- use custom fonts (bitmap fonts);
- use LayerManager/TiledLayer.
Go to my projects and grab JavaMEParallaxScrolling project (with all resources and full source code). You can easily open it in NetBeans 6.9
Please don't forget to check my game projects (need feedback):
Queops, LunarPatrol & X-Rally.
Thank you.
Another yet is to come....maybe a new version of Space Impact...
Brought to you by George Roberto Peres (JavaMan)
reflexus@ig.com.br
/**********************************************************************************************/
import java.io.IOException;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class Main extends MIDlet {
private ParallaxScrollingCanvas canvas; private Display display;
public Main() {
//
}
public void startApp() {
if (canvas==null) canvas = new ParallaxScrollingCanvas();
display = Display.getDisplay(this);
//Display.getDisplay(this).setCurrent(canvas);
try {
canvas.init();
canvas.start();
}
catch (IOException e) { }
display.setCurrent(canvas);
}
public void pauseApp() {
//
}
public void destroyApp(boolean destroy) throws MIDletStateChangeException {
display = null;
if (canvas!=null) canvas.stop();
System.gc();
notifyDestroyed();
}
}
/**********************************************************************************************/
/*
* This class demonstrate how to use LayerManager/TiledLayer. * by George Roberto Peres * reflexus@ig.com.br */
import java.io.IOException; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.LayerManager; import javax.microedition.lcdui.game.TiledLayer;
/**
* * @author George Roberto Peres */
public final class Design {
// Tiled Layers private Image back, front, solo; protected TiledLayer layerBack, layerFront, layerSolo; // fonte private Image imgFont; // imagem da fonte.png protected int charLarg, charAlt;
protected Design() {
try {
layerBack = defineCenario(layerBack, getBack(), 4);
layerFront = defineCenario(layerFront, getFront(), 4);
layerSolo = defineCenario(layerSolo, getSolo(), 2);
}
catch (IOException ex) { }
}
// ordem e' importante. o ultimo a ser anexado representa a camada mais profunda
protected void updateLayerManager(LayerManager lm) throws IOException {
lm.append(layerSolo);
lm.append(layerFront);
lm.append(layerBack);
}
protected TiledLayer defineCenario(TiledLayer layer, Image image, int rows) throws IOException {
short c=1, l=0;
layer = new TiledLayer(32, rows, image, 16, 16);
for (byte lin=0; lin<rows; lin++) {
for (byte col=0; col<32; col++) {
layer.setCell(col, lin, c);
c++;
if ((c-1)%(l+16)==0) c = (byte)(l+1);
}
l+=16;
c+=16;
}
return layer;
}
private Image getBack() throws IOException {
if (back == null) {
back = Image.createImage("/back.png");
}
return back;
}
private Image getFront() throws IOException {
if (front == null) {
front = Image.createImage("/front.png");
}
return front;
}
private Image getSolo() throws IOException {
if (solo == null) {
solo = Image.createImage("/solo.png");
}
return solo;
}
protected Image getFont() {
if (imgFont == null)
try {
imgFont = Image.createImage("/fonts.png");
charLarg = imgFont.getWidth() / 96; //9
charAlt = imgFont.getHeight(); //12
}
catch (IOException e) { }
return imgFont;
}
}
/**********************************************************************************************/
/*
* This class demonstrate how to get randomNumber/randBoolean. * brought to you by George Roberto Peres * reflexus@ig.com.br */
import java.util.Random; import javax.microedition.lcdui.Graphics;
public final class Tools {
private static final Random RAND = new Random();
//static { RAND = new Random(); }
protected static final int GRAPHICS_TOP_LEFT = Graphics.LEFT | Graphics.TOP;
// Dimensoes da tela Game Canvas
protected static final int LARG = 240; // largura da tela (x)
protected static final int ALT = 320; // altura da tela (y)
protected static final int Y = 200; // altura da canvas principal game
protected static final boolean TELA_CHEIA = true; // tela cheia (full screen)
// codigos dos softkey variam dependendo do modelo do celular
// -6 e -7 sao valores validos para telefones Nokia. Consultar para outros modelos.
protected static final int LEFT_SOFTKEY_CODE = -6;
protected static final int RIGHT_SOFTKEY_CODE = -7;
/****************************************************************************
* gerador de numero aleatorio inteiro *
* @param max numero gerado e' maior ou igual a zero e menor que max *
* @return r, um numero pseudo-randomico *
****************************************************************************/
protected static int randomNumber(int max) {
return Math.abs(RAND.nextInt() % max); // valor absoluto de -(max-1) ate' max-1
}
/****************************************************************************
* gerador de booleano aleatorio *
* @return boolean *
****************************************************************************/
protected static boolean randBoolean() {
return (randomNumber(2)==1);
}
/*// numero randomico inteiro entre min e max
protected static int getRand(int min, int max) {
return (Math.abs(RAND.nextInt()) % (max - min)) + min;
}*/
}
/**********************************************************************************************/
/*
* This is a simple ParallaxScrolling example. * It also demonstrate how to: pause/unpause an app (RIGHT SOFT KEY / phone call); * use custom fonts; get high frames por second (fps): smaller painted screen area, * no threads (only canvas thread, of course), no timers, no synchronized. * The code presented here is easy to understand (clean code). * brought to you by George Roberto Peres * reflexus@ig.com.br */
import java.io.IOException; //import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.game.GameCanvas; import javax.microedition.lcdui.game.LayerManager;
/**
* @author George Roberto Peres * @version 1.0 */
public class ParallaxScrollingCanvas extends GameCanvas implements Runnable {
private final Graphics g = getGraphics();
private LayerManager lm;
private Thread t;
private Design design;
private static final byte[] PAUSE = { 80, 65, 85, 83, 69 };
private static final byte[] FPS = { 70, 80, 83, 58 };
private boolean pausa;
private byte cont; // timing
private int a; // auxiliar geral
private boolean isRunning;
private boolean showFPS = true;
// ciclos = frames por segundo mostrado na tela
private int fps;
private int cyclesThisSecond;
private long lastFPSTime;
// sobrescreve o metodo de javax.microedition.lcdui.Canvas
public void keyPressed(int keyCode) {
if (keyCode==Tools.RIGHT_SOFTKEY_CODE) {
if (!pausa) pausa(); else resume();
}
else
if (keyCode==Tools.LEFT_SOFTKEY_CODE) {
if (pausa) resume();
}
else
if (keyCode == this.KEY_NUM0) {
showFPS = !showFPS; // inverte boolean
}
}
/** Creates a new instance of theCanvas */
public ParallaxScrollingCanvas() {
super(true);
setFullScreenMode(Tools.TELA_CHEIA);
//g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL));
design = new Design();
g.setColor(0,0,0);
g.fillRect(0, 0, getWidth(), getHeight());
}
protected void init() throws IOException {
// gerenciador de camadas
lm = new LayerManager();
design.updateLayerManager(lm);
design.layerBack.setPosition(0, 56);
design.layerFront.setPosition(0, 80);
design.layerSolo.setPosition(0, 140);
//System.out.println("lm size = "+lm.getSize());
}
public void start() {
isRunning = true;
t = new Thread(this);
//t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
public void stop() {
isRunning = false;
t = null;
}
private void tarefas() {
// cenario do fundo. move mais devagar
if (cont%5==0) {
design.layerBack.move(-1, 0);
// empacota para repetir os mesmos graficos novamente
if (Math.abs(design.layerBack.getX())>255) design.layerBack.setPosition(0, 56);
}
// cenario da frente. move mais depressa
if (cont%3==0) {
design.layerFront.move(-1, 0);
// empacota para repetir os mesmos graficos novamente
if (Math.abs(design.layerFront.getX())>255) design.layerFront.setPosition(0, 80);
}
// move layerSolo
design.layerSolo.move(-1, 0);
// empacota para repetir os mesmos graficos novamente
if (Math.abs(design.layerSolo.getX())>255) design.layerSolo.setPosition(0, 140);
}
private void renderiza() {
// background
g.setColor(0, 0, 0);
g.fillRect(0, 0, Tools.LARG, Tools.Y);
lm.paint(g, 0, 0); // desenha layer manager
if (showFPS) {
drawString(FPS, 0, 0);
drawScoreInt(fps, 2, design.charLarg*4, 0);
}
drawScoreInt(Math.abs(design.layerSolo.getX()), 3, 120, 0);
flushGraphics(0, 0, Tools.LARG, Tools.Y); // descarrega graficos
}
// desenha caracter de acordo com o codigo da tabela ASCII
// util para acrescentarmos caracteres especiais customizados
private void drawCharASCII(int ASCII, int x, int y) {
// caractere espaco ou nao imprimivel ou o caractere>255 nao sao desenhados
if (ASCII < 33 || ASCII > 255) return;
g.clipRect(x, y, design.charLarg, design.charAlt); // altera clip pra tamanho do caractere
// desenha o caractere dentro do retangulo. (cIndex - 32) * charLarg = posicao do caractere
g.drawImage(design.getFont(), x - ((ASCII - 32) * design.charLarg), y, Tools.GRAPHICS_TOP_LEFT);
g.setClip(0, 0, Tools.LARG, Tools.ALT); // reset o clip - full screen, ou seja, volta o normal
}
private void drawString(byte []s, int x, int y) {
int cx = x; // posicao inicial
// loop atraves de todos os caracteres na string
for (a = 0; a < s.length; a++) {
drawCharASCII(s[a], cx, y);
cx += design.charLarg; // va para a proxima posicao para desenhar
}
}
// desenha score int em x,y (ASCII de 48~57). ex: 000777 (6 digitos)
private void drawScoreInt(int score, int digitos, int x, int y) {
if (digitos<1 || digitos>8) return;
int d = 1;
for (a=1; a<digitos; a++) d *= 10;
a = x; // posicao inicial
for (int i=0; i<digitos; i++) {
drawCharASCII(score/d + 48, a, y); // +48 pra coincidir com tabela ASCII
a += design.charLarg; // va para a proxima posicao para desenhar
score -= d*(score/d);
d /= 10;
}
}
public void hideNotify() {
if (!pausa) pausa();
}
// nada e' feito, porque despausa e' feito manualmente
public void showNotify() {
//
}
protected void pausa() {
pausa = true;
stop();
drawString(PAUSE, Tools.LARG/2 - (design.charLarg*5)/2, Tools.Y/2 - design.charAlt/2);
flushGraphics(Tools.LARG/2 - (design.charLarg*5)/2, Tools.Y/2 - design.charAlt/2,
design.charLarg*5, design.charAlt);
}
protected void resume() {
start();
pausa = false;
}
public void run() {
while(isRunning) {
// metodo pra conseguir uma medida do FPS mostrado na tela
if (System.currentTimeMillis() - lastFPSTime > 1000) {
lastFPSTime = System.currentTimeMillis();
fps = cyclesThisSecond;
cyclesThisSecond = 0;
} else
cyclesThisSecond++;
// timing base 60. why? it divides by 1,2,3,4,5,6,10,12,15,20,30
cont++;
if (cont==60) cont=0;
//
tarefas();
renderiza();
// dorme um pouco
try { Thread.sleep(0); }
catch (InterruptedException e) { }
}
}
}
/**********************************************************************************************/

