15. díl - Programujeme Android hru - Animace, zvuky

Java Android Programujeme hru Programujeme Android hru - Animace, zvuky

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Vítejte v dalším díle našeho kurzu programování hry pro Android. Než budeme pokračovat, stručně si vysvětlíme princip činnosti našeho kódu z dílu minulého.

Třída AssetManager.java je jasná, stejně jako u obrázků jsme provedli načtení bitmapového písma (fontu) do paměti - nic nového.

Zajímavější je třída OverlapsManager­.java. Neustále dokola je volána její metoda update(...), která obsahuje na první pohled složitou podmínku:

if (Intersector.overlaps(food.getRectForOverlap(),chicken.getRectForOverlap()) && food.getWasClicked() && (!food.getWasCounted()) && chicken.isStanding())

Vypadá složitě, ale nic na ni není. Používáme zde metodu overlaps(...) vestavěné třídy Intersector, která vrací true, pokud se protínají dva obdélníky předané jako argumenty. V našem případě to jsou obdélníky rectForOverlap v instancích chicken a food. Nám to ale nestačí, to že se krmivo protíná s kuřetem, ještě neznamená přičtení skóre.

Musí být splněny další podmínky. Na krmivo musí být kliknuto (food.getWasClic­ked()) a přičtení skóre musí proběhnout až po zastavení kuřete (chicken.isStan­ding()). Nepravda !food.getWasCou­nted() zajistí, aby skóre bylo přičteno pouze jednou, protože jak již bylo řečeno, metoda update(...) je volána neustále. V okamžiku, kdy jsou tyto podmínky splněny, dojde ke zvýšení skóre o jedničku a nastavení proměnné lifeTime v instanci food na 0, tím fakticky začne losování a následné promítnutí krmiva na nových souřadnicích. Neustálé opakované volání metod update() v našem projektu bychom v současnosti mohli znázornit následujícím diagramem:

Volání metod update

Domnívám se, že metoda clickedPositi­on(...) je výstižně okomentovaná. Je zavolána instancí inputManager vždy při kliknutí uživatele na obrazovku a jako argumenty přijímá souřadnice tohoto kliku. Stará se o nastavení proměnné wasClicked v instanci krmiva a o zvýšení/reset rychlosti v instanci kuřete.

To je k objasnění minulé lekce vše. Již dobře víme, že v souladu se zásadou OOP zapouzdření "musíme" proměnné třídy deklarovat jako private a vytvořit k nim přístupové metody a také, že po definici třídy musíme vytvořit její instanci (nyní opomeňme statickou třídu). Přidané metody do třídy Render.java není nutné objasňovat, z jejich názvu je jasné, co promítají.

Zvuky a animace

Dnes slepici přidáme zvuky a animaci. Vždy, když hráč klikne na krmivo, slepice mu pípnutím "odpoví", že kliknul na správné místo, kde se potrava nachází. Dále přidáme animaci se zvukem, která se přehraje vždy, když jsou splněny podmínky pro zvýšení skóre, tato animace bude tedy znázorňovat, že slepice právě žere.

Začneme nahráním zdrojů. Otevřeme třídu AssetManager.java a pod deklarace stávajících proměnných přidáme:

public static Texture eatLeft1,eatLeft2,eatLeft3,eatLeft4;              //na animaci eating
public static Texture eatRight1,eatRight2,eatRight3,eatRight4;          //na animaci eating
public static TextureRegion rEatLeft1,rEatLeft2,rEatLeft3,rEatLeft4;    //na animaci eating
public static TextureRegion rEatRight1,rEatRight2,rEatRight3,rEatRight4;//na animaci eating
public static Animation eatLeftAnime;                                   //na animaci eating
public static Animation eatRightAnime;                                  //na animaci eating

public static Music beepSound,eatSound;

Nyní všechny tyto proměnné načteme, na konec metody load() přidáme následující kód:

/* zacatek nahrani animaci eating */
eatLeft1=new Texture(Gdx.files.internal("eatleft1.png"));
eatLeft1.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatLeft2=new Texture(Gdx.files.internal("eatleft2.png"));
eatLeft2.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatLeft3=new Texture(Gdx.files.internal("eatleft3.png"));
eatLeft3.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatLeft4=new Texture(Gdx.files.internal("eatleft4.png"));
eatLeft4.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);

eatRight1=new Texture(Gdx.files.internal("eatright1.png"));
eatRight1.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatRight2=new Texture(Gdx.files.internal("eatright2.png"));
eatRight2.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatRight3=new Texture(Gdx.files.internal("eatright3.png"));
eatRight3.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
eatRight4=new Texture(Gdx.files.internal("eatright4.png"));
eatRight4.setFilter(TextureFilter.Nearest, TextureFilter.Nearest);

