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í.

17. diel - Programujeme Android hru - Jednoduchá herné slučka

Vitajte u ďalšej lekcie. Minule sme sa venovali energiu kuraťa, teda hlavné postavy našej hry. Na úvod dnešnej lekcie sa vás pokúsim motivovať vyhlásením, že v dnešnom diele píšeme kód pred nahraním našej hry do mobilu / tabletu naposledy.

Poďme vykonať sľúbené rozvetvenie kódu podľa herného stavu pomocou konštrukcie switch. Otvoríme triedu GameManager.java, kde k deklaráciám atribútov pridáme ďalšie:

public enum GameState {READY, RUNNING, GAMEOVER}
private GameState gameState;
private Food food;
private OverlapsManager overlapsMng;
private float delayer;
private int score[];

Do konstruktoru pridáme ich inicializácia:

this.gameState = GameState.READY;
this.food = objectManager.getFood();
this.overlapsMng = objectManager.getOverlapsManager();
this.delayer = 4;
this.score = overlapsMng.getScore();

Existujúce metódu update (...) zmeníme do nasledujúcej 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 triedy pridáme ďalšie metódy, umiestnime treba pod našou novo upravenú metódu 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;
}

Existujúce metóda:

public Chicken getChicken() { // kvůli render
    return chicken;
}

sa javí už ako zbytočná, preto ju môžeme zmazať. Pridáme importy a triedu uložíme. V metóde update (...) pomocou switch vetvám kód do troch častí podľa enum konštanty GameState. Zostáva nám len zabezpečiť postupné prepínanie konštanty a realizovať premietanie podľa jej aktuálnej hodnoty (READY, RUNNING alebo GameOver).

Začneme prípravou premietačky. Otvoríme triedu Renderer.java a napríklad pod privátne metódu drawTextScore () si pridáme metódu ďalšie:

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);
}

Ďalšie dve metódy si pridáme napríklad ihneď pod existujúce metódu 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 existujúce metódy render (...) implementujeme vetvenia pre premietanie podľa enum konštanty GameState. Mohli by sme to pokojne opäť vykonať pomocou switch, my si tu ale dáme klasické if / else if. Metóda render (...) bude mať 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.");
}

Pridáme importy, triedu uložíme a môžeme opustiť. Posledným úlohou je zabezpečiť správne prepínanie enum konštanty GameState, podľa ktorej máme teraz rozvetvené updatované a premietanie našej hry. To bude veľmi jednoduchá záležitosť, ktorú nám vyrieši tri if podmienky. Otvoríme si našej triedu InputManager.java a jej funkciu Touchdown (...) prepíšeme na nasledujúce 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;
}

Pridáme importy a vidíme, že nám tu svieti jeden warning s nevyužitým atribútom gameScreen. Prevedieme jeho odstránenie z deklarácie atribútov, zmažeme teda riadok:

private GameScreen gameScreen;

V konstruktoru odstránime tiež jeho inicializácii:

this.gameScreen = gameScreen;

Atribút odstránime aj z parametrov konstruktoru, výsledný tvar hlavičky konstruktoru teda bude:

public InputManager(GameManager gameManager, float scaleRatioX, float scaleRatioY)

Tým spôsobíme ďalšie warning :) s nevyužitým importom, tento drobný problém vyriešime zmazaním riadku:

import com.wackychicken.screens.GameScreen;

alebo našej známu klávesovou skratkou pre pridanie importov, iste ste si už dávno všimli, že funguje aj pre ich odobratie, ak sú nevyužité. Triedu uložíme a môžeme zatvoriť. Odstránením parametra GameScreen gameScreen z konstruktoru sa vyvolá ďalšia chyba v triede GameScreen.java :) , Ktorú rýchlo vyriešime. Otvoríme triedu GameScreen.java a jej riadok:

InputManager inputManager = new InputManager(gameManager, this, w/orthoWidth, h/orthoHeight);

Skrátime na:

InputManager inputManager = new InputManager(gameManager, w/orthoWidth, h/orthoHeight);

Triedu uložíme a taktiež môžeme zatvoriť. Ospravedlňujem sa za komplikácie, tento odkaz na gameScreen mi tam omylom ostal, pretože som ho v nejakej svojej medzi-verziu hry potreboval pre implementáciu ďalších doplnkov.

Aplikáciu môžeme spustiť a vyskúšať, či nám funguje herné slučka a tiež ukladanie high score. Po spustení ešte hľadáme, či niekde v Eclipse nesvieti nejakej warnings alebo problems, snáď koukám dobre - žiadne nevidím:

bez problému - Programujeme Android hru

V krátkom videu nižšie vidíme, že ukladanie, načítanie a zobrazovanie high score funguje. Herný slučka je tiež v poriadku. Tým sme s našou aplikáciou hotoví a nabudúce prevedieme jej nahranie do mobilu.

Ďakujem vám za prečítanie, zdrojový kód je ako vždy priložený k stiahnutiu.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 45x (6.27 MB)
Aplikácia je vrátane zdrojových kódov v jazyku Java

 

Predchádzajúci článok
Programujeme Android hru - Energia kurčaťa
Všetky články v sekcii
Programujeme Android hru
Preskočiť článok
(neodporúčame)
Programujeme Android hru - Nahranie hry do zariadenia
Článok pre vás napísal Jaroslav Polívka
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se věnuje převážně jazykům JAVA a C++
Aktivity