Programování flash her – 4 – Herní smyčka

27.12.2012 by: Aimia

Trochu mě zdržela moje nová vánoční hra Goblins wanna star ale je po vánocích a tak přináším další díl seriálu. V tomto díle si ukážeme jak vytvořit herní smyčku. Herní smyčka je základem většiny her. Jde vlastně o neustálé střídaní vstupu uživatele, aktualizace herní logiky a zobrazení hry. Zjednodušeně to vypadá takto.

while(gameIsRunning)
{
	checkPlayerInput(); // jakou klavesu hrac stiskl, kde klikl apod.
	gameUpdate(time); // proveď posun nepřátel, animace apod.
	Draw(); // vykresli výsledek
}

Tuto herní smyčku používá většina her. Nemusíme ji použít v případě her které reagují jen na vstup uživatele(např. Pexeso, kde až po kliku na kartu se karta otočí a zase se čeká na vstup uživatele). Naproti tomu na akční hry, realtime strategie apod. ji použít musíme(v těchto hrách se něco děje i když hráč zrovna nereaguje).

//

GameLoop Příklad
Přemýšlel jsem jak co nejpřehledněji popsat kód, ale je toho tolik že by článek byl příliš rozsáhlý. Proto jsem připravil zdrojáky ke stažení. Jsou okomentované, tady popíšu jen nejdůležitější věci. Ke spuštění je potřeba si nainstalovat Flash Develop – viz Programování flash her – 3 – Vytvoření prvního projektu.

Příklad je velmi jednoduchý a ukazuje jak s herní smyčkou pracovat. Je zobrazen jednoduchý Shape(flash třída definující jednoduchý geometrický útvar) – červené kolečko. Sám se pohybuje směrem doprava. Pokud hráč drží tlačítko šipku vlevo, pohne se doleva. Pokud pohnete myší, přesune se kolečko na pozici myši. Pokud stisknete levé myšítko, přesune se kolečko na počáteční pozici. Takhle si můžete očichat, jak se ve flashi pracuje s detekováním stisku tlačítka klávesy, tlačítka myši, pohybu myší a jak se aktualizuje herní logika.

Definování flash výstupu

[SWF(width="640", height="480", backgroundColor="#ffffff", frameRate="25")]

Všimněte si tohoto kódu na začátku Main.as. Popsal jsem jej už v kódu, ale pro jistotu shrnu že tímto nastavením si přímo v kódu můžete měnit rozměry flashe, barvu pozadí a framerate. Možností nastavení je víc, dají se dohledat v dokumentaci Adobe, ale tohle vám myslím bude stačit.

Pozn.: U backgroundColor jen uvedu že není dobré se na něj spoléhat. Je dobré zobrazit vespod scény nějaký grafický objekt(Shape s nastavenou barvou, bitmapu). Stalo se mi totiž že na některých stránkách se hra zobrazila s transparetním pozadím, a tak místo modré barvy moře v Submarine Fighter hráč viděl skrz.

Pozn. 2: Rozměry flash videa 640×480 jsou poměrně dostatečné. Teoreticky si můžete zvolit rozměry jaké chcete, ale pokud chcete hru dále šířit i na jiné weby, je nejlepší použít rozměry max. 800x600px. Je to kvůli tomu že herní portály mají kolem her umístěné další prvky(reklamu, chat apod.) a větší hra by se tam zkrátka nevešla. Zbytečně byste tak přicházeli o možnost dostat hru co nejvíce lidem. Já používám většinou rozměry 640×480 – přijdou mi dostatečné, hra se vejde na všechny portály a je hratelná i na netbooku.

Framerate
Zmíním tu problém update a framerate. Framerate udává počet překreslení hry za sekundu(angl. zkratka fps). Čím je číslo větší, tím je lepší. Hodnota pod 20 může být pro oko už viditelná, hráč cítí/vidí že hra se škube. Naproti tomu frame rate nad 30 už je pro oko nerozeznatelný a je zbytečné jej nastavovat větší. A to z důvodu toho, že Flash, pokud nestihne frame zobrazit, automaticky framerate sníží.