rEatLeft1=new TextureRegion(eatLeft1,0,0,115,90);
rEatLeft1.flip(false, true);
rEatLeft2=new TextureRegion(eatLeft2,0,0,115,90);
rEatLeft2.flip(false, true);
rEatLeft3=new TextureRegion(eatLeft3,0,0,115,90);
rEatLeft3.flip(false, true);
rEatLeft4=new TextureRegion(eatLeft4,0,0,115,90);
rEatLeft4.flip(false, true);
TextureRegion[]rEatLeft1234={rEatLeft1,rEatLeft2,rEatLeft3,rEatLeft4};
eatLeftAnime=new Animation(0.2f,rEatLeft1234);
eatLeftAnime.setPlayMode(Animation.PlayMode.LOOP);

rEatRight1=new TextureRegion(eatRight1,0,0,115,90);
rEatRight1.flip(false, true);
rEatRight2=new TextureRegion(eatRight2,0,0,115,90);
rEatRight2.flip(false, true);
rEatRight3=new TextureRegion(eatRight3,0,0,115,90);
rEatRight3.flip(false, true);
rEatRight4=new TextureRegion(eatRight4,0,0,115,90);
rEatRight4.flip(false, true);
TextureRegion[]rEatRight1234={rEatRight1,rEatRight2,rEatRight3,rEatRight4};
eatRightAnime=new Animation(0.2f,rEatRight1234);
eatRightAnime.setPlayMode(Animation.PlayMode.LOOP);
/* konec nahrani animaci eating */

beepSound = Gdx.audio.newMusic(Gdx.files.internal("pip12.mp3"));
eatSound = Gdx.audio.newMusic(Gdx.files.internal("woodpecker.mp3"));

Uklidíme po sobě, na konec metody dispose() přidáme:

eatLeft1.dispose();
eatLeft2.dispose();
eatLeft3.dispose();
eatLeft4.dispose();
eatRight1.dispose();
eatRight2.dispose();
eatRight3.dispose();
eatRight4.dispose();

beepSound.dispose();
eatSound.dispose();

Přidáme importy, třídu uložíme. Samozřejmě nezapomeneme tyto soubory naimportovat do složky assets.

Zdroje animací i zvuků máme, dále potřebujeme změnit (přepnout) stavové proměnné kuřete na EATLEFT nebo EATRIGHT vždy, když jsou splněny podmínky pro zvýšení skóre - když "slepice žere" s tím, že tímto přepnutím v renderu vyvoláme promítání těchto zdrojů animací. Otevřeme si třídu OverlapsManager­.java a její metodu update(...) změníme do následujícího tvaru:

public void update(float delta) {       // trida hlida, zda se instance food a chicken protinaji!
        if (Intersector.overlaps(food.getRectForOverlap(),chicken.getRectForOverlap()) &&
        food.getWasClicked() && (!food.getWasCounted()) && chicken.isStanding()) {
                score[0]++;
                food.setWasCounted(true);

                if (chicken.getStandingState() == StandingState.STANDLEFT || chicken.getStandingState() == StandingState.STANDRIGHT) {
                        if (chicken.getStandingState() == StandingState.STANDLEFT)
                                chicken.setStandingState(StandingState.EATLEFT);
                        else
                                chicken.setStandingState(StandingState.EATRIGHT);
                }
        }
}

V této třídě ještě upravíme metodu clickedPositi­on(...) a to tak, že do její 1. if podmínky přidáme příkaz k přehrání zvuku pípnutí kuřete, takže výsledná podoba této podmínky bude:

if(food.getRectForOverlap().contains(x, y)) {
        food.setWasClicked(true);
        chicken.incrWholeSpeed(10); // vzdy, kdyz tapneme na jidlo, zvysit rychlost
        if (!AssetManager.beepSound.isPlaying()) {
                AssetManager.beepSound.play();
        }
}

Předposlední záležitostí, kterou je nutné pro animace provést, je rozvětvení metody update(...) ve třídě ObjectManager.java tak, že když probíhá animace, bude se deltou odečítat její "doba přehrávání" a když animace neprobíhá, budou se updatovat ostatní objekty. Otevřeme tedy třídu ObjectManager.java a její metodu update(...) rozšiřme do následující podoby:

