Objavljeno: 29.10.2013 | Avtor: Ciril Bohak, Matevž Pesek | Monitor November 2013

Android in razvoj 3D igre, zaključek

Pa smo pri koncu. V tokratnem članku bomo predstavili še zadnje malenkosti v igri, a to vsekakor ne pomeni, da je igra že končana in pripravljena za prodajo. Implementirali bomo še nekaj posebnih in zvočnih učinkov, dodali  glasbo za ozadje in si ogledali, kako lahko v igro zajamemo tudi animacijo in bonuse. S tem se lahko začne postopek piljenja in glajenja videza in igralnosti, kar pri razvoju igre pomeni precejšen del razvoja. Kot vedno bosta tudi tokrat končna igra in koda dostopni na spletnem naslovu: android.monitor.si.

Kar nekaj kode smo spisali, odkar smo skupaj začeli ustvarjati 3D igro za platformo Android. Najprej smo si ogledali osnove 3D računalniške grafike in spoznali vmesnik OpenGL ES, ki ga za prikaz strojno pospešene 3D računalniške grafike uporabljajo praktično vse mobilne naprave. Prek osnov smo se prebili do načrta igre in igro razdelili na posamezne zaključene dele ter se počasi lotevali njihove implementacije. Pot nas je vodila prek uvažanja modelov, priprave ozadja, implementacije gibanja predmetov po prostoru, zaznavanja trkov pa vse do izdelave grafičnega vmesnika tako tistega, ki ga vidimo med igranjem, kot tistega, ki nas popelje v samo igro. Ne nazadnje smo dodali tudi točkovalni sistem in sistem življenj. V pričujočem članku pa si bomo pogledali še, kako v igro dodamo bonuse,  glasbo za ozadje, ki nas spremlja skozi celotno igro, in kako dodamo posamezne zvočne učinke. Ogledali si bomo tudi, kako implementiramo nekatere vizualne posebne učinke, kot so posebni senčilniki za izstrelke in animirane eksplozije asteroidov.

Bonusi

V drugem delu serije smo predstavili, kako ladjico oblečemo v energijski ščit. Doslej smo jo ovili zgolj ob začetku igre, dodali pa bomo še možnost, da tak ščit za nekaj časa vključimo tudi med samo igro, tako da poberemo ustrezen bonus. Bonusi predstavljajo »pakete« v igralnem prostoru, ki igralcu olajšajo dokončanje igre. V našem primeru bomo v igro vpeljali dve vrsti bonusov. Prvi je namenjen pridobivanju dodatnega življenja, drugi pa vključitvi ščita.

Posamezen bonus je sestavljen iz dveh elementov. Prvi element je sfera, ki je izvedena podobno kot pri ščitu, le da smo uporabili drugačno teksturo. V sredino sfere pa postavimo kvadratno ploskev in nanjo prilepimo teksturo, ki predstavlja posamezen bonus. Bonuse bomo v sceno postavili na mesto, kjer uspešno uničimo posamezen asteroid. Pri tem igralcu seveda ponagajamo s tem, da omejimo čas, ko lahko bonus poberemo. Po preteku tega časa bonus izgine. Kateri izmed bonusov se bo v določenem trenutku pojavil, izberemo naključno.

Zvok

V okolju Android poznamo dva osnovna načina za uporabo zvoka, ki sta namenjena različnim rabam. V naši igri bosta prišla v poštev oba. Prvi način je namenjen igranju daljših zvočnih posnetkov, ko začetna zakasnitev predvajanja ni tako pomembna, pomembno je le, da imamo nadzor nad samim predvajanjem zvoka, ki ga lahko prekinemo, prevrtimo, mu med predvajanjem spreminjamo glasnost ipd. Drugi način je namenjen uporabi, kadar želimo krajše zvoke prožiti v točno določenem trenutku.

Slika 1: Prikaz bonusov v igri. S ščitom je predstavljen bonus energijskega ščita.

Slika 1: Prikaz bonusov v igri. S ščitom je predstavljen bonus energijskega ščita.

Glasba za ozadje

