Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

Bez problému - Programujeme Android hru

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 45x (6.27 MB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

Předchozí článek
Programujeme Android hru - Energie kuřete
Všechny články v sekci
Programujeme Android hru
Přeskočit článek
(nedoporučujeme)
Programujeme Android hru - Nahrání hry do zařízení
Článek pro vás napsal Jaroslav Polívka
Avatar
Uživatelské hodnocení:
1 hlasů
Autor se věnuje převážně jazykům JAVA a C++
Aktivity