Pozn.: Mě se odvědčilo nastavení framerate na hodnotu kolem 25.

Update(elapsedTime)
Hodně důležité je nespoléhat se na to, že hra poběží stále stejně rychle. Rychlé počítače udrží stabilní framerate, ale u pomalejších se může stát že framerate kolísá. Záleží samozřejmě i na tom jak dobře máte hru napsanou(jak je optimalizovaná), kolik se toho zobrazujete každý frame apod.
Proto se každý frame zjišťuje, kolik času uběhlo od posledního framu a podle toho se změní pozice nepřítele, hráče apod. Takhle zajistíte, že hra poběží stejně i na pomalejších počítačích.
Tím je myšleno, že hra na pomalejším stroji se sice bude sekat, ale bude vypadat stejně(raketa za sekundu urazí stejnou vzdálenost na rychlém i pomalém počítači). Je to vidět na příkladu.

private function update(elapsedTime:Number):void
{
shape.x += 30 * elapsedTime; // zmena pozice x. 30 je pocet pixelu o ktery se posune Shape za sekundu(takze rychlost 30pixelu za sekundu)
}

Představte si že by v kódu bylo tohle:

private function update(elapsedTime:Number):void
{
shape.x += (30/25); // 30/25=1.2
}

(30 dělím hodnotou 25 abych dostal stejné zvýšení pozice x pro každý frame v případě stálého fps=25 ). V tomto případě by při fps 25 hra fungovala správně, shape by se posunul o 30 pixelů Ale v případě že by fps najednou kleslo na 10, za sekundu by se shape posunul jen o 12 pixelů(1.2*10). Takže vidíte že bez elapsedTime by hra vypadala na různých počítačích jinak.

KeyUp/ keyDown události
Všimněte si jedné věci – při stisku a uvolnění klávesy neprovádím přímo žádný kód, jen nastavuji proměnnou keyLeftPressed. Veškerý kód týkající se vstupu hráče provádím v metodě processInput(elapsedTime). Lépe se s tím pracuje, a opět můžeme používat elapsedTime.

Tohle by bylo pro dnešek vše. Pokud vám něco není jasné, ptejte se v komentářích. V dalším díle se chystám vytvořit Scene objekt(jakýsi objekt obrazovky), aby vaše hra mohla mít několik různých obrazovek.

Filed under: flash,Jak programovat hry

Comments