Prvi pristop bomo uporabili pri implemetaciji predvajanja glasbe v ozadju, ki se bo predvajala vso igro, od samega zagona pa vse, dokler ne bomo igre zaprli ali je postavili v ozadje. V tem primeru bomo uporabili medijski predvajalnik, ki je predstavljen z razredom MediaPlayer. Da naša aplikacija ne bo po nepotrebnem zasedala v delovnem pomnilniku (RAMu) dodatnega prostora v velikosti zvočne datoteke glasbe v ozadju, kar lahko hitro pomeni kar nekaj dragocenih megabajtov, bomo zvočno datoteko shranili v nepovezane vire aplikacije, ki se berejo šele, ko jih naslavljamo iz aplikacije in se ne naložijo skupaj z aplikacijo v delavni pomnilnik. Takšnim vsebinam je v strukturi Android projekta namenjena podmapa assets. Datoteke v tej mapi se ne preverjajo posebej, temveč se pri prevajanju združijo v eno samo datoteko, katere dele lahko nato naslavljamo iz naše aplikacije. V našem primeru to pomeni, da bomo izbrano mp3 datoteko prenesli v mapo assets. V našem primeru smo datoteko z  glasbo za ozadje poiskali na priljubljenem spletnem mestu za izmenjavo glasbe – SoundCloud (http://www.soundcloud.com), kjer najdemo tudi brezplačno glasbo, ki jo smemo prosto uporabiti za lastne potrebe. Ko želeno datoteko prenesemo v omenjeno mapo, lahko do nje pridemo tudi iz programske kode. Za prebiranje datotek v mapi assets moramo uporabiti razred AssetFileDescriptor, s katerim lahko dosežemo izbrano datoteko. Preostane nam samo še to, da datoteko pripnemo novo ustvarjenemu objektu razreda MediaPlayer, ki mu ustrezno nastavimo parametre glasnosti predvajanja in ponavljanja ter sprožimo predvajanje. Vse skupaj je predstavljeno v spodnjem izseku kode.

backgroundPlayer = new MediaPlayer();

AssetFileDescriptor descriptor =

  mContext.getAssets().openFd("background.mp3");

backgroundPlayer.setDataSource(

  descriptor.getFileDescriptor(),

  descriptor.getStartOffset(),

  descriptor.getLength());

descriptor.close();

backgroundPlayer.prepare();

backgroundPlayer.setVolume(0.1f, 0.1f);

backgroundPlayer.setLooping(true);

backgroundPlayer.start();

Celoten izsek je zaradi morebitnih izjem priporočljivo oviti v try ... catch blok zaradi možnih izjem, ki se lahko zgodijo med predvajanjem glasbe. Predvajanje glasbe v ozadju lahko sprožimo že med samo inicializacijo igre, torej v metodi initScene razreda AsteroidsRenderer. Paziti je treba, kaj naj se s predvajanjem glasbe zgodi, ko se osnovna dejavnost naše igre pošlje v ozadje. V tem trenutku je najprimerneje predvajanje glasbe zaustaviti in ga znova sprožiti, ko naša igra spet preide v ospredje. To storimo s preobložitvijo metod onPause in onResume osnovne dejavnosti naše aplikacije AsteroidActivity. V tem primeru je treba klicati ustrezni metodi medijskega predvajalnika pause in start. Še posebej pazljivi pa moramo biti, da ne pozabimo najprej klicati iste metode nadrazreda, saj lahko v nasprotnem primeru naša igra neha delovati. Podali bomo primer za metodo onPause, implementacija metode onResume pa je ustrezno prirejena. Če ne moremo neposredno do metod medijskega predvajalnika, lahko to storimo prek dodatnih metod razreda, v katerem imamo neposreden dostop do medijskega predvajalnika.

public void onPause() {

  super.onPause();

  if (backgroundPlayer != null) {

    backgroundPlayer.pause();

  }

}

Implementacija predvajanja glasbe v ozadju je pri razvoju za Android preprosta. Lahko pa si seveda življenje zagrenimo tako, da uporabimo v menujih drugačno glasbo v ozadju kakor v sami igri. Prav tako je morebiti smiselno uporabiti več kot eno samo glasbeno datoteko, saj se bodo uporabniki v nasprotnem primeru kaj hitro naveličali poslušanja ene in iste podlage. Implementacijo takšnih razširitev prepuščamo posameznim nadobudnežem.

Slika 2: Prikaz gradiva izstrelka ob različnem času. Izstrelek je prikazan kot običajna sfera zaradi vidljivosti.

Slika 2: Prikaz gradiva izstrelka ob različnem času. Izstrelek je prikazan kot običajna sfera zaradi vidljivosti.

Zvočni učinki

Zvočni učinki predstavljajo kratke zvoke v igri, kot so zvok izstrelka, zvok eksplozije ipd. V našem primeru bomo implementirali predvajanje zvokov za pet dogodkov: izstrelitev projektila, aktiviranje pogona ladjice, eksplozijo asteroida, aktivacijo ščita in pojavitev bonusa na zaslonu. Primerne zvoke smo poiskali na strani freesound (http://www.freesound.org), kjer je brezplačno na razpolago velik nabor najrazličnejših zvokov, ki jih lahko uporabimo tudi v našem primeru kot zvočne učinke. Za predvajanje zvočnih učinkov bomo uporabili že omenjeni razred SoundPool. Omogoča nam, da vzporedno predvajamo tudi več prekrivajočih se zvokov do števila, ki ga podpira posamezna naprava, oz. do števila, ki ga določimo ob ustvarjanju objekta. V tak objekt lahko povežemo celo množico zvokov s klicem metode load nad primerkom razreda. Metoda kot parametre sprejme kontekst aplikacije mContext, kazalec na zvočni vir in prioritetno stopnjo. Pri tem si moramo na lasten način implementirati način, kako bomo hranili, na katerem mestu je kateri zvok. V našem primeru bomo to storili s preslikovalno tabelo. Pri rabi tega pristopa se moramo zavedati, da zvokov ne moramo nujno prožiti neposredno po zagonu aplikacije, saj se morajo poprej naložiti v pomnilnik in pripraviti za predvajanje. Zvočne datoteke bomo v tem primeru pripravili v obliki datotek wav, to pa seveda ne izključuje drugih formatov. Datoteke bomo prenesli v mapo s surovimi viri, ki je tako kot za druge surove vire v podmapi res\raw. Pri tem naj spet opozorimo na ustrezno poimenovanje datotek - male črke brez presledkov in posebnih znakov razen izjem.

Podobno kot za glasbo v ozadju bomo vse potrebne stvari pripravili že med inicializacijo igre v metodi initScene razreda AsteroidRenderer. Kratek izsek kode je prikazan spodaj, tam dodamo 2 zvoka, za druge pa je stvar ekvivalentna.

soundPool = new SoundPool(10, AudioManager.

  STREAM_MUSIC, 100);

soundPoolMap = new HashMap<Integer,

  Integer>(3);    

soundPoolMap.put(explosionSound,

  soundPool.load(mContext, R.raw.explosion, 1) );

soundPoolMap.put(exhaustSound,

  soundPool.load(mContext, R.raw.exhaust, 2) );

...

V spremenljivki soundPoolMap hranimo reference na prednaložene zvoke, kar v našem primeru pomeni pare vrednosti int. S tem si zagotovimo, da bomo znali v ustreznem trenutku predvajati pravi posnetek.

Preostane nam samo še, da ob določenih trenutkih prožimo ustrezne zvoke. Tako moramo v trenutku,  ko zaznamo trk med asteroidom in izstrelkom, sprožiti zvok eksplozije. Ko sprožimo izstrelek, moramo sprožiti zvok izstrelka in v ustreznih trenutkih tudi druge zvoke. Spodaj je prikazan izsek kode, ki sproži predvajanja posnetka eksplozije v trenutku, ko zaznamo trk med izstrelkom in asteroidom.

// zaznana eksplozija

this.addChild(explosion);

if(soundPool != null && soundPoolMap != null){

  // glasnost, ki jo uporabimo za oba kanala

  float volume = 1;

// sprožimo predvajanje

  soundPool.play(soundPoolMap.get(explosionSound),

    volume, volume, 1, 0, 1f);

}

Metoda play objekta soundPool sprejme šest parametrov: referenco na izbrani posnetek, glasnost levega kanala, glasnost desnega kanala, prioriteto, število ponovitev in hitrost predvajanja. V našem primeru je glasnost na levem in desnem kanalu enaka, prioriteta je 1. stopnje, ponovitev nimamo, hitrost predvajanja pa je enaka originalni. Enako kot za eksplozijo izvedemo tudi predvajanje drugih posnetkov.

S tem smo končali implementacijo zvoka v naši igri. Seveda se lahko bralec posveti še kakšni malenkosti. Podobno lahko dodamo zvoke tudi na osnovni menu aplikacije in s tem popestrimo izbiro posamezne izbire. Dodamo lahko tudi zvok ob koncu igre in s tem igralca še dodatno opomnimo, da je igre konec.

Slika 3: Končni videz izstrelkov v igri

Slika 3: Končni videz izstrelkov v igri

Animacija

V preteklih člankih smo že predstavili, kako lahko animiramo premike in rotacije posameznih predmetov, pozornosti pa nismo posvečali animiranju materialov na objektih, s čimer lahko pričaramo posebne učinke, kot so eksplozije in spreminjajoči se laserski izstrelki.

Laserski izstrelki

Laserski izstrelki so v našem primeru predstavljeni kot v dveh smereh sploščena sfera, na katero smo prilepili proceduralno material prelivajočih barv, ki je tudi animirano. Gradivo »zamikamo« med premikanjem sfere in to povzroči učinek prelivajočih se barv izstrelka, kot vidimo na sliki 1.

Omenjeni material je izvedeno z namenskim senčilnikom, ki se spreminja glede na trenutni čas. V ta namen smo ustvarili nov razred BulletMaterial, ki razširja razred SimpleMaterial. Materialu sporočamo trenutni čas, na podlagi katerega se nato prilagodi izris barv v senčilniku. Preprost senčilnik GLSL je predstavljen v izseku kode spodaj. Kako točno uporabimo predstavljeni senčilnik, je predstavljeno v programski kodi.

precision mediump float;

uniform float uTime;

varying vec2 vTextureCoord;

void main() {

  vec4 newColor = vec4(0, 0, 1.0, 1.0);

  float x = min(vTextureCoord.s, 1.0 -

    vTextureCoord.s);

  float y = vTextureCoord.t;

  newColor.g = sin( 20.0 * x + uTime * 8.0 );

  gl_FragColor = newColor;

}

Kot lahko razberemo iz zgornjega senčilnega programa, se pri izračunu barve na površini sfere uporabi tudi spremenljivka uTime, ki se skozi čas spreminja. Barvni preliv pa je ustvarjen s preprosto rabo trigonometričnih funkcij, ki barve ustrezno razporedijo po površini predmeta. Na tem mestu naj vas spet opomnimo, da so senčilniki programi, ki se izvajajo na sami grafični kartici. To pripomore k njihovi hitrosti. Zaradi tega si lahko tudi privoščimo, da se tak program izvaja za vsak izrisan slikovni element posebej.

Vse skupaj smo »zapakirali« v razred Bullet, ki ga uporabimo, kadar sprožimo izstrelitev posameznega izstrelka. Ta razred ima tudi druge metode, ki omogočajo premikanje izstrelka, uničenje ali preverjanje, ali je izstrelek zadel katerega izmed objektov.

Slika 4: Sekvenca zaporednih sličic eksplozije

Slika 4: Sekvenca zaporednih sličic eksplozije

Eksplozija Asteroida

Posebni učinek eksplozije bomo izvedli s t. i. pristopom sprite-animation. Pri tem pristopu uporabimo zvijačo, da uporabniku namesto simulacije prave eksplozije prikazujemo zaporedje sličic, ki prikazujejo eksplozijo. Takšna sekvenca slikic je prikazana na sliki 4. Kot je razvidno, bomo v našem primeru za eksplozijo prikazali zaporedje 64 sličic. Glede na to, da so sličice v dveh razsežnostih, moramo paziti, da so te sličice stalno obrnjene povsem proti igralcu. V našem primeru to sicer ni problem, ker se kamera v igri ne premika, nekoliko pa se zaplete, ko je kamera prosto gibljiva.

Za potrebe eksplozije bomo ustvarili nov razred AnimatedMaterial, ki razširja osnovno gradivo, a je prilagojen zaporednemu izrisu zgolj posameznega dela celotne teksture. Pristop je nekoliko podoben kot pri izstrelku, treba pa je implementirati nove senčilnike. Eksplozija je v našem primeru  predstavljena s preprosto kvadratno ploskvijo, ki jo postavimo pred vse druge objekte. Pri tem moramo paziti tudi na prosojnost, saj želimo, da se skozi eksplozijo, kjer ni ognja in dima, vidijo stvari v ozadju. V primeru eksplozije moramo implementirati dva senčilnika. V prvem bomo izvedli ustrezne preračune izreza teksture, ki ga želimo v določenem trenutku prikazati, v drugem senčilniku pa bomo poskrbeli, da se ustrezen del teksture tudi dejansko izriše na ploskev in upošteva prosojnost.

Medtem ko pri izstrelku neprestano preračunavamo, kakšen bo material v določenem trenutku, je pri eksploziji povsem drugače. Ko enkrat pridemo do prikaza zadnje sličice, želimo končati izris. Naš razred Animated material je prilagojen za prikaz različnih animacij, določiti moramo le, koliko sličic je dejansko »zapakiranih« v celotno teksturo; torej kolikšen del teksture naj se naenkrat prikazuje. Kako je to izvedeno v senčilniku, je prikazano v spodnjem izseku kode.

void main() {

  vec4 position = vec4(aPosition);

  gl_Position = uMVPMatrix * position;

  vTextureCoord.s = (

    mod(uCurrentFrame, uNumTileRows)

    + aTextureCoord.s) * uTileSize;

  vTextureCoord.t = uTileSize *

    (floor(uCurrentFrame  / uNumTileRows)

    + aTextureCoord.t);

}

Podobno kot pri izstrelku tudi pri eksploziji vse skupaj zajamemo v razred Explosion, ki ga uporabljamo za proženje posameznih eksplozij na ustreznih mestih na zaslonu. Eksplozije je smiselno prožiti, kadar asteroid zadenemo z izstrelkom, kadar se vanj zaletimo z vesoljsko ladjico ali pa takrat, ko med seboj trčita dva asteroida.

Možne razširitve

Slika 5: Prikaz eksplozije v sami igri

Slika 5: Prikaz eksplozije v sami igri

V celotni seriji člankov smo se skupaj spoznali z osnovami 3D računalniške grafike, spoznali smo ogrodje Rajawali, ki nam močno olajša razvoj 3D aplikacij za platformo Android, nato pa smo ugriznili tudi v sočni sadež razvoja 3D računalniških iger. Implementirali smo kar nekaj funkcionalnosti, ki jih ima večina iger. Ogledali smo si, kako se lotiti načrtovanja, na kaj vse moramo biti pozorni in kateri elementi so najpomembnejši. Avtorja upava, da sva s to serijo člankov v bralcih vzpodbudila željo, da se razvoja igre lotijo tudi sami.

Žal celotnega razvoja, od zamisli pa do splavitve, ni mogoče predstaviti tako na kratko. Kljub temu upava, da sva dovolj jasno predstavila osnove in pokazala, kje lahko posameznik najde še več informacij. Na spletu že vrsto let deluje skupnost slovenskih razvijalcev računalniških iger pod imenom SloGameDev. Spletna stran skupnosti je na spletnem naslovu: http://www.slogamedev.net/, kjer se objavljajo novice o dogajanju v skupnosti, prav tako si člani prek foruma izmenjujejo mnenja, sodelujejo in si pomagajo pri reševanju problemov. Skupnost najdete tudi v družabnem omrežju Facebook.

• • •

Razvoj iger pa seveda ni trdno vezan na posamezno platformo. Zamisli in koncepti so enaki pri razvoju na večini platform, pa naj bodo to Okna, OsX ali Linux na platformi PC, katera izmed konzol ali pa celo spletna platforma. Nedavno je prek skupinskega financiranja (glej Kickstarter) prišla na trg prva konzola na platformi Android. Sicer je treba posamezno razvito igro za konzolo malenkost prilagoditi, predvsem zaradi uporabe igralnega ploščka, vse drugo pa ostaja enako kot pri razvoju za mobilne naprave.

Doslej smo v Monitorjevem poglavju Android predstavili razvoj dvodimenzionalnih in tridimenzionalnih iger, med obema vodnikoma pa smo si ogledali tudi rabo vgrajenih senzorjev in izdelavo aplikacij z vgrajenimi pogledi. Čeprav je razvoj aplikacij resen posel, smo želeli pokazati bolj do začetnika prijazno plat razvoja z vgrajenimi funkcionalnostmi in programskimi ogrodji. Možnosti za razvoj je še neskončno. Čeprav smo za razvoj tridimenzionalne igre uporabili programsko okolje, tak razvoj terja nekaj poprejšnjega matematičnega znanja in izkušenj s programiranjem. Popolnim začetnikom so na voljo tudi programski paketi za lažji razvoj iger, ki sicer omejijo zmožnosti končnega izdelka, a poenostavljajo razvoj. Take pakete si bomo ogledali v eni izmed prihodnjih številk revije. Navsezadnje pa razvoj ni najtežji del uspeha – tudi dobra zamisel šteje in z malo truda lahko prav tvoja aplikacija postane svetovna uspešnica. 

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