V tomto článku se už skutečně pustíme do programování. Začneme úplně minimálním základem, který pak budeme později rozšiřovat o různé vymoženosti. Nejprve však musíme aplikaci spustit a ukončit a mezi tím také něco nakreslit na displej.

Třídy aplikace

Hra se bude pro začátek skládat ze tří tříd:

  • GameMIDlet – hlavní třída aplikace rozšiřující abstraktní třídu javax.microedition.midlet.MIDlet.
  • GameCanvas – třída zodpovědná za vlastní hru.
  • GameSprite – pomocná třída sloužící k reprezentaci pohyblivých objektů. Implementuje rozhraní třídy javax.microedition.lcdui.game.Layer z herního API MIDP 2.0, ke kterému přidává navíc ještě metodu pro kontrolu vzájemných kolizí objektů dle vzoru třídy javax.microedition.lcdui.game.Sprite z téhož herního API. U telefonů, které implementují MIDP 2.0, se místo této třídy použije třída javax.microedition.lcdui.game.Sprite.

GameMIDlet

V našem případě bude hlavní třída velmi jednoduchá, protože píšeme minimalistickou aplikaci bez větší logiky. Jediné, co musíme udělat, je při spuštění aplikace vytvořit instanci třídy GameCanvas a zobrazit ji na displeji.

protected void startApp() throws MIDletStateChangeException {
   if(canvas==null){
      // Spuštění aplikace
      canvas = new GameCanvas(this);
      Display.getDisplay(this).setCurrent(canvas);
      } else {
      // Aktivace aplikace po jejím pobytu v pasivním stavu, v našem případě zde není potřeba dělat nic.
   }
}

Při ukončení aplikace v metodě destroyApp() zatím také nemusíme dělat skoro nic. Pokud bychom však chtěli ukládat aktuální stav hry, patřila by tato akce sem.

protected void destroyApp(boolean arg0) {
   /* Dáváme aplikačnímu manažeru najevo, že midlet přešel do zrušeného stavu */
   notifyDestroyed();
}

Pokud jste v J2ME úplní začátečníci, doporučuji přečíst si článek J2ME v kostce – první midlet.

GameCanvas

Na displej lze kreslit komponenty, které implementují rozhraní javax.microedition.lcdui.Displayable. Tyto zobrazitelné komponenty se dělí na základní dvě skupiny – vysokoúrovňové komponenty a nízkoúrovňové komponenty.

Vysokoúrovňové komponenty neumožňují odchytávání událostí klávesnice, ani kreslení jednodušších útvarů na konkrétní souřadnice. Hodí se například k zobrazování formulářů a seznamů položek (viz článek J2ME v kostce – kreslíme na displej). To je pro hru samozřejmě nedostatečné, takže třída GameCanvas bude rozšiřovat třídu javax.microedition.lcdui.Canvas, která jako jediná ve specifikaci MIDP 1.0 umožňuje nízkoúrovňovou grafiku. Specifikace MIDP 2.0 rozšiřuje nabídku nízkoúrovňových komponent o formulářovou položku CustomItem, pomocí které je možné napsat i jednodušší hry, ale její popis nespadá do rámce tohoto článku.

Třída GameCanvas je celkem komplexní. Musí vykreslovat hru, pravidelně aktualizovat polohu předmětů, reagovat na stisk kláves uživatelem a umožnit uživateli ukončit aplikaci.

Abstraktní příkaz

Po svém předku javax.microedition.lcdui.Canvas třída GameCanvas zdědila možnost ovládání pomocí abstraktních příkazů. Toto ovládání je společné se všemi zobrazitelnými komponentami. Praktické provedení je následující (ukázka z konstruktoru třídy GameCanvas):

/* vytvoření nového příkazu, první parametr určuje jeho text, druhý jeho typ a třetí prioritu */
endCmd = new Command(„Konec“, Command.EXIT, 1);
/* přidání příkazu komponentě */
addCommand(endCmd);
/* nastavení posluchače událostí, který obsluhuje vyvolání příkazu uživatelem */
setCommandListener(this);

Umístění a způsob zobrazení abstraktních příkazů na displeji je zcela závislé na implementaci MIDP v telefonu. Obvyklé například bývá, že jeden příkaz se zobrazí na displeji dole k některému ze softkey (kontextová klávesa) a druhý softkey obsahuje rozbalovací menu se zbylými příkazy. Tento způsob zobrazování má tu nevýhodu, že aplikace nemá k dispozici celý displej, ale je ochuzená o pruh s abstraktními příkazy.

Obrázek ukazuje, jak abstraktní příkazy zobrazují telefony značky Nokia. Jsou-li příkazy maximálně dva, vejdou se všechny najednou na displej, je-li jich víc, vejde se na displej jeden a ostatní se zobrazí po stisknutí odpovídajícího softkey na zvláštní obrazovce.

Nokia - dva příkazy
Nokia – dva příkazy

Nokia - více příkazů
Nokia – více příkazů

Nokia - menu
Nokia – menu

Telefony značky Siemens přítomnost abstraktních příkazů pouze naznačí šipkou v pravém dolním rohu displeje a po stisku odpovídajícího softkey teprve zobrazí všechny příkazy.

Siemens - naznačení přítomnosti příkazů
Siemens – naznačení přítomnosti příkazů

Siemens - menu
Siemens – menu