public void update(float delta) {
        // kdyz probiha animace, tak updatuj prave jen animaci
        if (chicken.getStandingState() == StandingState.EATLEFT || chicken.getStandingState() ==        StandingState.EATRIGHT) {

                if (!AssetManager.eatSound.isPlaying()) {
                        AssetManager.eatSound.play();
                }
                if (eatAnimeLifetime <= 0) {    // animace skoncila?
                        if(chicken.getStandingState() == StandingState.EATLEFT) {
                                chicken.setStandingState(StandingState.STANDLEFT);
                                food.setLifeTime(0);
                        } else {
                                chicken.setStandingState(StandingState.STANDRIGHT);
                                food.setLifeTime(0);
                        }
                }
                eatAnimeLifetime-=delta;
        } else  {
                chicken.update(delta);
                food.update(delta);
                overlapsMng.update(delta);
                eatAnimeLifetime=2;
        }
}

Přidáme importy, IDE ohlašuje chybu neznámé proměnné eatAnimeLifetime - napravíme. Pod deklarace stávajících proměnných třídy doplníme další:

float eatAnimeLifetime;

a na konci konstruktoru provedeme její inicializaci:

this.eatAnimeLifetime = 2;

Do metody receivePositi­on(...) musíme přidat ještě jednu if podmínku, její výsledná podoba bude:

public void receivePosition(int x,int y) { //souradnice z inputu //pozor, volano z inputu jen, kdyz je stav hry running

        if (!(chicken.getStandingState()==StandingState.EATLEFT || chicken.getStandingState()==StandingState.EATRIGHT) )
        {       // kdyz slepice zere, nereagovat na uzivatelsky vstup, kdyz zere tak nepravda
                overlapsMng.clickedPosition(x,y);
                turnMng.turnChicken(x, y);
                turnMng.setChickenDistance(x, y);
                turnMng.setChickenSpeed();

                chicken.setXPositionAchieved(false);
                chicken.setYPositionAchieved(false);
        }
}

Nyní již OK. Třídu uložíme.

Poslední nutnou věcí je promítnutí animace. Otevřeme třídu Renderer.java, kde řádek s deklaracemi animací rozšíříme do následující podoby:

private Animation standLeftAnime,standRightAnime,eatLeftAnime,eatRightAnime;

Provedeme inicializaci našich dalších animací, nejlépe poblíž stávajících inicializací proměnných standLeftAnime a standRightAnime v metodě initAssetsObjec­ts():

this.eatLeftAnime=AssetManager.eatLeftAnime;
this.eatRightAnime=AssetManager.eatRightAnime;

Na konci metody drawChicken(...) stávající else větev:

else {
        runTime+=delta; // kvuli animaci
        if (chicken.getStandingState() == StandingState.STANDLEFT)
                batcher.draw(standLeftAnime.getKeyFrame(runTime), chicken.getPositionX(), chicken.getPositionY(), chicken.getWidth(), chicken.getHeight());
        else if (chicken.getStandingState() == StandingState.STANDRIGHT)
                batcher.draw(standRightAnime.getKeyFrame(runTime), chicken.getPositionX(), chicken.getPositionY(), chicken.getWidth(), chicken.getHeight());
        else
                Gdx.app.log("renderer:", "Sem by se rizeni nemelo dostat.");
}

rozšíříme na:

else {
        runTime+=delta; // kvuli animaci
        if (chicken.getStandingState() == StandingState.STANDLEFT)
                batcher.draw(standLeftAnime.getKeyFrame(runTime),chicken.getPositionX(),chicken.getPositionY(),chicken.getWidth(),chicken.getHeight());

        else if(chicken.getStandingState() == StandingState.STANDRIGHT)
                batcher.draw(standRightAnime.getKeyFrame(runTime),chicken.getPositionX(),chicken.getPositionY(),chicken.getWidth(),chicken.getHeight());

        else if(chicken.getStandingState() == StandingState.EATLEFT)
                batcher.draw(eatLeftAnime.getKeyFrame(runTime),chicken.getPositionX(),chicken.getPositionY(),chicken.getWidth(),chicken.getHeight());

        else if(chicken.getStandingState() == StandingState.EATRIGHT)
                batcher.draw(eatRightAnime.getKeyFrame(runTime),chicken.getPositionX(),chicken.getPositionY(),chicken.getWidth(),chicken.getHeight());

        else
                Gdx.app.log("renderer:", "Sem by se rizeni nemelo dostat.");
}

Třídu uložíme. Tím máme pro dnešek hotovo. Pokud si nejsme čímkoli jisti, čerpáme ze zdrojového kódu, který je jako vždy včetně assets níže přiložen ke stažení. Aplikaci spustíme, animace eatLeftAnime i eatRightAnime fungují a také oba zvuky jsou v pořádku přehrány.


 

Stáhnout

Staženo 10x (6.39 MB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

 

Č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++
Miniatura
Všechny články v sekci
Programujeme Android hru
Aktivity (2)

 

 

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