Objavljeno: 26.6.2012 | Avtor: Ciril Bohak | Monitor Junij 2012 | Teme: android

Sam svoj programer

Programiranje v predmetno usmerjenem jeziku, kot je java, nam omogoča, da aplikacijo, ki jo želimo razviti, opišemo v obliki strukture (hierarhije) razredov. Razredi v takšni strukturi predstavljajo osnovne sestavne dele naše aplikacije in s svojimi spremenljivkami in metodami določajo delovanje celotne aplikacije. Tako lahko še tako zahtevno aplikacijo razčlenimo na enostavne sestavne dele in s tem razbijemo kompleksno implementacijo na več enostavnejših delov. S tem ne le poenostavimo sam razvoj aplikacije, temveč tudi zmanjšamo možnosti vnosa napak v programsko kodo, saj se pri implementaciji posameznega dela aplikacije lažje osredotočamo na samo funkcionalnost kode. Z razredno strukturo prav tako poenostavimo razumevanje kode, povečamo možnost vnovične rabe posameznih delov kode, pa tudi povečamo pregled nad celotno implementacijo in lažje določimo, katere funkcionalnosti moramo v našo aplikacijo še vpeljati.

Razredna struktura igre

Igro smo razdelili na več smiselnih delov, ki so predstavljeni s posameznimi razredi. Vstopni razred v našo aplikacijo je, kot pri večini drugih androidnih aplikacij, tipa Aktivnost. To v našem primeru predstavlja razred And_GameActivity, ki poskrbi za inicializacijo naše igre. Razred Game predstavlja samo igro, hrani informacije, v katerem stanju je trenutno igra, in vse druge komponente igre. GameView je razred, ki smo ga v podobni obliki spoznali že v prejšnjem delu serije in skrbi za prikaz vsebine igre in interakcijo z njo. Abstraktni razred GameObject predstavlja šablono za vse objekte, ki se v igri izrisujejo in so predstavljeni z razredi GUI, SpaceShip, SpaceInvader, Bullet in GameLevel. Prav tako razred GameObject implementira vmesnik Renderable, s čimer poskrbimo za to, da so vsi primerki tega razreda izrisljivi (vsebujejo metodo render). V nadaljevanju so predstavljene podrobnosti posameznih razredov, njihove lastnosti in funkcionalnosti. Slika 1 prikazuje razredno strukturo naše igre. Označena so dedovanja in implementacije med razredi, s čimer si laže predstavljamo organizacijo same igre.

SLIKA 1: Razredni diagram, ki predstavlja celotno razredno hierarhijo naše igre.

And_GameActivity

Aktivnost predstavlja vstopni razred za androidno aplikacijo za vse aplikacije z uporabniškim vmesnikom. Tako tudi v našem primeru vstopni razred razširja razred Activity. V razredu nastavimo razporejevalnik uporabniškega vmesnika, za kar bomo v našem primeru uporabili zgolj instanco razreda GameView, definiranega podobno kot v prejšnjem delu serije člankov in podrobneje opisanega v nadaljevanju. V glavnem razredu prav tako nastavimo, da naša aplikacija prepreči ugašanje in zaklepanje zaslona po določenem času. To storimo tako, da aplikacija zahteva preprečitev ugašanja in zaklepa zaslona, kar prikazuje spodnja koda:

getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);

V aktivnosti ustvarimo še nov primerek razreda Game, ki prevzame nadaljnji nadzor nad igro. Igri ob stvaritvi podamo tudi GameView, na katerega se bo celotna igra izrisovala.

Game