Jak si jistě pozorní čtenáři všimli, třída GameCanvas implementuje rozhraní javax.microedition.lcdui.CommandListener. Toto rozhraní obsahuje pouze metodu commandAction(Command command, Displayable displayable), která slouží k obsluze události vyvolání příkazu. Parametr command obsahuje příkaz, který byl vyvolán, a parametr displayable obsahuje zobrazitelnou komponentu, v níž událost nastala. Nás bude zajímat pouze parametr command, protože v aplikaci zatím máme jen jednu zobrazitelnou komponentu. Kdybychom však měli aplikaci složenou z více grafických komponent, byl by například příkaz „Zpět“ typickou ukázkou příkazu, který obsahují skoro všechny obrazovky a jehož zpracování se liší v závislosti na obrazovce, kde událost nastala.

public void commandAction(Command command, Displayable displayable) {
   if(command==endCmd){
      midlet.destroyApp(true);
   }
}

Kreslení na displej

Veškeré kreslení probíhá v metodě paint(Graphics graphics) zděděné ze třídy javax.microedition.lcdui.Canvas. Nelze předpokládat, že je stále zobrazené, co jsme nakreslili při předchozím volání této metody, je potřeba pokaždé nakreslit vše znova. Parametr graphics se může používat pouze v rámci metody paint() (a samozřejmě také v rámci metod volaných z této metody).

Při každém volání metody jsou nastaveny některé vlastnosti grafiky na výchozí hodnoty:

  • aktuální barva je černá
  • font je nastaven na výchozí font
  • styl čáry je nepřerušovaný (Graphics.SOLID)
  • počátek souřadnic je v levém horním rohu displeje

Aplikační manažer zavolá metodu paint() jenom v případě, že je Canvas právě zobrazený na displeji. O překreslení displeje lze zažádat metodou repaint(), která neblokuje běh aplikace (ukončí se nezávisle na volání metody paint()). To však neznamená, že bude displej okamžitě překreslen, neboť jeho překreslení závisí na viditelnosti překreslované oblasti.

Pokud si chceme na překreslení displeje počkat a teprve poté pokračovat v běhu aplikace, musíme použít po volání metody repaint() ještě metodu serviceRepaints(). Tato metoda si vynutí obsluhu všech zatím neobsloužených požadavků na překreslení displeje a ukončí se teprve až po jejím provedení. Jedinou výjimkou je případ, kdy Canvas není viditelný, v tom případě volání metody serviceRepaints() neprovede nic. Při zobrazování nějaké animace se celkem hodí vykreslit všechny její kroky, takže budeme volat funkci repaint() spolu s funkcí serviceRepaints().

V konstruktoru třídy GameCanvas je také potřeba nahrát obrázky. V našem případě můžeme nahrát klidně všechny, včetně těch, které použijeme až při výbuchu lodi. Při tvorbě aplikací s větším počtem velkých obrázků se však pravidelně stává, že se všechny nevejdou telefonu do paměti. Pak je potřeba důsledně uvolňovat všechny zdroje v okamžiku, kdy nejsou potřeba, a nahrávat je až těsně před jejich použitím.

Obrázky v J2MEWTK dáme do adresáře J2MEWTK_HOME/Blabouch/res. Tyto obrázky při vytváření JAR souboru s aplikací skončí přímo v kořeni tohoto JAR souboru a nahrají se následujícím způsobem:

try{
   Image background = Image.createImage(„/background.png“);
} catch(Exception e){
   e.printStackTrace();
}

Před ukázkou metody paint() se hodí ještě blíže popsat způsob umisťování obrázků a textových řetězců na displej. Metody Graphics.drawImage() a Graphics.drawString() v prvním parametru dostávají objekt, který chceme vykreslit. Druhý a třetí parametr určují souřadnice kotvy, poslední parametr pak říká, v jakém místě obrázku nebo textu se kotva nachází. Umístění kotvy se definuje pomocí bitového součtu (operátor |) konstanty třídy Graphics definující její x souřadnici a konstanty definující její y souřadnici. V následující ukázce je kotva umístěna v obou případech v levém horním rohu objektu:

protected void paint(Graphics g) {
   // nastavení barvy na bílou
   g.setColor(0xffffff);
   // překreslení celého displeje aktuální barvou
   g.fillRect(0,0,getWidth(), getHeight());
   // nakreslení obrázku background
   g.drawImage(background, 0, 0, Graphics.TOP | Graphics.LEFT);
   /* zde se vykreslují jednotlivé předměty */
   // nastavení barvy na červenou
   g.setColor(0x840400);
   // nakreslení počtu bodů
   g.drawString(„“ + score, 0, 2, Graphics.TOP | Graphics.LEFT);
}

GameSprite

Třídu GameSprite použijeme k reprezentaci lodi, mincí a bomb. Jak už je řečeno výše, je to velmi minimalistická implementace třídy Sprite z herního rozhraní MIDP 2.0. Třída Sprite navíc umožňuje spoustu praktických věcí, například animace obrázků či jejich transformace. Také metoda collidesWith(GameSprite sprite, boolean pixelLevel) v GameSprite bohužel v rámci MIDP 1.0 nemá možnost zjistit kolize na úrovni pixelů (dva obrázky kolidují pouze tehdy, překrývají-li se neprůhlednými pixely), takže druhý parametr je zde jen do počtu z důvodu kompatibility se třídou Sprite.

Třída GameSprite obsahuje jeden obrázek a pozici jeho levého horního rohu, kterou lze měnit relativně (move(int dx, int dy)) nebo nastavit absolutně (setPosition(int x, int y)). Za vykreslení je zodpovědná metoda paint(Graphics g). Třída GameSprite může být viditelná či neviditelná. Je-li neviditelná, metoda paint(Graphics g) neudělá nic.

Ke stažení

Veškeré zde uvedené zdrojové kódy i obrázky ke hře a další materiál si můžete stáhnout a použít.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět