{progz}

Játékciklus MIDP 2.0-ban: GameCanvas, getKeyStates és Sprite

A MIDP 1.0 idején minden játékfejlesztő megírta a saját játékkönyvtárát: dupla puffert, sprite-ot, csempés hátteret. A MIDP 2.0 ebből sokat átvett a javax.microedition.lcdui.game csomagba. Ma már a legtöbb új telefon MIDP 2.0-s, ezért érdemes ezekre az osztályokra építeni. Ebben a jegyzetben egy egyszerű, de rendesen felépített játékciklust mutatok be.

A váz

import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;

public class JatekVaszon extends GameCanvas implements Runnable {
    private static final int KEPKOCKA_MS = 50;   // kb. 20 fps
    private volatile boolean fut;
    private Sprite hajo;
    private int x = 60, y = 150;

    public JatekVaszon(Image hajoKep) {
        super(true);                 // a billentyűeseményeket elnyomjuk
        hajo = new Sprite(hajoKep, 16, 16);   // 16x16-os képkockák
    }

    public void indit() {
        fut = true;
        new Thread(this).start();
    }

    public void leallit() { fut = false; }

    public void run() {
        Graphics g = getGraphics();
        while (fut) {
            long kezdet = System.currentTimeMillis();

            bemenet();
            frissit();
            rajzol(g);
            flushGraphics();         // a háttérpuffer kirakása egyben

            long eltelt = System.currentTimeMillis() - kezdet;
            if (eltelt < KEPKOCKA_MS) {
                try { Thread.sleep(KEPKOCKA_MS - eltelt); }
                catch (InterruptedException e) { }
            }
        }
    }

    private void bemenet() {
        int k = getKeyStates();
        if ((k & LEFT_PRESSED) != 0)  x -= 3;
        if ((k & RIGHT_PRESSED) != 0) x += 3;
        if ((k & UP_PRESSED) != 0)    y -= 3;
        if ((k & DOWN_PRESSED) != 0)  y += 3;
    }

    private void frissit() {
        hajo.setPosition(x, y);
        hajo.nextFrame();
    }

    private void rajzol(Graphics g) {
        g.setColor(0xF5F6F8);
        g.fillRect(0, 0, getWidth(), getHeight());
        hajo.paint(g);
    }

    protected void hideNotify() { leallit(); }
}

Miért így?

Saját szál. A GameCanvas-nál nem a rendszer hívja a paint()-et, hanem mi rajzolunk a getGraphics()-től kapott háttérpufferbe, majd a flushGraphics() egyszerre rakja ki a képet. Így nincs villogás.

Fix képkockaidő. A ciklus mindig 50 ms-ra egészít ki. Gyors telefonon többet alszik, lassún kevesebbet. Ha egy készülék a 50 ms-ot sem tudja tartani, a játék lelassul, de nem ugrál. Ennél a műfajnál ez a jobb választás.

Billentyűállapot. A super(true) miatt a keyPressed események nem érkeznek meg, cserébe a getKeyStates() egyetlen bitmaszkban adja vissza, mi van éppen lenyomva. Átlós mozgásnál (fel és jobbra egyszerre) ez a különbség érezhető.

Ütközés

if (hajo.collidesWith(akadaly, false)) {     // gyors, téglalap alapú
    if (hajo.collidesWith(akadaly, true)) {  // pixelpontos, csak ha kell
        robbanas();
    }
}

A collidesWith második paramétere kapcsolja a pixelpontos vizsgálatot. Ez az átlátszó pixeleket is figyelembe veszi, de lassú. A fenti kétlépcsős megoldás a legtöbb esetben elég gyors marad.

Háttér: TiledLayer és LayerManager

A pályát érdemes TiledLayer-ből építeni (egy kép csempékre vágva, és egy tömb, amely megmondja, melyik csempe hol van), az egészet pedig egy LayerManager-be tenni. A setViewWindow hívással így egyetlen sorban lehet görgetni a nézetet, a rajzolási sorrendet pedig a rétegek sorrendje adja.

Végül: a szál leállítását a MIDlet pauseApp metódusából is hívd meg. A hideNotify önmagában nem minden telefonon érkezik meg hívás közben.