Lekce 17 - Programujeme Android hru - Jednoduchá herní smyčka
Vítejte u další lekce. Minule jsme se věnovali energii kuřete, Programujeme Android hru - Energie kuřete, tedy hlavní postavě naší hry.
Na úvod dnešní lekce se vás pokusím motivovat prohlášením, že v dnešním díle píšeme kód před nahráním naší hry do mobilu/tabletu naposledy.
Pojďme provést slíbené rozvětvení kódu dle herního stavu pomocí konstrukce switch. Otevřeme třídu GameManager.java, kde k deklaracím atributů přidáme další:
public enum GameState {READY, RUNNING, GAMEOVER} private GameState gameState; private Food food; private OverlapsManager overlapsMng; private float delayer; private int score[];
Do konstruktoru přidáme jejich inicializace:
this.gameState = GameState.READY; this.food = objectManager.getFood(); this.overlapsMng = objectManager.getOverlapsManager(); this.delayer = 4; this.score = overlapsMng.getScore();
Stávající metodu update(...) změníme do následující podoby:
public void update(float delta) { switch (gameState) { case READY: overlapsMng.scoreRestart(); this.delayer = 4; break; // score vynuluj až po odchodu do READY case RUNNING: updateRunning(delta); break; case GAMEOVER: updateGameOver(delta); break; default: break; } }
Do třídy přidáme další metody, umístíme je třeba pod naší nově upravenou metodu update(...):
private void updateRunning(float delta) { if (delta > 0.1f) { delta = 0.1f; // delta cap for old machines } if(chicken.getEnergy() <= 0) { // kuře chcíplo gameState = GameState.GAMEOVER; chicken.restart(); food.restart(); if (score[0] > AssetManager.getHighScore()) { AssetManager.setHighScore(score[0]); } } objectManager.update(delta); } private void updateGameOver(float delta) { // zpoždění z GAMEOVER state na READY if (delayer - delta < 0) { delayer=-0.1f; } else { delayer-=delta; } } public GameState getGameState() { // kvůli render return gameState; } public void setGameState(GameState gameState) { this.gameState=gameState; } public float getDelayer() { return this.delayer; }
Stávající metoda:
public Chicken getChicken() { // kvůli render return chicken; }
se jeví již jako zbytečná, proto ji můžeme smazat. Přidáme importy a třídu uložíme. V metodě update(...) pomocí switch větvíme kód do tří částí podle enum konstanty GameState. Zbývá nám pouze zajistit postupné přepínání konstanty a realizovat promítání podle její aktuální hodnoty (READY, RUNNING nebo GAMEOVER).
Začneme přípravou promítačky. Otevřeme třídu Renderer.java a například pod privátní metodu drawTextScore() si přidáme metodu další:
private void drawTextHighScore() { shadowFont.draw(batcher, "HighScore: "+AssetManager.getHighScore(), screenBoundBegin.x+demandedScreen.x*0.45f+2,screenBoundBegin.y+5+2); yellowFont.draw(batcher, "HighScore: "+AssetManager.getHighScore(), screenBoundBegin.x+demandedScreen.x*0.45f,screenBoundBegin.y+5); }
Další dvě metody si přidáme například ihned pod stávající metodu render(...):
private void drawReady() { shadowFont.getData().setScale(1); yellowFont.getData().setScale(1); shadowFont.draw(batcher, "Wacky chicken", screenBoundBegin.x+240+2,screenBoundBegin.y+BEGINYCONSTANT+2 ); yellowFont.draw(batcher, "Wacky chicken", screenBoundBegin.x+240,screenBoundBegin.y+BEGINYCONSTANT ); shadowFont.draw(batcher, "Touch to play", screenBoundBegin.x+255+2,screenBoundBegin.y+BEGINYCONSTANT+55+2); yellowFont.draw(batcher, "Touch to play", screenBoundBegin.x+255,screenBoundBegin.y+BEGINYCONSTANT+55); shadowFont.getData().setScale(0.5f); yellowFont.getData().setScale(0.5f); } private void drawGameOver() { shadowFont.getData().setScale(1.5f); yellowFont.getData().setScale(1.5f); shadowFont.draw(batcher, "GameOver", screenBoundBegin.x+243+4,screenBoundBegin.y+BEGINYCONSTANT+4); yellowFont.draw(batcher, "GameOver", screenBoundBegin.x+243,screenBoundBegin.y+BEGINYCONSTANT); // zpozdeni ze state RUNNING na READY if (gameMng.getDelayer() > 0) { shadowFont.draw(batcher,""+(int)(1+gameMng.getDelayer()), screenBoundBegin.x+380+4,screenBoundBegin.y+BEGINYCONSTANT+80+4); yellowFont.draw(batcher,""+(int)(1+gameMng.getDelayer()), screenBoundBegin.x+380,screenBoundBegin.y+BEGINYCONSTANT+80); } else { shadowFont.draw(batcher,"Touch", screenBoundBegin.x+305+4,screenBoundBegin.y+BEGINYCONSTANT+80+4); yellowFont.draw(batcher,"Touch", screenBoundBegin.x+305,screenBoundBegin.y+BEGINYCONSTANT+80); } shadowFont.getData().setScale(0.5f); yellowFont.getData().setScale(0.5f); }
A do stávající metody render(...) implementujeme větvení pro promítání dle enum konstanty GameState. Mohli bychom to klidně opět provést pomocí switch, my si zde ale dáme klasické if/else if. Metoda render(...) bude mít výsledný tvar:
public void render(float delta) { Gdx.gl.glClearColor(0, 0, 0, 1); // black background reduce flashing Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batcher.begin(); batcher.draw(rBackground,screenBoundBegin.x, screenBoundBegin.y, demandedScreen.x, demandedScreen.y); // pozadi batcher.end(); if (gameMng.getGameState() == GameState.RUNNING) { batcher.begin(); drawTextScore(); drawTextSpeed(); drawTextFood(); drawFood(); drawChicken(delta); batcher.end(); drawEnergyBar(); } else if (gameMng.getGameState() == GameState.READY) { batcher.begin(); drawReady(); batcher.end(); } else if (gameMng.getGameState() == GameState.GAMEOVER) { batcher.begin(); drawTextScore(); drawTextHighScore(); drawGameOver(); batcher.end(); } else Gdx.app.log("renderer:", "Sem by se rizeni nemelo dostat."); }
Přidáme importy, třídu uložíme a můžeme opustit. Posledním úkolem je zajistit správné přepínání enum konstanty GameState, podle které máme nyní rozvětvené updatování a promítání naší hry. To bude velmi jednoduchá záležitost, kterou nám vyřeší tři if podmínky. Otevřeme si naší třídu InputManager.java a její funkci touchDown(...) přepíšeme na následující tvar:
@Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { screenX = scaleX(screenX); screenY = scaleY(screenY); // Gdx.app.log("Inputmng touchDown x:", "" + screenX); // Gdx.app.log("Inputmng touchDown y:", "" + screenY + "\n"); if (gameManager.getGameState() == GameState.RUNNING) { // reaguj na stisk jen když je stav RUNNING objectManager.receivePosition(screenX, screenY); } if (gameManager.getGameState() == GameState.READY) { gameManager.setGameState(GameState.RUNNING); } if ((gameManager.getGameState() == GameState.GAMEOVER) && (gameManager.getDelayer() <= 0)) { gameManager.setGameState(GameState.READY); } return false; }
Přidáme importy a vidíme, že nám zde svítí jeden warning s nevyužitým atributem gameScreen. Provedeme jeho odstranění z deklarace atributů, smažeme tedy řádek:
private GameScreen gameScreen;
V konstruktoru odstraníme také jeho inicializaci:
this.gameScreen = gameScreen;
Atribut odstraníme také z parametrů konstruktoru, výsledný tvar hlavičky konstruktoru tedy bude:
public InputManager(GameManager gameManager, float scaleRatioX, float scaleRatioY)
Tím způsobíme další warning s nevyužitým importem, tento drobný problém vyřešíme smazáním
řádku:
import com.wackychicken.screens.GameScreen;
nebo naší známou klávesovou zkratkou pro přidání importů, jistě jste
si již dávno všimli, že funguje i pro jejich odebrání, pokud jsou
nevyužité. Třídu uložíme a můžeme zavřít. Odebráním parametru
GameScreen gameScreen
z konstruktoru se vyvolá další chyba ve
třídě GameScreen.java ,
kterou rychle vyřešíme. Otevřeme třídu GameScreen.java a její
řádek:
InputManager inputManager = new InputManager(gameManager, this, w/orthoWidth, h/orthoHeight);
Zkrátíme na:
InputManager inputManager = new InputManager(gameManager, w/orthoWidth, h/orthoHeight);
Třídu uložíme a taktéž můžeme zavřít. Omlouvám se za komplikace, tento odkaz na gameScreen mi tam omylem zůstal, protože jsem ho v nějaké své mezi-verzi hry potřeboval pro implementaci dalších doplňků.
Aplikaci můžeme spustit a vyzkoušet, zda nám funguje herní smyčka a také ukládání high score. Po spuštění ještě hledáme, zda někde v Eclipse nesvítí nějaké warnings nebo problems, snad koukám dobře - žádné nevidím:

V krátkém videu níže vidíme, že ukládání, načítání a zobrazovaní high score funguje. Herní smyčka je rovněž v pořádku. Tím jsme s naší aplikací hotovi a příště provedeme její nahrání do mobilu.
Děkuji vám za přečtení, zdrojový kód je jako vždy přiložen ke stažení.
V příští lekci, Programujeme Android hru - Nahrání hry do zařízení, kurz zakončíme nahráním naší hry do mobilního telefonu nebo tabletu.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 47x (6.27 MB)
Aplikace je včetně zdrojových kódů v jazyce Java