6 komentářů to “Programování flash her – 4 – Herní smyčka”
  1. reptile napsal:

    Cauko. Zaujimave. Uz som aj zistil ako nacitat png a pohybovat s nim. Problem je ze ked ho chcem rotovat tak ho rotuje okolo jeho nuloveho bodu t.j suradnice v lavom hornom rohu. Docital som sa, ze sa to da vyriesit cez transform matrix, ale pri import.fl.motion … hodi error ze cannot be found. Je nejake riesenie ako rotovat s bitmapou v lubovolnom bode? Ja obrazok nacitavam cez rectangle, pricom vypln nie je farba, ale bitmapa a teda obrazok, ktory chcem zobrazit. Neviem ci to je nastastnejsie riesenie. Dik za odpoved.

    • Aimia napsal:

      Ahoj, buď bych to udělal tak, že bych bitmapu vložil do Sprite(sprite.addChild(bitmap)) a pak nastavil pivotX a pivotY. Pivot je právě ten bod, kolem kterého se otáčí. Není to ale nejrychlejší řešení, ale pokud je objektů jen málo, použil bych to, je to nejjednodušší.

      Druhý způsob je přes tu matici. Já to dělám nějak takhle:
      matrix.identity();
      if (scale != 1)
      {
      matrix.translate(-pivot.x, -pivot.y);
      matrix.scale(scale, scale);
      matrix.translate(pivot.x, pivot.y);
      }
      if (rotation != 0)
      {
      matrix.translate(-pivot.x, -pivot.y);
      matrix.rotate(rotation);
      matrix.translate(+pivot.x, +pivot.y);
      }
      matrix.translate((pos.x-pivot.x), (pos.y-pivot.y));
      spriteBatch.draw(bitmapData, matrix, colorTransform, blendMode, null, false);

      Trošku popíšu – identity() vynuluje matici, kvůli rychlosti jsem ji vytvořil dřív. Mrkni např. na rotaci – jde vidět že první musíš bitmapu přesunout o pivot(translate), pak rotovat, a pak posunout zpět(aby zůstala zachována původni pozice ve scéně). Pokud nepřesuneš o pivot(nastavíš jej např. na střed), dojde právě k té rotaci kolem počátku.
      Vzal jsem to rovnou ze svého kódu, já používám jen bitmapy, proto jsem si musel naprogramovat i scale a nakonec jsem musel i udělat translate na správnou pozici ve scéně.

      A bacha na matice, při násobení matic je rozdíl mezi A * B != B * A (A, B jsou dvě různé matice). Takže proto nemůžeš např. na začátku přesunout objekt pomocí matice někam a pak rotovat. Translate se většinou dělá na konci, protože mění jen některé hodnoty matice, u rotace a tuším scale dochází k násobení matic.

  2. reptile napsal:

    Ok dik. Nejako som to rozbehal.

    Inak je zaujimave, ze:

    shape = new Shape();
    shape.graphics.beginFill(0xDC2F03);
    shape.graphics.drawCircle(400, 15, 8);
    shape.graphics.endFill();
    addChild(shape);

    toto nakresli kruznicu na suradnici 400,15 o polomere 8. Clovek by vsak cakal, ze shape.x = 400 a shape.y = 15 a pritom nie! shape.x = 0 a shape.y = 0. Presnejse shape.x a shape.y = 0, ale tento pociatok je prave v suradniciach 400,15. Je to cudne, ja ze je pociatocna hodnota definovana v tych zatvrokach.

    Este je dost zaujimave, ze pokial chces tento kruh odstranit zo sceny tak removeChild(shape); vyhodi chybu, prej nemozes vymazat parent. Musis to riesit takto krkolomne:

    if (shape.parent) {
    try {
    shape.parent.removeChild(shape);
    }catch(err:Error){

    }}

    Pokial mas cas mozes napisat clanok ako animovat pomocou striedania obrazkov, trebarz png. Co som skusal animovany gif nefunguje, nakresli len prvy frame, alebo posledny.

    DIK.

    • Aimia napsal:

      K tomu Shape – funguje jako kontejner, takže příkazem drawCircle(400, 15, 8); nakreslíš kružnici na pozici 400,15(kontejner je neohraničený, můžeš kreslit kamkoli, i na mínusové souřadnice). Samozřejmě můžeš napsat drawCircle(0, 0, 8); a pak shape.x = 400 a shape.y = 15. Druhý způsob je podle mě lepší, kreslíš vše kolem počátku a když potřebuji centrovat tak napíši shape.pivotX = shape.width / 2;

      RemoveChild by mělo normálně fungovat, nezkoušíš ho volat z jiné třídy? Každopádně if (shape.parent) shape.parent.removeChild(shape); je vždy bezpečnější 🙂

      A díky za zájem o článek, bohužel jsem poslední dobou v časové tísni a nestíhám. Ale budu to mít na paměti. 🙂

  3. reptile napsal:

    Cauko. Este jedna vec. 25 fps je strasne malo. Odporucam ti nastavovat 60 fps. Ved si to skus a uvidis ten rozdiel. Dnesne pocitace s 2D grafikou nemaju absolutne ziadne problemy toto zvladnut a pre oko je to plynulejsie. Filmy sice maju 25 framov za sekundu, ale to je uplny iny system zobrazovania, takze mozog to vnima inak. Na pocitaci ten frame trva strasne kratko a je ostry narozdiel od filmu. Optimum je 60 tolko ma vacsina LCD monitorov frekvenciu.

    • Aimia napsal:

      Díky za informaci, asi máš pravdu, já to nastavuji na 25/30 fps z historických důvodů. Poslední dobou používám Starling, tam už nastavuji vždy 60fps.

Leave a Reply