C týden
Tento týden až 80% sleva na e-learning týkající se jazyka C
50 % bodů zdarma na online výuku díky naší Slevové akci!

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

Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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

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.


 

Stáhnout

Staženo 42x (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
Článek pro vás napsal Jaroslav Polívka
Avatar
Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!
Autor se věnuje převážně jazykům JAVA a C++
Aktivity (4)

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!