Razred predstavlja osrčje igre in skrbi, da se celotna igra izvaja, kot je treba. V njem hranimo stanje, v katerem je trenutno naša igra (glavni menu, igranje, konec igre ...), kakšno je stanje točk v naši igri, koliko življenj še ima igralec in podobne lastnosti. Poleg teh informacij pa razred hrani še vse predmete, ki se med igro izrisujejo (vesoljsko ladjico, napadalce, izstrelke, vmesnik ...), časovnike, ki skrbijo za izris objektov ob pravem času, poslušalce za detekcijo dotika in metode, potrebne za izvajanje igre. Javne metode so:

  • initializeGame - nastavi začetne lastnosti igre, kot so nastavitev stanja igre v začetno stanje (MAIN_MENU_STATE), velikost igre in vmesnika, začetno stanje števila točk in življenj.
  • startGame - preklopi stanje igre v stanje igranja (GAMEPLAY_STATE), inicializira vse objekte, ki se med igro izrisujejo (več tipov napadalcev v več vrstah, vesoljsko ladjico in izstrelke), in nastavi časovnike za njihov izris (napadalci so animirani in se premikajo na 500 ms, izstrelki pa se premikajo vsakih 40 ms. Zgled časovnika za izris napadalcev je prikazan spodaj:
    Timer invaderTimer = new Timer("Invaders");
    invaderTimer.scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
    gameLoop();
    }, 0, 500);
  • render - metoda kot vhodni parameter prejme "platno" in skrbi za izris vseh vidnih objektov na "platno" med izvajanjem igre in se po vsakem izrisu spet kliče. S tem poskrbimo za ažurno posodabljanje izrisa trenutnega stanja igre. Metoda glede na globalno stanje igre izrisuje različne stvari; v stanju MAIN_MENU_STATE tako izrisuje grafiko glavnega menuja, v stanju GAMEPLAY_STATE izrisuje dogajanje med samim izvajanjem igre (premike in animacije objektov), v stanju GAME_OVER_STATE pa izrisuje vmesnik, ki igralcu prikazuje končno stanje igre. Izsek metode je prikazan spodaj:
    public void render(Canvas c) {
    switch (currentGameState) {
    case MAIN_MENU_STATE:
    ...
    break;
    case GAMEPLAY_STATE:
    ...
    break;
    case GAME_OVER_STATE:
    ...
    break;
    }
    gameView.postInvalidate();
    }
  • getCurrentGameState - metoda vrača trenutno stanje igre in omogoča drugim objektom, da se glede na vrnjeno stanje ustrezno odzovejo.
  • setScale - metoda sprejme velikostni faktor, v katerem želimo igro prikazovati. Velikostni faktor je odvisen od velikosti zaslona naprave, na kateri igro igramo. S tem omogočimo resolucijsko neodvisno igranje igre na vseh napravah, kjer lahko igro poganjamo.
  • zasebne metode pa so:

  • gameLoop - metoda se izvaja na določen časovni interval in s tem predstavlja glavno zanko, ki izrisuje spremembe stanja igre na zaslon. V našem primeru skrbi za animacijo napadalcev. Metoda prav tako skrbi, da se napadalci v predpisanih intervalih pomikajo po zaslonu, pri čemer je nov položaj napadalcev odvisen od zadnjega položaja. Napadalci se tako "odbijajo" od levega in desnega roba zaslona, pri vsakem "odboju" pa se pomaknejo tudi nekoliko navzdol in s tem pridejo bliže vesoljski ladjici. Ko napadalci dosežejo ravnino, v kateri je vesoljska ladjica, se igra konča. To dosežemo tako, da preklopimo stanje igre v GAME_OVER_STATE.
  • fireShipBullet - metoda poskrbi za izstrelitev izstrelka vesoljske ladjice. V praksi to pomeni, da na trenutnem položaju vesoljske ladjice ustvarimo nov primerek razreda Bullet in ga začnemo premikati. Ker pravila igre določajo, da je lahko hkrati na zaslonu zgolj en sam izstrelek vesoljske ladjice, je treba preveriti, ali je v določenem trenutku na zaslonu že kak izstrelek in ali je veljaven. Neveljaven postane izstrelek takrat, ko zadane katerega izmed napadalcev ali se pomakne prek zgornjega roba zaslona.
  • moveShipBullet - metoda je namenjena premiku izstrelka vesoljske ladjice in se izvede vsakih 40 ms. S tem poskrbimo, da se izstrelek gladko premika po zaslonu. Metoda tudi poskrbi, da se ob vsakem premiku izstrelka kliče metoda za preverjanje trkov. Prav tako metoda preveri, ali je neki izstrelek že preletel zgornji rob zaslona, in ga v tem primeru označi za neveljavnega in s tem igralcu omogoči izstrelitev novega izstrelka.
  • invaderCollisionDetection - metoda je namenjena detekciji trkov med napadalci in izstrelki vesoljske ladjice. Ta metoda, ki se kliče ob vsakem premiku izstrelka vesoljske ladjice, preveri za vsakega izmed napadalcev, ali je v trku z izstrelkom s pomočjo metode AABB. Če trka ni, se igra izvaja naprej, kadar je prišlo do trka, pa metoda poskrbi, da se ustrezen napadalec odstrani in izstrelek razveljavi. Tako se pri nadaljnjih izrisih zadeti napadalec in izstrelek ne bosta izrisovala.
  • AABB - metoda je namenjena ugotavljanju prekrivanja dveh pravokotnih objektov. Okrajšava pomeni "Axis Aligned Bounding Box" oz. osno poravnane obsegajoče škatle. V našem primeru metodo uporabimo za ugotavljanje trkov med izstrelki in napadalci. Ko se sliki napadalca in izstrelka vsaj delno prekrivata, smo zaznali trk. Seveda to ni vedno najboljša rešitev, saj se lahko zgodi, da meje slike ne predstavljajo tudi mej objekta, ki ga slika predstavlja. Metoda pa predstavlja poenostavljen pristop k detekciji trkov, saj bi bilo podrobnejše preverjanje veliko kompleksnejše za implementacijo, pa tudi računsko bolj zahtevno. Zgled detekcije trka ob pomoči opisanega pristopa je prikazan na sliki 2.
  • SLIKA 2: Prikaz detekcije trka med napadalcem in izstrelkom s pomočjo metode AABB

    Slovarček

    abstraktni razred - razred, za katerega velja, da ne moremo ustvariti njegovih primerkov. Koncept se uporablja pri definiranju razredne hierarhije.

    inicializacija - postopek za pripravo programa ali računalniškega sistema za delovanje.

    vmesnik - angl. interface, programerski koncept, ki od razredov, ki ga implementirajo, zahteva, da vsebujejo redefinicijo metod, definiranih v vmesniku.

    GameObject

    Razred GameObject razširja razred View, namenjen prikazu androidne aplikacije. Podrobneje smo nalogo takšnega razreda predstavili že v prejšnjem delu serije člankov. V trenutni implementaciji smo razred poenostavili in uporabljamo predvsem njegovo sposobnost izrisa na zaslon. Tako se znotraj metode onDraw ????

    Renderable

    Renderable predstavlja vmesnik za objekte, ki so izrisljivi. Razredi, ki implementirajo ta vmesnik, morajo tako nujno redefinirati njegove metode in s tem izpolniti določene funkcionalnosti. V našem primeru to pomeni, da bodo vsi razredi, ki implementirajo ta vmesnik, vsebovali metodo render, ki kot parameter prejme objekt Canvas (platno) in skrbi za izris objekta na zaslon. Razred, ki neposredno razširja vmesnik, je GameObject. Razredi, ki vmesnik razširjajo posredno, pa so GUI, SpaceShip, SpaceInvader, Bullet in GameLevel. Definicija vmesnika je nadvse preprosta in je prikazana spodaj:

    public interface Renderable {
    public abstract void render(Canvas c);
    }

    GameObject

    Abstraktni razred, ki predstavlja osnovo za dedovanje vseh izrisljivih elementov igre in vsebuje spremenljivke, ki jih uporabljajo vsi razširjeni razredi. Te spremenljivke so:

  • mPaint - spremenljivka tipa Paint, namenjena izrisu bitnih slik na platno;
  • viri - spremenljivka tipa Resources, ki predstavlja referenco na vire, ki jih androidni aplikaciji dodamo v okolju Eclipse v mapo resources. Referenco na te vire potrebujemo, ko želimo v razredni hierarhiji dostop do virov, kot so bitne slike, s katerimi grafično predstavimo izrisljive elemente;
  • scale - spremenljivka tipa float, ki predstavlja velikostni razred izrisa posamezne izrisljive komponente. Ob pomoči te spremenljivke lahko poskrbimo, da se posamezni izrisljivi objekti izrisujejo v različnih velikostnih razredih.
  • GUI

    Razred je namenjen izrisu celotnega grafičnega vmesnika igre. Grafični vmesnik se razlikuje za posamezna stanja, v katerih je igra v določenem času, kot so glavni menu, igranje, konec igre ... Pri glavnem menuju je treba izrisati ime igre in gumbe vmesnika (začetek igre, izhod) in vse druge grafične elemente (naslov, napadalce ...), kar je prikazano na sliki 3.

    SLIKA 3: Slika glavnega menuja igre, ki se prikaže ob zagonu

    Poleg glavnega menuja razred GUI poskrbi tudi za izris vmesnika med samo igro. V času igranja nam igra prek vmesnika sporoča, koliko točk smo zbrali in koliko življenj imamo še na voljo. Na tem mestu naj kot zgled omenimo, kako smo izvedli izpis številk. Posamezna cifra je predstavljena s samostojno sliko, kar skupno nanese 10 slik (za cifre 0, 1, ... , 9). Izpis samega števila točk pa je izveden kot prikaz ustreznih slik na ustreznih mestih na zaslonu. Izsek kode, ki skrbi za izris števil, je prikazan spodaj:

    for (int i=0; i<score.length(); i++) {
    int x = 10 + scoreImage.getWidth()/2 + i * no0.getWidth()/2;
    int y = 20;
    int no = Integer.parseInt(score.substring(i, i+1));
    switch (no) {
    case 0:
    c.drawBitmap(no0, null,
    new RectF(x, y, no0.getWidth()/2+x, no0.getHeight()/2+y), mPaint);
    break;
    ...
    case 9:
    c.drawBitmap(no9, null,
    new RectF(x, y, no0.getWidth()/2+x, no0.getHeight()/2+y), mPaint);
    break;
    };

    Podobno bi lahko izvedli tudi izris besedila, a se za to možnost zaradi majhne količine slednjega nismo odločili. Besedilo smo oblikovali in shranili kot slike napisov, ki se prikazujejo v igri. Za aplikacije oz. igre z več besedila bi bilo to vsekakor smiselno. Poleg števila zbranih točk in življenj med igro izrisujemo tudi puščice, ki nakazujejo predele zaslona, kjer dotik pomeni pomik ladjice v smer, ki jo nakazuje puščica.

    Drugačen vmesnik pa se prikaže tudi, ko igro končamo. Takrat na zaslon izpišemo "GAME OVER" in s tem uporabnika obvestimo, da je igre konec, in ga vprašamo, ali želi igro spet začeti ali se vrniti na glavni menu igre.

    Vmesnik tako vsebuje metode,s katerimi lahko osvežujemo prikazano vsebino. Metode razreda GUI so:

  • render - metoda, ki sprejme kot argument objekt Canvas in določa izris celotnega grafičnega vmesnika na zaslon v odvisnosti od stanja, v katerem je trenutno igra.
  • setSize - metoda, s katero določimo velikost vmesnika in se kliče ob zagonu igre, ko izvemo, kakšna bo velikost izrisane aplikacije, določena z velikostjo komponente GameView.
  • setScore - metoda, ki sprejme kot argument število tipa int in nastavi število točk, ki naj jih vmesnik prikazuje.
  • getLeftX, getRightX - metodi, ki vračata meji izrisanih puščic, kar uporabimo pri odločitvi, kaj storiti ob detekciji dotika. Če je dotik znotraj mej puščic, vesoljsko ladjico ustrezno premaknemo, v nasprotnem primeru pa poskušamo izstreliti izstrelek.
  • SpaceShip

    Razred predstavlja vesoljsko ladjico, ki jo upravlja igralec. Ladjica je predstavljena z eno samo bitno sliko. Razred vsebuje spremenljivke za hranjenje trenutnega položaja ladjice in metode, s katerimi lahko izvemo, kje je ladjica, in jo tudi ustrezno premikamo. Največkrat uporabljene metode v igri so:

  • moveRight, moveLeft - metodi poskrbita, da se ladjica pomakne za določeno število zaslonskih pik v levo ali desno stran. Metodi se kličeta ob detekciji dotika na levo in desno puščico.
  • getX, getY - metodi vrneta navpični in vodoravni položaj vesoljske ladjice, ki ga med igranjem igre največkrat uporabimo pri izstrelitvi, saj je treba nov izstrelek ustvariti na trenutnem položaju ladjice.
  • Izris ladjice je zelo enostaven, saj moramo med igranjem v določenem času le na ustrezno mesto izrisati bitno sliko, ki predstavlja ladjico.

    SpaceInvader

    Razred SpaceInvader predstavlja primerke napadalcev. V igri so trije tipi napadalcev, ki se med seboj razlikujejo po videzu in številu točk, ki jih prinesejo ob uspešni sestrelitvi. Vesoljski napadalci so tudi animirani, kar pomeni, da se njihov videz s časom spreminja. To smo izvedli tako, da smo za vsakega napadalca narisali dve sliki, ki ju pri premiku napadalca zamenjujemo. Zgledi parov slik so prikazani na sliki 4. Tako ustvarimo navidezno spreminjanje napadalca (premikanje njegovih posameznih delov). Razred SpaceInvader je zasnovan tako, da ga lahko uporabimo za prikaz poljubnega izmed treh tipov napadalcev. Stvaritev napadalca, določitev njegovega tipa, velikosti in položaja prikazuje spodnji zgled:

    SpaceInvader invader = new SpaceInvader(gameView.getResources());
    invader.setInvaderType(2);
    invader.setScale(scale);
    invader.setPosition(...);

    SLIKA 4: Prikazane slike vseh treh parov slik napadalcev, svetlo moder - vreden 10 točk, zelen - vreden 20 točk, in roza - vreden 30 točk.

    Metode, ki jih vsebuje razred SpaceInvader, so:

  • setInvaderType - metoda, ki sprejme argument tipa int in s katero določimo tip napadalca. Tip napadalca določimo tako, da določimo, kateri dve sliki napadalca določata, in nastavimo vrednost spremenljivke razreda, ki hrani tip napadalca;
  • swichImage - metoda, ki zamenja sliko napadalca, uporabljeno pri izrisu;
  • setPosition - metoda prejme dva argumenta tipa float, s katerima določimo vodoravni in navpični položaj napadalca na zaslonu. Pri tem je treba upoštevati, da je s temi vrednostmi določeno središče slike.
  • moveX, moveY - metodi, ki skrbita za premik napadalca po zaslonu v vodoravni in navpični smeri za vnaprej določeno vrednost.
  • changeDirectionX - metoda, ki spremeni smer premikanja napadalca v vodoravni smeri.
  • getDirectionX - metoda, ki vrne trenutno smer premikanja napadalca v vodoravni smeri.
  • getWidth, getHeight - metodi, ki vrneta širino in višino slike napadalca.
  • getX, getY - metodi, ki vrneta vodoravni in navpični položaj središča napadalca.
  • getMinX, getMaxX, getMinY, getMaxY - metode, ki vračajo vrednosti minimalnih in maksimalnih vrednosti napadalca na zaslonu.
  • getScore - metoda, ki vrne število točk, kolikor je vreden napadalec.
  • Bullet

    Izstrelki, ki jih kot igralec izstreljujemo proti napadalcem, so definirani z razredom Bullet. Razred v osnovi predstavlja izstrelek, ki ga izstreli vesoljska ladjica, lahko pa bi z njim realizirali izstrelke, ki bi jih napadalci izstreljevali proti vesoljski ladjici. Spremenljivke razreda hranijo sliko izstrelka, položaj izstrelka na zaslonu, smer izstrelka, hitrost izstrelka in tip izstrelka, metode razreda pa so:

  • setPosition - metoda, s katero določimo položaj izstrelka. Metodo kličemo ob sami izstrelitvi, ko je treba položaj izstrelka nastaviti na položaj vesoljske ladjice, s čimer dobimo občutek, da vesoljska ladjica dejansko izstreli izstrelek.
  • setSpeed - metoda za nastavitev hitrosti izstrelka. S to metodo lahko otežimo delo igralca tako, da izstrelke upočasnimo ali poenostavimo tako, da izstrelke pohitrimo.
  • move - metoda, ki premakne izstrelek v določeni smeri premika glede na določeno hitrost.
  • setType - metoda, s katero določimo vrsto izstrelka v primerih, ko želimo v igri uporabiti več kot eno samo vrsto izstrelka.
  • isValid - metoda, ki nam pove, ali je izstrelek ob določenem času veljaven ali ne. Izstrelek se označi kot neveljaven, kadar se pomakne onkraj meja zaslona ali pa je udeležen v trku.
  • getMinX, getMaxX, getMinY, getMaxY - metode, ki vračajo vrednosti minimalnih in maksimalnih vrednosti izstrelka na zaslonu.
  • Izstrelek je animiran, kar je realizirano s časovnikom, podobno kot pri napadalcih. Za razliko od napadalcev se položaj izstrelka izračuna vsakih 40 ms, s čimer postane njegovo premikanje po zaslonu veliko bolj zvezno.

    GameLevel

    Celotno okolje, v katerem se odvija igra (ozadje, okrasi ...), je definirano z razredom GameLevel. Na ta način lahko enostavno prilagajamo videz posameznega nivoja igre in tako naredimo igro bolj zanimivo. V razredu tako glede na trenutni nivo igre izrisujemo različne grafične elemente, s katerimi je definirano igralno okolje.

    Kako naprej?

    V članku je opisana struktura celotne igre in povzeta funkcionalnost posameznih delov. Kljub temu da gre za preprosto igro, skozi članek spoznamo, da je za lažji razvoj in izvedbo zelo pomembna dobra razredna struktura aplikacije. Naša aplikacija - igra je sestavljena iz 10 razredov. Nekateri razredi predstavljajo zgolj drobne koščke celote (posamezni elementi izrisa GUI, SpaceInvader, SpaceShip ...), nekateri pa povezujejo celotno množico razredov (Game) in s tem definirajo delovanje celotne igre. Omenili smo tudi možnosti za nadgradnjo igre z dodatnimi nivoji in možnostjo implementacije agresivnejših igralcev s sposobnostjo streljanja. Prav tako trenutna realizacija igre ne upošteva življenj igralca, temveč se konča, ko napadalec uniči vse napadalce, ali pa ko napadalci dosežejo našo vesoljsko ladjico. Projekt tega članka je tako kot prejšnji dostopen na android.monitor.si. Vsi nastavki za nadgradnje programa so namenoma nedokončani. Program bomo skupaj nadgradili v naslednjem članku, bralec pa lahko do takrat doda poljubno funkcionalnost po lastni želji ali na svoj način implementira nakazane razširitve.

    Naroči se na redna tedenska ali mesečna obvestila o novih prispevkih na naši spletni strani!

    Komentirajo lahko le prijavljeni uporabniki

     
    • Polja označena z * je potrebno obvezno izpolniti
    • Pošlji