Development of a Game Engine and Editor for Creation of 2D Games
Fakulteta za elektrotehniko, računalništvo in informatiko Smetanova ulica 17 2000 Maribor, Slovenija
Bojan Kerec
IZDELAVA IGRALNEGA POGONA IN UREJEVALNIKA ZA IZDELAVO 2D IGER
Magistrsko delo
Maribor, november 2014
i
IZDELAVA IGRALNEGA POGONA IN UREJEVALNIKA ZA IZDELAVO 2D IGER Magistrsko delo
Študent: Bojan Kerec Študijski program: Računalništvo in informacijske tehnologije (MAG) Mentor: red. prof. dr. Borut Žalik Somentor: doc. dr. Gregor Klajnšek
ii
iii
Zahvala
Zahvaljujem se mentorju red. prof. dr. Borutu Žaliku in somentorju doc. dr. Gregorju Klajnšku za pomoč in vodenje pri izdelavi magistrskega dela.
Zahvaljujem se tudi družini za podporo in strpnost.
Zahvala gre tudi pristojnim organizacijam, da me niso vpisale v register nepremičnin, zaradi neštetih ur, preživetih za računalnikom.
iv
Izdelava igralnega pogona in urejevalnika za izdelavo 2D iger
Ključne besede: igralni pogon, urejevalnik, računalniška igra
UDK: 004.354.7:004.9(043.2)
Povzetek:
V tem magistrskem delu predstavimo postopek izdelave igralnega pogona. Nalogo začnemo z opsiom razvoja igralnih pogonov skozi čas in pregledom obstoječih rešitev. Nato podamo prednosti in slabosti izdelave lastnega igralnega pogona in ovrednotimo smotrnost izbire že obstoječega igralnega pogona. Nadaljujemo s predstavitvijo igralnega pogona, ki smo ga razvili. Predstavimo arhitekture in organizacije sistemov igralnega pogona, podamo kratek pregled implementacije najpomembnejših sistemov in opišemo izdelavo urejevalnika nivojev. Zmogljivost razvitega igralnega pogona prikažemo z izdelavo preproste 2D igre.
v
Development of a game engine and editor for creation of 2D games
Keywords: game engine, editor, computer game
UDK: 004.354.7:004.9(043.2)
Abstract:
In this work we present the process of development of a game engine. Thesis begins with description of evolution of game engines and with an overview of the existing solutions. After that an analysis of advantages and disadvantages of development of a proprietary game engine is given together with evaluation of expedience of using an existing game engine. Thesis continues with description of development of our proprietary game engine. The architecture of the engine and organization of core system is presented at first, followed by specific implementation details and description of development of the level editor. The power and capacity of the developed game engine is presented through development of a simple 2D game.
vi
Kazalo vsebine
1 Uvod ...... 1
2 Obstoječe rešitve ...... 4
3 Izbira ali izdelava igralnega pogona ...... 7
4 Arhitektura ...... 9
4.1 Aplikacijski nivo ...... 10
4.2 Igralna logika ...... 11
4.3 Igralni pogledi ...... 12
5 Organizacija akterjev ...... 14
6 Implementacija ...... 17
6.1 Razred GameApp ...... 18
6.2 Upravljanje z oknom ...... 20
6.3 Viri ...... 20
6.4 Vhodne naprave ...... 21
6.5 Sistem za upodabljanje ...... 23
6.6 Dogodki ...... 24
6.7 Mrežna komunikacija ...... 26
6.8 Igralna logika ...... 27
6.9 Glavna zanka ...... 28
6.10 Akterji...... 29
6.11 Scena ...... 31
6.12 Igralni pogledi ...... 32
6.13 Avdio sistem ...... 32
6.14 Grafični uporabniški vmesnik ...... 33
vii
6.15 Podpora uporabi skriptnega jezika ...... 34
7 Urejevalnik ...... 37
8 Uporaba v praksi ...... 43
9 Sklep ...... 48
10 Literatura ...... 49
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 1
1 Uvod
Izraz igralni pogon (angl. game engine) izvira iz sredine devetdesetih let z referenco na prvoosebne strelske igre, kot je bil takrat izjemno popularen Doom, izdelek podjetja id Software. Doom je bil zasnovan z zelo dobro ločitvijo temeljnih komponent (sistem za upodabljanje, sistem za zaznavanje trkov) in komponent igre (viri, svetovi in pravila igre). Vrednost ločitve se je pokazala, ko so razvijalci začeli obstoječe produkte preurejati in iz tega izdelovati nove produkte tako, da so ti dobili novo grafično podobo, nove svetove, orožja, vozila, zgolj z minimalnimi spremembami temeljnih komponent. Proti koncu devetdesetih let so bile nekatere igre (Quake 3 Arena, Unreal) že zasnovane s ponovno uporabo in z mislijo na bodoče spremembe in nadgradnje v mislih. Temelji teh iger, imenovani igralni pogoni, so bili narejeni tako, da so omogočali izdelavo in prilagajanje vsebine igre s posebno namenskimi urejevalniki in skriptnimi jeziki. Takrat je postalo licenciranje igralnih pogonov dobra alternativa za zaslužek.
Meja med igro in njenim pogonom je pogosto zamegljena. Nekateri pogoni jasno nakažejo ločnico, pri drugih pa te ločnice ne moremo določiti. Primer tega je, da v enem pogonu lahko prikažemo le točno določeno pošast, v drugem pa imamo na voljo orodja za risanje, postopek upodabljanja določene pošasti pa je definiran ločeno. Razliko med igralnim pogonom in igro v grobem določi podatkovno- usmerjena arhitektura. Kadar igra vsebuje zakodirano logiko ali pa vsebuje specifične elemente, izvorno kodo take igre zelo težko uporabimo za razvoj drugačne igre ali pa je ponovna uporaba povsem nemogoča. Iz tega spoznamo, da je igralni pogon programski produkt, ki je razširljiv in ga je moč uporabiti brez velikih sprememb kot temelj za mnogo različnih iger [1].
Igralni pogoni so običajno specifični za določen žanr. Igralni pogon za izdelavo športnih iger (Fifa) je povsem drugačen kot igralni pogon za izdelavo prvoosebnih
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 2
strelskih iger (Quake). Vendar pa imajo tudi veliko skupnega. Vsak igralni pogon mora tako znati izrisovati geometrijo, predvajati zvok, obdelovati in analizirati vhodne podatke iz različnih naprav (tipkovnica, miška, igralni plošček) in opravljati še veliko drugih funkcij. V nadaljevanju bomo izpostavili ključne lastnosti igralnih pogonov, ki so pomembne za specifičen žanr.
Prvoosebne strelske igre (angl. first person shooter - FPS) vsebujejo dokaj počasno pešačenje po notranjih ali zunanjih prostorih, vožnjo z vozili (po zemlji, zraku in vodi) in običajno veliko izbiro orožja ter balistiko. Tipični predstavniki te skupine so igre Quake, Unreal Tournament, Half Life in druge podobne igre. Običajno so tehnološko najbolj razvite in se osredotočajo na učinkovito upodabljanje 3D svetov, odzivno premikanje po svetu, kakovostne animacije in dobro umetno inteligenco.
Ploščadne igre (angl. platformer) so tretjeosebne ali 2D stransko pomične (angl. side scroller) igre, pri katerih je glavna mehanika skakanje iz ploščadi na ploščad. Tipični predstavniki so Donkey Kong, Super Mario Bros ter Rachet & Clank. Ta žanr se osredotoča na premične platforme, vrvi, lestve, uganke z uporabo okolijskih elementov ter napredni nadzor nad kamero.
Pretepaške igre so običajno dvoigralske igre, pri katerih se humanoidni liki mikastijo med sabo v ringu ali areni. Predstavniki so Soul Calibur, Tekken in Mortal Kombat. Igre pretepaškega žanra se osredotočajo na veliko število pretepaških animacij, natančno zaznavo trkov ter zmožnost vhodnega sistema, da procesira kompleksne kombinacije vhodnih podatkov.
Dirkaški žanr zajema vse igre, pri katerih je glavni cilj vožnja avta ali kakšnega drugega vozila. Žanr ima podzvrsti, kot so simulacije in arkadne igre. Osredotoča se na uporabo posebnih podatkovnih struktur za prikazovanje prog, umetno inteligenco in iskanje poti, uporabo trikov upodabljanja manj pomembne geometrije (ozadje) ter postavitev kamere (sledenje avtu, pogled iz avta).
Realno-časovne strategije so igre, pri katerih igralec strateško postavlja bojne enote po velikem bojnem polju, da bi premagal svojega nasprotnika. Predstavniki so Warcraft, Command & Conquer in Age of Empires. Te igre običajno uporabljajo
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 3
pogled iz višav. Pri tem žanru je pomembno, da imajo enote sorazmerno nizko kakovostno geometrijo, da jih lahko na ekranu predstavimo v ogromnem številu. Igralec lahko postavlja enote in zgradbe, interakcija pa običajno poteka z miško.
V uvodnem poglavju smo definirali pojem igralni pogon in nato predstavili nekaj najpomembnejših lastnosti igralnih pogonov. V nadaljevanju bomo najprej predstavili obstoječe rešitve in nato navedli, kdaj je izbira obstoječega igralnega pogona ustrezna rešitev in kdaj se je smiselno posvetiti razvoju lastnega igralnega pogona. V poglavju 2 bomo tako predstavili nekaj najbolj znanih in razširjenih igralnih pogonov, v poglavju 3 pa se bomo posvetili analizi razlogov za oziroma proti izdelavi lastnega igralnega pogona. V poglavjih 4 in 5 bomo predstavili arhitekturo našega igralnega pogona, v poglavju 6 pa dejansko implementacijo igralnega pogona. V poglavju 7 bomo nadaljevali z prestavitvijo razvoja urejevalnika nivojev in v poglavju 8 zaključili z izdelavo preproste igre, ki bo kot osnovo uporabljala razvit igralni pogon. Nalogo zaključimo s poglavjem 9.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 4
2 Obstoječe rešitve
Na trgu obstaja že veliko obstoječih rešitev. V nadaljevanju bomo izpostavili nekaj popularnih igralnih pogonov. Veliko igralnih pogonov je narejenih kot skupek raznih orodij, združenih v en celovit program (urejevalnik). Med najbolj znanimi so Unreal Engine, CryEngine, Unity, Source Engine in idTech, vsi znani po tem, da so bili z njimi narejeni naslovi AAA. Izdelujejo jih podjetja, ki se bolj ali manj osredotočajo na izdelavo igralnih pogonov.
Igralni pogon Unreal Engine [2] je razvilo podjetje Epic Games. Njegovi začetki segajo v devetdeseta leta z razvojem igre Unreal Tournament. Skozi čas je postajal vedno bolj razvit z vedno več funkcijami ter tako postal celovit programski produkt za izdelavo iger. Med najbolj prepoznavnimi igrami, narejenimi z igralnim pogonom Unreal Engine, so franšize Unreal Tournament, Gears of War, Bioshock, Batman: Arkham in še mnogo posameznih naslovov, razvitih tako s strani največjih razvojnih studijev kot tudi neodvisnih razvijalcev. Igralni pogon CryEngine [3] podjetja CryTek je prav tako celovita rešitev za razvoj iger, znana po zmožnostih prikazovanja ogromnih in detajlnih pokrajinah. Postal je testni program za ocenjevanje grafičnih zmožnosti računalnikov. Najbolj znane igre, izdelane s pogonom CryEngine, so Far Cry, Crysis in Ryse. Igralni pogon Source Engine [4] podjetja Valve je znan po tem, da se zelo veliko uporablja v skupnosti "modding". Z njim je narejenih na stotine različnih modifikacij (angl. mod). Najbolj je znan po igrah Half Life 2, Counter Strike: Source, Left4Dead in Portal. Igralni pogon Unity [5] podjetja Unity Tehcnologies je postal zelo popularna izbira med neodvisnimi razvijalci. Vsebuje vse potrebno tako za razvoj 2D kot tudi 3D iger brez potrebe po dodatkih. Igralni pogon idTech [6] (znan tudi kot Doom Engine) je igralni pogon, razvit s strani podjetja id Software. Znan je predvsem po inovativnih pristopih izrisovanja geometrije, saj je kot prvi uvedel binarno delitev prostora (angl. binary space partitioning), osvetlitev
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 5
na nivoju pikslov (angl. per-pixel lighting) in MegaTexture (celoten teren nivoja na eni veliki teksturi). Najbolj znane igre, ki uporabljajo pogon idTech, so serije Doom, Quake in Wolfenstein. Vsi našteti pogoni imajo podporo za več ciljnih platform (PC, konzole in mobilne naprave). Obstaja še veliko bolj ali manj znanih igralnih pogonov s podobnimi funkcionalnostmi. Med njimi najdemo:
- Havok Vision Engine [7] (Orcs Must Die!, Stronghold 4, The Settlers 7),
- Frostbite [8] (Battlefield 3 in 4, Army of Two),
- Leadwerks [9],
- Torque3D [10],
- ShiVa [11].
Nekaj igralnih pogonov je narejenih posebej za specifične žanre. Med njimi najdemo SAGE, Ignite in EGO. SAGE [12] (angl. Strategy Action Game Engine) je igralni pogon, ki ga uporablja podjetje Westwood Studios skupaj z Electronic Arts za izdelavo realno-časovnih strateških iger. Uporabljen je bil za izdelavo iger iz serije Command & Conquer. Igralni pogon Ignite [13] je nastal pod okriljem Electronic Arts in je namenjen izdelavi športnih iger. Njegove posebnosti so dobra umetna inteligenca in natančen fizikalni model igralcev. Z njim so narejene najnovejše igre iz serij Fifa, Madden, NBA Live, UFC in NHL. Igralni pogon EGO [14] je bil razvit v podjetju Codemasters in se uporablja za izdelavo dirkaških iger. Njegova posebnost je zelo podroben poškodbeni in fizikalni model. Najbolj znane igre, izdelane s tem pogonom, so Race Driver: Grid, serija Colin McRae: Dirt in serija F1.
Čeprav lahko z vsakim 3D igralnim pogonom naredimo 2D igro, ponuja le peščica teh pogonov pravo podporo izdelavi 2D iger. Večina do sedaj omenjenih igralnih pogonov se osredotoča predvsem na tehnološke rešitve, ki jih lahko popolnoma izkoristijo le osebni račnunalniki in igralne konzole. Vendar pa je v zadnjem času ponovno postala popularna tudi izdelava 2D iger, posebej pri neodvisnih razvijalcih, ki v večini izdelujejo igre za mobilne naprave, redkeje pa tudi za računalnike in konzole. Igralni pogoni, ki se osredotočajo na izdelavo iger za mobilne naprave (2D,
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 6
3D ali oboje), so naslednji: GameMaker, Project Anarchy, App Game Kit, Marmalade, Gamesalad, Cocos2D in Construct 2. GameMaker [15] so razvili v podjetju YoYo Games in je postal zelo popularna izbira za izdelavo 2D iger. Namenjen je tako začetnikom, saj je z njim možno izdelati igre brez programerskega znanja, kot tudi profesionalcem, ki jim je na voljo skriptni jezik GML, posebej razvit za ta igalni pogon. Igralni pogon Project Anarchy [16] je izdelalo podjetje Havok in je za razliko od pogona Havok Vision Engine posebej namenjen za razvoj iger za mobilne naprave. Izmed tehnologij, ki jih je Havok razvil, uporablja Physics, Animation Studio in AI. Igralni pogon App Game Kit [17] je razvilo podjetje TGC z namenom, da poenostavi razvoj iger za več mobilnih platform hkrati. Za pisanje izvorne kode uporablja skriptni jezik BASIC. Marmalade [18] je igralni pogon istoimenskega podjetja. Razvoj iger omogoča s programskim jezikom C++ ali skriptnim jezikom (LUA) in nima lastnega razvojnega okolja. Enake funkcionalnosti omogoča tudi Cocos2D - odprtokodni projekt [19], pri katerem je na voljo preprosto orodje za izdelavo nivojev ter uporabniškega vmesnika. Uporablja skriptni jezik LUA, s pomočjo katerega lahko v posebej prirejenim razvojnim okoljem izdelamo preprosto igro. Igralni pogon Gamesalad [20] za razvoj iger ponuja lasten urejevalnik, pri katerem za izdelavo igre ne potrebujemo programerskega znanja. Enako nudi tudi Construct 2 [21] podjetja Scirra, pri katerem za izdelavo logike uporabljamo vnaprej definirane dogodke.
V tem poglavju seveda nismo predstavili vseh igralnih pogonov, saj je seznam preobsežen. Zato smo izpostavili le najbolj znane igralne pogone ter tiste, ki so posebni na določenem področju.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 7
3 Izbira ali izdelava igralnega pogona
V prejšnjem poglavju smo navedli, da danes na trgu obstaja že veliko uporabnih rešitev. Preden se lotimo izdelave lastne igre nas torej najprej čaka odločitev, ali bomo igro izdelali s pomočjo kakšnega obstoječega igralnega pogona ali se bomo lotili izdelave lastnega pogona [22, 23]. Vsaka od možnosti ima svoje dobre in slabe lastnosti. Razlogi, zakaj bi bila izbira obstoječega igralnega pogona ustrezna rešitev, so:
- pomanjkanje tehničnega kadra (znanja programiranja),
- prihranek pri času in
- neposredno osredotočanje na vsebino igre.
Razloge, zakaj bi bila ustrezna rešitev izdelava lastnega igralnega pogona, pa lahko strnemo v:
- uporaba lastnih tehnologij, ki jih ne ponuja noben drug igralni pogon,
- igralni pogon ne podpira določenih funkcionalnosti, ki jih potrebujemo,
- stroški licenc in
- želja po učenju.
Kadar imamo idejo o igri in se ne želimo preveč obremenjevati s tehničnimi stvarmi, takrat je izbira obstoječega igralnega pogona prava odločitev. Dandanes je na trgu že precej različnih rešitev in ena izmed teh bi skoraj zagotovo podpirala želene funkcionalnosti igre. Tudi iz finančne plati položaj ni kritičen. Veliko igralnih pogonov je brezplačnih ali odprtokodnih, zraven tega pa tudi glavni igralci ponujajo licenčne modele, ki so prilagojene neodvisnim razvijalcem (Epic, CryTek in Unity ponujajo mesečno naročnino z nizkimi cenami). Velikokrat pa se lahko zgodi, da
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 8
obstoječi igralni pogoni ne podpirajo določenih funkcionalnosti, ki so unikatne pri določeni igri. V takem primeru pa smo primorani izdelati lasten pogon. Motiv za to potezo pa je lahko tudi pridobivanje novega znanja. Kadar želimo razumeti tehnologijo, ki se skriva za igralnim pogonom, ali pa se želimo izpopolniti na določenem področju, je izdelava lastnega pogona prava odločitev. Pri izdelavi lastnega igralnega pogona je pomemben tudi pristop. Napačen pristop bi bil, da bi začeli izdelovati igralni pogon, ne da bi vnaprej vedeli, za kakšno igro bo ta uporabljen. V takšnem primeru lahko veliko časa zapravimo za implementacijo funkcionalnosti, ki jih v igri sploh ne bi potrebovali. Takšen pristop bi bil ustrezen le v primeru, ko bi skupina izkušenih programerjev začela izdelovati igralni pogon z namenom, da ga kasneje trži.
Predstavili bomo dva možna pristopa k izdelavi igralnega pogona. Prvi pristop je takšen da se odločimo za izdelavo igre, ko imamo sestavljeno že vso razvojno dokumentacijo. Nabor funkcionalnosti je torej definiran, zato se lahko pri izdelavi igralnega pogona osredotočimo na implementacijo teh funkcionalnosti. Z dobrim načrtovanjem in ločitvijo splošnih funkcionalnosti lahko izdelamo igralni pogon, ki ga bo možno uporabiti tako za izdelavo zasnovane igre kot tudi za razvoj drugih iger s podobnimi funkcionalnostmi. Pri drugem pristopu se na začetku osredotočimo samo na izdelavo iger. Ko smo izdelali nekaj iger, po možnosti čim bolj različnih, začnemo opažati določene funkcionalnosti, ki so skupne vsem igram. Te funkcionalnosti nato združimo v celoto in tako dobimo igralni pogon.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 9
4 Arhitektura
Obstaja mnogo načinov, kako lahko posamezne sisteme igralnega pogona organiziramo. Izbrali smo trinivojsko arhitekturo, ki vsebuje aplikacijski nivo, nivo igralne logike in nivo igralnih pogledov [24, 25, 26]. Zastavljeno arhitekturo prikazuje slika 4.1. Aplikacijski nivo skrbi za komunikacijo s strojno opremo in operacijskim sistemom. Kadar bomo igro prenašali na druge platforme, bomo morali ponovno napisati večino kode v aplikacijskem nivoju. Igralna logika se ukvarja z upravljanjem stanja igre in je povsem neodvisna od strojne opreme naprave, na kateri bomo igro igrali in načina, kako je igra predstavljena igralcu. V idealnem primeru bo koda, napisana v tem nivoju, prenosljiva med platformami brez kakršnekoli spremembe. Igralni pogled je odgovoren za prikaz trenutnega stanja igre. V igri lahko imamo več igralnih pogledov in vsak lahko ima drugačno implementacijo. Glavni pogled je pogled, ki stanje igre prikazuje na prikazovalnik in predvaja zvok. Naslednji tip pogleda je pogled umetne inteligence. Tretji pa je lahko pogled omrežnega igralca v večigralskem načinu.
Aplikacijski nivo
Igralna logika
Igralni pogled
Slika 4.1: Pregled 3-nivojske arhitekture
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 10
Uporaba trinivojske arhitekture, pri kateri je igralni pogled povsem ločen od igralne logike, ni preprosta, je pa zato zelo fleksibilna. V igralni logiki so podatki, ki opisujejo vse akterje v igri (ljudi, vozila, zgradbe). Akter predstavlja osnovni gradnik scene in ga bomo podrobneje opisali v pogavjih 5 in 6.11. Igralna logika vsebuje tudi fizikalni sistem, ki skrbi za obnašanje akterjev. Vhodi v igralno logiko so povezani z glavnimi akterji, npr. premikanje glavnega lika, skok in podobno. Izhodi iz logike pa so sprememba stanj in dogodki. Pod te spada položaj akterjev, proženje raznih akcij, dogodki trkov (iz fizikalnega sistema) ipd.
Igralne poglede smo razdelili v tri kategorije: igralčev pogled, pogled umetne inteligence in mrežni pogled. Igralčev pogled predstavi stanje igre skozi igralčevo perspektivo. Izrisati mora sceno, prikazati razne vizualne učinke in predvajati zvok. Ta pogled tudi zajema podatke iz raznih vhodnih naprav (miška, tipkovnica, igralni plošček, volan, kamera, pospeškometer) in jih prevede v ukaze – dogodke, ki se pošljejo igralni logiki. Pogled umetne inteligence je nekoliko drugačen. Sprejema enake spremembe stanj in dogodke kot igralčev pogled. Na osnovi teh podatkov izračuna ukaze, ki določajo obnašanje računalniško-krmiljenih akterjev, in jih posreduje igralni logiki. Ti ukazi so lahko popolnoma enaki, kot pri igralčevem pogledu. Pri mrežnem pogledu se stanja in dogodki posredujejo oddaljenemu igralcu v omrežju. Zaradi takšne razporeditve se igralni logiki ni treba ukvarjati z vprašanjem ali igralec igra igro na lokalnem računalniku ali na oddaljeni napravi v omrežju.
4.1 Aplikacijski nivo
Vsebina aplikacijskega nivoja je razdeljena na različna področja, ki se ukvarjajo z vhodnimi in izhodnimi napravami, komunikacijo z operacijskim sistemom in življenjsko dobo igre [24]. Glavna področja, ki jih pokriva aplikacijski nivo, so:
- Vhodni sistem: Igre lahko zajemajo vhodne podatke iz velikega števila vhodnih naprav. Branje podatkov iz teh naprav je vedno odvisno od operacijskega sistema.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 11
- Upravljanje z viri: Igre lahko vsebujejo veliko količino podatkov, ki morajo biti ustrezno urejeni, da jih lahko učinkovito uporabljamo.
- Upravljanje s pomnilnikom: Upravljalnik pomnilnika je eden izmed kritičnih sistemov, kadar razvijamo veliko in kompleksno igro. Podatkovne strukture so običajno majhne in zelo dinamične, pri takšni organizaciji pa privzeti upravljalnik pomnilnika postane hitro neučinkovit zaradi fragmentacije podatkov.
- Časovnik: Sledenje času je v igrah kritično, saj na podlagi časovne informacije sinhroniziramo animacije, zvok, fiziko, in vse dinamične elemente igre.
- Upravljanje z besedili: Ločena besedila za uporabniški vmesnik olajšajo lokalizacijo iger.
- Nitenje: Izvajanje igre v več nitih je dandanes že obvezno zaradi vedno večje kompleksnosti iger.
- Mrežna komunikacija: Mrežna komunikacija je storitev, ki jo zagotavlja operacijski sistem. Skrbi za medsebojno povezovanje računalnikov.
- Skriptiranje: Večina iger uporablja skriptne jezike. Njihova uporaba pohitri razvoj in omogoča naknadno modificiranje igre.
4.2 Igralna logika
Igralna logika predstavlja glavnino igre in upravlja z vsem, kar ta igra vsebuje [24]. Tako kot aplikacijski nivo, je igralna logika razdeljena na področja:
- Podatkovne strukture: Vsaka igra ima zbirko akterjev. Preproste igre lahko imajo seznam, kompleksnejše igre pa potrebujejo bolj fleksibilno podatkovno strukturo. Ta mora biti optimizirana za hitro iskanje in razširljiva.
- Fizika in trki: Fizika definira vse od premikanja akterjev, kadar na njih deluje gravitacija, do dogajanja ob medsebojnih trkih akterjev.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 12
- Dogodki: Kadar igralna logika izvede spremembo stanja, morajo drugi sistemi reagirati. Komunikacija med njimi je zagotovljena z uporabo dogodkov. Arhitektura, ki temelji na dogodkih, se nagiba k učinkovitejšim sistemom.
- Upravljanje procesov: Izvajanje igre je običajno razdeljeno na majhne dele kode, ki se izvajajo neki določen čas. Take dele kode lahko predstavimo kot procese. Procesi se izvajajo znotraj upravitelja procesov in se sami uničijo, ko končajo z delom.
- Odzivi na ukaze: Igralna logika se mora odzivati na razne ukaze, ki jih dobiva od drugih sistemov. Ukazi so lahko odzivi na smerne tipke, kadar želi igralec premakniti svoj virtualni lik, postavitev novih akterjev ob nekem dogodku in podobno.
4.3 Igralni pogledi
Igralni pogled je skupek sistemov, ki komunicirajo z igralno logiko in predstavijo igro določenemu tipu opazovalca [24]. Ta je lahko človek, umetna inteligenca ali oddaljen sistem. Človeški pogled se mora odzivati na razne dogodke, izrisovati sceno na zaslon, predvajati zvok in opravljati še nekatere druge funkcije.
- Grafična predstavitev: Izrisovanje scene na zaslon je eden izmed procesorsko najzahtevnejših postopkov. Pri upodabljanju kompleksnih scen se moramo posluževati raznih optimizacijskih tehnik, če želimo prikazati veliko sceno z ogromno detajli. Optimizacijske tehnike zajemajo uporabo geometrije z več stopnjami podrobnosti (angl. Level of Detail – LOD) in filtriranje nevidne geometrije (angl. visible geometry culling).
- Avdio: Zvoki v igrah se običajno delijo na tri skupine: zvočni efekti, glasba in govor. Zvočni efekti so najpreprostejši, le naložimo zvok in ga predvajamo. Glasba se ne razlikuje veliko od zvočnih efektov. Najtežje pri njej je določitev ustrezne ambientne glasbe za določene dogodke v igri. Najzahtevnejša je uporaba govora, saj moramo poskrbeti še za sinhronizacijo ustnic.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 13
- Uporabniški vmesnik: Uporabniški vmesnik v igrah ima drugačen stil kot uporabniški vmesniki pri standardnih programih. Običajno ne potrebujemo kompleksnih kontrol, le gumbe, tekstovna polja in slike. Moderna orodja, kot so Iggy in Scaleform, nam omogočajo, da v igro neposredno uvozimo celoten uporabniški vmesnik, narejen s tehnologijo Flash.
Igralni pogled umetne inteligence je sestavljen iz dveh delov, s pomočjo katerih nadzira akterje. Prvi del analizira vhodne dogodke (premiki, trki, napadi). Naloga programerja je, da na tem mestu definira, kako bo umetna inteligenca reagirala na te dogodke. Drugi del je odločitveni sistem, ki je v celoti prirejen točno določeni igri.
Pri uporabi večigralstva mrežni pogled sodeluje skupaj z mrežno igralno logiko. Mrežna igralna logika je specializacija igralne logike. Od igralnega pogleda prejema dogodke in jih posreduje strežniku. Na strežniku se oddaljeni igralec prek mrežnega pogleda predstavlja enako kot agent umetne inteligence. Prejema dogodke, poslane s strani mrežne igralne logike, in jih posreduje igralni logiki.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 14
5 Organizacija akterjev
Igro sestavlja ogromno statičnih in interaktivnih objektov, ki jih med igranjem vidimo, slišimo, ali jih na kak drugačen način zaznamo. Objekti so pogosto imenovani tudi entitete, agenti ali akterji. Odločili smo se, da bomo v našem igralnem pogonu uporabljali izraz akterji.
Izbira ustrezne arhitekture za predstavitev akterjev je ključnega pomena pri izdelavi igre [24, 27]. V preteklosti so programerji za predstavitev akterjev zelo pogosto uporabljali tradicionalno hierarhijo z globokim dedovanjem razredov. Ta način postaja z večanjem števila akterjev prveč zahteven in težak za vzdrževanje. V zadnjih letih se trend nagiba proti drugim, bolj fleksibilnim arhitekturam. Ena izmed teh je arhitektura, temelječa na komponentah, ki smo jo izbrali za naš igralni pogon.
Primerjava tradicionalnega pristopa in komponentne arhitekture bo razkrila razlog, zakaj smo se osredotočili za komponente. V večini primerov je akter nek objekt v igri, ki je najverjetneje viden igralcu in se običajno lahko premika. Pri tradicionalnem pristopu ponavadi začnemo z osnovnim razredom za akterje, v katerem definiramo osnovne lastnosti, ki jih mora imeti vsak akter (kot npr. identifikator in položaj). Nadaljujemo s postopnim dodajanjem podrazredov, v katerih implementiramo dodatne funkcionalnosti. Primer takšne hierarhije prikazuje slika 5.1.
Čeprav na videz takšna razporeditev izgleda v redu, ima veliko slabih lastnosti. Z večanjem števila različnih funkcionalnosti začne graf rasti in postaja vedno težji za vzdrževanje. V primeru, da želimo imeti nekje v ozadju igre animiran objekt, ki nima nobenega vpliva na igro, potrebuje pa vizualno predstavitev, moramo imeti zraven tudi vse fizikalne lastnosti, čeprav le-teh sploh ne potrebujemo. Nekaj
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 15
podobnega bi se zgodilo, če bi želeli imeti animiran predmet, ki ga lahko poberemo. V takšnem primeru bi morali uporabiti večkratno dedovanje, ki lahko vodi do problema diamanta in podvojenih podatkov. Lahko poskušamo zgraditi drugačno drevo dedovanja, vendar nam nikoli ne bo uspelo narediti zadeve tako fleksibilne, da akterji ne bi na neki način vsebovali podatkov, ki jih v danem trenutku ne potrebujejo.
Akter
Grafika Točka pojavitve
Fizika
Animacija Predmet pobiranja
Človek Strelivo zdravje
Slika 5.1: Razporeditev razredov s tradicionalno metodo
Če dobro pogledamo graf na sliki 5.1, opazimo, da podrazredi dodajajo nove funkcionalnosti. Namesto da za dodajanje funkcionalnosti uporabimo dedovanje, se lahko reševanja problema lotimo tako, da posamezne funkcionalnosti združimo v komponente in z njimi sestavimo akterje; namesto dedovanja torej raje uporabimo kompozicijo. Tako lahko dobimo akterje z enakimi funkcionalnostmi kot s klasično arhitekturo, ki pa omogočajo veliko mero prilagodljivosti. Akter tako postane le mesto, kjer se shranjujejo komponente. Na sliki 5.2 vidimo razredni diagram, ki prikazuje komponentno usmerjeno arhitekturo. V tem modelu si akter lasti seznam komponent. Sam po sebi ne dela ničesar posebnega. Ima le identifikator, ime, seznam komponent in metode za njihovo dodajanje, odstranjevanje in dostop.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 16
Komponente so organizirane v skupine (družine) glede na skupno funkcionalnost. Komponente imajo skupen vmesnik, preko katerega komunicirajo z akterjem. Vmesnik vsebuje identifikator, ime komponente, starša in ime družine. Velikokrat morajo komponente komunicirati med seboj. Komunikacija je zagotovljena prek akterja, pri katerem kličemo metodo za dostop do posamezne komponente.
Akter Komponenta
Vmesnik fizikalne Vmesnik poberljive komponente komponente
Kvader Krogla Strelivo zdravje
Slika 5.2: Predstavitev razredov s komponentno arhitekturo
Gradnja akterja poteka v posebnih objektih imenovanih tovarne. Tovarna akterjev zgradi akterja na podlagi njegovega opisa. Pri gradnji si pomaga s tovarno komponent. Ta deluje enako kot tovarna akterjev, le da prejme opis komponente in vrne zgrajeno komponento. Komponente se po gradnji posredujejo upravitelju komponent. Njegova naloga je združevanje komponent po določenih merilih in njihovo posodabljanje.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 17
6 Implementacija
Pri implementaciji igralnega pogona smo najprej definirali seznam funkcionalnosti, ki jih bomo kasneje potrebovali za izdelavo različnih iger. Cilj je bil izdelati igralni pogon, s katerim bo mogoče izdelovati 2D igre za osebni računalnik. Ciljne igre so stransko pomikajoče ploščadne igre (angl. side-scrolling platformers), igre s pogledom iz ptičje perspektive (angl. top down view), nameri in klikni igre (angl. point and click) ter razne izpeljanke naštetih. To so igre, ki predstavljajo dosegljiv cilj za malo številčno ekipo neodvisnih razvijalcev.
Izdelava igralnega pogona je zahtevno opravilo, velikokrat preveliko za eno osebo. Obseg funkcionalnosti smo zato zelo omejili in se osredotočili samo na tiste, ki so ključne za delovanje igralnega pogona. Implementirali smo naslednje fukcionalnosti:
- ogrodje aplikacije,
- glavno zanko,
- osnovno nalaganje virov,
- osnovno risanje geometrije,
- osnovno predvajanje zvokov,
- igralno logiko,
- igralčev pogled,
- podporo uporabi skriptnega jezika,
- komunikacijo med sistemi z uporabo dogodkov in
- fizikalno simulacijo.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 18
Ker smo se odločili, da bomo naš igralni pogon ponujal podporo le za prikazovanje 2D grafike, smo lahko močno oklestili nabor funkcionalnosti. Pri implementaciji smo izpustili naslednje pomembnejše funkcionalnosti:
- napredne funkcionalnosti za delo z viri (predpomnilnik, arhivi),
- napredno upodabljanje geometrije (uporaba senčilnikov),
- večnitnost,
- upravljalnik pomnilnika,
- boljše tehnike optimiziranja upodabljanja scene in
- večja razširljivost uporabe skriptnega jezika.
Pri naboru izpuščenih funkcionalnosti smo poudarili le pomembnejše. Seznam želenih funkcionalnosti bi se lahko raztezal čez več strani, saj igralni pogon predstavlja programski produkt, ki ni nikoli končan.
Uporabili smo programski jezik C++, ki je zaradi svojih lastnosti (hitrost, nadzor nad pomnilnikom) postal zelo popularen pri razvoju iger. Produktivnost smo povečali z uporabo novosti v standardu C++11, ki ga podpira že večina prevajalnikov. Za izdelavo urejevalnika smo izbrali programski jezik C# in tehnologijo WPF, kar nam je omogočilo lažjo in hitrejšo izdelavo uporabniškega vmesnika. Uporabili smo razvojno okolje MS Visual Studio, saj je bil MS Windows glavna razvojna platforma, to pa je vplivalo tudi na izbiro knjižnic, s katerimi smo si olajšali razvoj igralnega pogona.
V nadaljevanju bomo na kratko opisali implementirane sisteme igralnega pogona. Zaporedje sistemov sledi zaporedju arhitekture. Najprej so opisani sistemi v aplikacijskem nivoju, nato sistemi v igralni logiki in na koncu igralni pogledi.
6.1 Razred GameApp
Razred GameApp je glavni razred igralnega pogona in uporabniku navzen predstavlja aplikacijski nivo. V njem se izvajajo platformsko specifične naloge,
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 19
upravljanje z življenjsko dobo aplikacije in inicializacija logike igre. Ta razred je zasnovan kot vmesnik, ki naj ga določena igra deduje in tako razširi njegovo funkcionalnost s stvarmi, ki so specifične zanjo. Med te funkcionalnosti spada inicializacija logike igre, igralnih pogledov ter začetnega stanja igre.
Znotraj inicializacije aplikacijskega nivoja se inicializirajo naslednji sistemi:
- upravitelj z viri (razred ResourceManager),
- upravitelj dogodkov (razred EventManager),
- upravitelj okna (razred WindowHandler),
- upravitelj vhodnih naprav (razred InputManager),
- grafični uporabniški vmesnik (razred GuiSystem),
- upravitelj skript (razred ScriptManager),
- sistem za upodabljanje (razred Renderer),
- igrana logika (razred GameLogic) in
- omrežni upravitelj (razred SocketManager).
Inicializacija sistemov mora potekati v točno določenem vrstnem redu, saj so nekateri sistemi odvisni od drugih. Enako je tudi z njihovo ustavitvijo. Večina sistemov je implementiranih z uporabo načrtovalskega vzorca edinec (angl. singleton). Razred GameApp skrbi za njihovo življenjsko dobo. V aplikacijskem nivoju teče tudi glavna zanka, ki skrbi za posodabljanje stanja igre. Primer specializacije razreda GameApp prikazuje naslednji odsek kode:
// aplikacija igre class SuperIgra : public GameApp { protected: // metoda za inicializacijo igralne logike, specifične za trenutno igro // metodo moramo obvezno implementirati virtual BaseGameLogic* VCreateGameLogic() override; };
BaseGameLogic* SuperIgra::VCreateGameLogic() {
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 20
// inicializacija logike m_gameLogic = new SuperIgraLogic(); m_gameLogic->Init();
// inicializacija človeškega igralnega pogleda shared_ptr
return m_gameLogic; }
6.2 Upravljanje z oknom
Upravitelj oken (razred WindowHandler) je manjši sistem v aplikacijskem nivoju, ki skrbi za funkcije, povezane z oknom aplikacije. Nabor funkcij obsega kreiranje in uničenje okna aplikacije, spreminjanje velikosti okna, zaznavo sporočil operacijskega sistema (zaustavitev sistema, spanje in spremembo zaslona) in posredovanje informacij iz vhodnih naprav (tipkovnica, miška in druge vhodne naprave) v sistem za analizo vhodnih podatkov (razred InputHandler).
Objekt nima globljega pomena. Obstaja predvsem zato, da imamo bolje organizirano kodo in smo v ta namen premaknili prej naštete funkcionalnosti iz glavnega razreda pogona v ta razred. Razred je platformsko odvisen in ga moramo v celoti na novo spisati, če želimo podpreti druge platforme.
6.3 Viri
Vire oz. resurse (angl. resources), ki jih bo igra vsebovala (teksture, zvoke), moramo nekako naložiti. Za to nalogo skrbi upravitelj virov (razred ResourceManager). Sestavljen je iz dveh glavnih delov: lokatorja virov (razred ResourceLocator) in nalagalnika virov (razred ResourceLoader).
Lokator virov skrbi za lociranje določenega vira. Razred ResourceLocator je spisan kot abstraktni razred, katerega specializacija določi način lociranja. V igralnem pogonu imamo implementiran razred FileLocator, ki locira določene datoteke na disku. Zaradi abstrakcije so lokatorji razširljivi na druge načine, npr. lokator za vire
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 21
na mrežni lokaciji ali lokator za vire iz arhiva (zip). Le novo specializacijo lokatorja moramo napisati.
Nalagalnik virov skrbi za nalaganje virov določenega tipa. Prav tako kot razred ResourceLocator, je tudi razred ResourceLoader spisan kot abstraktni razred in zanj velja enaka mera razširljivosti. Igralni pogon vsebuje nalagalnike za osnovne tipe virov (nekaj različnih tipov tekstur, wav za zvok, xml za razne podatke in lua za skripte).
Viri se po procesu nalaganja shranijo v pomnilnik in tam ostanejo na voljo drugim sistemom. Predstavljeni so z vmesnikom ResourceHandle, ta pa ima metodo, s katero iz njega pridobimo specifični tip vira.
Zahteva po viru poteka po naslednjem postopku. Upravitelj virov prejme zahtevo in preveri, ali je vir že naložen. Če je vir že naložen, vrne zahtevan vir, sicer pa nadaljuje z nalaganjem. Z uporabo lokatorja virov pridobi lokacijo vira. Nato, glede na tip zahtevanega vira, izbere ustrezen nalagalnik virov in mu posreduje lokacijo vira, da ga le-ta lahko naloži. Naložen vir se shrani v pomnilnik in vrne glede na zahtevo. Prikaz pridobitve vira je prikazan v naslednjem odseku kode:
// pridobimo vmesnik do vira z določenim imenom auto resourceHandle = ResourceManager::Get()->GetResource( Resource("ime vira"));
// vsi viri imajo skupen vmesnik za dostop do podatkov // iz vmesnika pridobimo zvok auto sound = resourceHandle->GetData
6.4 Vhodne naprave
Nabor vhodnih naprav za igranje iger je zelo velik, zato smo se izdelave vhodnega sistema lotili premišljeno. Cilj je bil ustvariti robusten vhodni sistem, katerega uporaba bi bila neodvisna od vhodne naprave [28].
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 22
Osnovni gradnik našega vhodnega sistema (razred InputManager) tako predstavlja kontekst. V njem so definirani vhodni parametri, ki so igralcu v danem trenutku na voljo. Med potekom igranja igre lahko imamo na voljo več različnih kontekstov npr. kontekst za meni in kontekst za igranje. Različne kontekste pa lahko imamo tudi med samim igranjem igre, kot je to narejeno v akcijskih igrah z vozili (GTA, Battlefield), kjer so različni konteksti za igranje peš, vožnjo z avtom, vožnjo z letalom in podobno.
Kontekst vsebuje več različnih vhodnih tipov: akcije, stanja in razpon vrednosti. V našem igralnem pogonu smo se osredotočili le na prva dva tipa. V trenutni verziji igralnega pogona nismo podprli igralnih ploščkov, zato razpon vrednosti ni igral nobene vloge. Akcija je nekaj, kar se zgodi samo enkrat, kot npr. skok, vklop stikala in podobno. Akcija se sproži samo enkrat, ne glede na to ali igralec drži ustrezno tipko dlje časa. Stanja delujejo podobno, vendar so zasnovana za neprekinjena dejanja npr. tek ali streljanje. Stanja so predstavljena z binarnim stikalom, če je stanje aktivno, se nekaj izvaja, v nasprotnem primeru pa ne. Razpon vrednosti je vhodni tip, ki vsebuje numerično vrednost. Običajno so vrednosti normalizirane na razpon od 0 do 1.
Glavni proces, ki se izvaja znotraj vhodnega sistema, je preslikava vhodnih podatkov. To je postopek, pri katerem se podatki iz vhodnih naprav preslikajo v stanja ali akcije. Pravila preslikav so definirana znotraj konteksta. Vsak kontekst ima svoja pravila, kar nam omogoča, da lahko isti gumb predstavlja različno akcijo ali stanje glede na to, v katerem stanju igre smo (kateri kontekst je v danem trenutku aktiven). Proces se začne z zbiranjem vhodnih podatkov. Vhodni podatki so običajno odvisni od platforme, zato je ta del ločen in je implementiran znotraj upravitelja oken. V njem se v vsaki iteraciji zanke zberejo vhodni podatki in se posredujejo vhodnemu sistemu. Proces se nadaljuje s preslikavo vhodnih podatkov, kjer se posameznemu vhodnemu podatku določi identifikator gumba ali tipke. S trenutno aktivnim kontekstom preslikamo identifikatorje tipk v stanja ali akcije. Aktivna stanja in akcije se zbirajo v posebnem objektu – zbiralniku. Proces preslikave je končan. Med posodobitvijo stanja igre lahko pridobimo zbiralnik in
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 23
preverimo, ali je aktivna določena akcija ali stanje. Uporaba sistema vhodnih naprav je prikazana v naslednjem odseku kode: auto mappedInput = InputManager::Get()->GetMappedInput(); if (mappedInput->IsActionActive("skok")) { // izvedemo skok } if (mappedInput->IsStateActive("deso")) { // premikamo lik v desno }
Vhodni sistem je podatkovno usmerjen. Opis kontekstov in njihovih preslikav je definiran v datoteki XML in prikazan v naslednjem izpisu:
6.5 Sistem za upodabljanje
Vizualna predstavitev igre je eden izmed njenih najpomembnejših delov. Igre brez vizualizacije skoraj ne obstajajo, saj moramo igralcu nekako predstaviti igralno
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 24
okolje. V našem igralnem pogonu smo podprli nekaj osnovnih funkcionalnosti za prikazovanje grafičnih elementov. Uporabili smo knjižnico DirectXTK [29], le-ta pa uporablja grafično knjižnico DirectX [30]. DirectXTK je knjižnica za pisanje grafičnih aplikacij z DirectX v C++.
Razred Graphics je spisan kot ovojnica čez DirectXTK. Pred uporabnikom skrije kodo za inicializacijo, brisanje in upravljanje z raznimi parametri. Razred omogoča osnovno risanje geometrije, prelepljene s teksturami, risanje črt in prikazovanje besedila. Uporaba naprednejših funkcij (senčilniki, geometrijski modeli, osvetlitveni modeli) je omejena ali pa ni podprta. Primer risanja prikazuje naslednji odsek kode:
// podatki o lokaciji objekta - kje bomo risali auto transform = m_owner->GetComponent
// reference na objekte za risanje Renderer* renderer = Renderer::Get(); auto basicEffect = renderer->GetBasicEffect(); auto primitiveRenderer = renderer->GetPrimitiveBatchTextured();
// nastavitev parametrov (m_resource - vmesnik do vira) renderer->SetLayoutPositionTexture(); basicEffect->SetWorld(transform->GetMatrix()); basicEffect->SetTexture(m_resource->GetData
// risanje (m_verts so primitive pravokotnika) primitiveRenderer->Begin(); primitiveRenderer->DrawQuad(m_verts[0], m_verts[1], m_verts[2], m_verts[3]); primitiveRenderer->End();
6.6 Dogodki
Komunikacija med posameznimi sistemi lahko hitro postane zelo kompleksna, zato mora biti že od samega začetka razvoja dobro zasnovana. Klasična komunikacija, kjer sistemi komunicirajo med seboj z uporabo klicev javnih metod, postane hitro pravo leglo hroščev, kodo pa je zelo težko vzdrževati. V našem igralnem pogonu smo se reševanja problema lotili z uporabo dogodkovnega sistema. Kadar nek sistem naredi nekaj pomembnega, sproži ustrezen dogodek. Drugi sistemi se nato ustrezno odzovejo.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 25
Dogodkovni sistem je sestavljen iz treh gradnikov, ki so: dogodki, odzivne metode in upravitelj dogodkov (razred EventManager). Vsak dogodek ima identifikator, ki predstavlja njegov tip, časovno značko, kdaj je bil prožen in podatke. Tipi dogodkov so tako decentralizirani. Ko ustvarimo nov tip dogodka, nam tako ni treba prevajati celotnega pogona (kar bi se dogajalo, če bi bili vsi tipi določeni znotraj ene strukture – enum), ampak le dele, ki ta tip dogodka uporabljajo. Primer dogodka prikazuje naslednji odsek kode:
// primer dogodka class EventActorCreate : public IEvent { public: static const EventType s_eventType; explicit EventActorCreate(); ~EventActorCreate() { } virtual const EventType& VGetEventType() const { return s_eventType; } virtual const char* VGetName() const { return "ActorCreate"; } };
// identifikator eventa – GUID const EventType EventActorCreate::s_eventType = 0x5ec34e2a;
// konstruktor EventActorCreate::EventActorCreate() { m_timeStamp = Timer::Get()->GetTime(); }
Odzivne metode predstavljajo odziv na prožen dogodek. Kot vhod prejmejo podatke proženega dogodka in z njimi izvedejo nalogo, za katero so programirane. Pri dodajanju odzivnih metod moramo zraven metode določiti tudi tip dogodka, ob katerem se bo metoda prožila. Za delovanje celotnega dogodkovnega sistema skrbi upravitelj dogodkov. Predstavljen je kot edinec, saj dostop do njega potrebujejo skoraj vsi sistemi. Njegove funkcionalnosti obsegajo dodajanje in odstranjevanje odzivnih metod, takojšnje proženje dogodka in vstavljanje dogodka v vrsto. Dogodki v vrsti se prožijo v za to namenjeni posodobitveni metodi. Ob proženju dogodka upravitelj poišče vse odzivne metode, ki so dodane pod trenutnim dogodkovnim tipom in jih kliče, ob tem pa metodam posreduje podatke iz dogodka. Prikaz uporabe odzivnih metod (definicija in dodajanje v upravitelja dogodkov) ter postopek proženja dogodka je prikazan na naslednjem odseku kode [31]:
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 26
// definicija logike class IgralnaLogika : public BaseGameLogic { public: // odzivna metoda za dogodek void OnActorCreate(IEventPtr eventData); };
IgralnaLogika::IgralnaLogika() { // dodajanje odzivnih metod EventManager::Get()->AddListener( "IL_OnActorSpawn", BIND_MEM_CB(&IgralnaLogika::OnActorCreate, this), EventActorCreate::s_eventType); }
// inicializacija dogodka shared_ptr
// proženje dogodka EventManager::Get()->TriggerEvent(dogodek);
6.7 Mrežna komunikacija
Naš igralni pogon ne podpira večigralstva, podpira pa mrežno komunikacijo. Slednja je bila ključnega pomena pri izdelavi urejevalnika nivojev. Ta je narejen iz dveh ločenih aplikacij, med katerima poteka komunikacija z uporabo vtičnic (angl. socket) in protokola TCP/IP [32]. V ta namen smo v igralnem pogonu implementirali strežnik. Mrežno komunikacijo smo razdelili v ločene korake, funkcionalnosti korakov pa smo združili v razredih, ki predstavljajo ovojnice čez funkcije vtičnic. Prvi korak je čakanje na povezavo, za katere skrbi objekt tipa SocketListener. V tem koraku vtičnico inicializiramo tako, da na določenih vratih (angl. port) posluša za dohodnimi povezavami. Drugi korak se začne, ko objekt SocketListener zazna dohodno povezavo. Takrat se ustvari objekt tipa IOSocket, ki skrbi za komunikacijo med strežnikom in odjemalcem. Upravljanje z mrežnimi objekti poteka v upravitelju vtičnikov (razred SocketManager). Ta skrbi za njihovo življenjsko dobo ter za pošiljanje in prejemanje surovih podatkov. Celoten sistem je bil zamišljen tako, da ga je možno na preprost način razširiti. To nam omogoča, da za implementacijo večigralstva ne potrebujemo veliko časa in virov.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 27
6.8 Igralna logika
Potek igre se izvaja skozi njeno logiko, kjer se izvede večina igralnih elementov in kjer se obravnava največ dogodkov. V našem igralnem pogonu se ta izvaja v za to namenjenem razredu. Osnovni del se imenuje BaseGameLogic in je namenjen, da ga pri razvijanju igre specializiramo. Razred BaseGameLogic predstavlja tudi drugi nivo našega igralnega pogona – logiko igre.
Igralna logika vsebuje tri sisteme: upravitelja procesov (razred ProcessManager), sceno (razred Scene) in fizikalni sistem (razred Physics). Upravitelj procesov skrbi za izvajanje procesov (kode, ki se izvaja čez več iteracij glavne zanke). Scena vsebuje vse akterje v trenutnem nivoju in jo bomo podrobneje predstavili v podpoglavju 6.11. Zadnji sistem je fizikalni sistem, ki skrbi za simulacijo fizike v igri. Za ta namen smo izbrali knjižnico Box2D [33], ki je zmožna simulirati toga telesa v dveh dimenzijah.
Posodabljanje stanja igre se v logiki igre izvaja v dveh ločenih postopkih. V prvem se v namenski metodi posodobijo fizika, upravitelj procesov, igralni pogledi in scena. Posodobitev teh sistemov se izvaja glede na stanje igre. Igra se v začetnem meniju posodablja drugače kot npr. med izvajanjem ugank. V drugem postopku so zajeti dogodki oz. odzivne metode na dogodke. Te moramo dodati v specializaciji razreda BaseGameLogic. Definicija specializacije razreda BaseGameLogic je prikazana v naslednjem odseku kode:
// razred za logiko urejevalnika nivojev class EditorLogic : public BaseGameLogic { private: shared_ptr
enum class SimulationState { Play, Pause, Stop };
SimulationState m_simulationState;
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 28
public: EditorLogic(); virtual ~EditorLogic();
// prekrite metode virtual void VAddView(std::shared_ptr
// odzivne metode na dogodke void ClientAttached(IEventPtr eventData); void ClientDetached(IEventPtr eventData); void OnActorCreateEmpty(IEventPtr eventData); void OnActorSelect(IEventPtr eventData); void OnLoadActors(IEventPtr eventData); // ... };
6.9 Glavna zanka
Igre se precej razlikujejo od klasičnih programov. Izvajamo jih neprekinjeno v zanki, medtem ko so drugi programi dogodkovno usmerjeni. Igre v vsaki iteraciji zanke posodabljajo svoje stanje. Obstaja veliko različnih oblik implementacije glavne zanke. V našem igralnem pogonu smo se odločili za obliko sodelovalne večopravilnosti (angl. cooperative multitasking). To je mehanizem, pri katerem vsak proces dobi za izvajanje del procesorskega časa. Osnovna enota je proces, ki vsebuje razne metode, ki se kličejo ob različnih spremembah stanja procesa (inicializacija, zaustavitev, prekinitev). Izvajanje njihove glavne metode za posodobitev je nastavljivo tako, da lahko metodo kličemo vsako iteracijo zanke ali pa na določene intervale. Procesi so namenjeni izvajanju kode, ki se bo izvajala čez več iteracij glavne zanke. Za njihovo izvajanje skrbi upravitelj procesov. Definicija procesa in njegova uporaba je prikazana na naslednjem odseku kode:
// definicija procesa class NovProces : public Process { public: // prekrite metode (VOnUpdate mora biti obvezno implementiran) void VOnInit() override; void VOnUpdate(float gameTime) override; void VOnSuccess() override; };
// inicializacija procesa shared_ptr
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 29
// dodajanje procesa v upravitelja procesov m_processManager->AttachProcess(proces);
6.10 Akterji
Akterji so sestavljeni iz komponent, kar nam omogoča veliko mero prilagodljivosti [34]. Funkcionalnosti komponente in njihove družine prikazuje slika 6.1.
Komponenta Transform je komponenta za predstavitev položaja. Vsebuje informacije o transformaciji, ki jo sestavljata koordinati x in y ter kot rotacije. Komponenta Transform se lahko poveže s komponento RigidBody. Kadar je ta prisotna v akterju, se iz nje prepisuje transformacija fizikalnega telesa. Če je akter predstavljen v fizikalnem sistemu, se informacije o transformaciji vsako iteracijo glavne zanke samodejno posodabljajo v posodobitveni metodi.
ActorComponent
RenderComponent PhysicsComponent CameraComponent
SpriteRenderComponent RigidBodyComponent TransformComponent
AnimationComponent CircleColliderComponent
BoxColliderComponent
Slika 6.1: Razporeditev implementiranih komponent (prazni kvadratki so družine)
Komponenta SpriteRender je komponenta za grafično predstavitev akterja. Z njo lahko akterja predstavimo kot sličico (angl. sprite). Komponenta vsebuje informacije o teksturi – sliki akterja, njeno velikost in geometrijo. Te podatke v fazi izrisovanja uporabi upravitelj komponent (razred ComponentManager).
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 30
Komponenta Animation je komponenta za predstavitev animacij. Podpira samo preproste animacije, ki so prestavljene z zaporedjem sličic, združenih v eno veliko sliko (angl. sprite sheet oz. texture atlas). Komponenta vsebuje podatke o teksturi, razporeditvi posameznih sličic na njej in seznam animacij. Animacije vsebujejo seznam sličic in podatke o tipu animacije (enkratna, ponavljajoča ali ping-pong) in hitrosti predvajanja. Posodabljanje animacij izvajamo znotraj komponente v posodobitveni metodi.
Komponenta RigidBody je komponenta za predstavitev fizikalnega telesa. Tesno je povezana z razredom b2Body iz knjižnice Box2D in vsebuje skoraj vse njene parametre za predstavitev togega telesa (položaj, maso, tip telesa, tip kolizije). Za predstavitev oblike telesa obstajajo komponente BoxCollider za pravokotno obliko in CircleCollider za okroglo obliko. Komponenta RigidBody in komponente Collider so v razmerju 1 : N. Tako je lahko telo sestavljeno iz več oblik. Komponente RigidBody se ob inicializaciji akterja prenesejo v fizikalni sistem, kjer ta z njihovo pomočjo upravlja življenjsko dobo objektov v fizikalnem svetu.
Komponenta Camera je komponenta za predstavitev kamere, s katero določimo vidno polje. Vidno polje predstavimo z mejnim pravokotnikom in določa območje, ki se bo zagotovo videlo na prikazovalniku. Če se razmerje stranic vidnega polja kamere in prikazovalnika ne ujema, se vidno polje poveča (prilagodi prikazovalniku).
Komponenta Script je namenjena za registracijo akterja v skriptah. Vsebuje imena registracijskih metod (konstruktor in destruktor) implementiranih v skriptah, ki se kličejo ob inicializaciji nivoja. Po registraciji je akter na voljo za uporabo v skriptah.
Akterji in njim pripadajoče komponente v celoti izkoriščajo serializacijo. Narejena je z uporabo označevalnega jezika XML. Serializirani podatki se uporabljajo pri shranjevanju in nalaganju nivojev ter za komunikacijo z urejevalnikom nivojev. Ustvarjanje akterjev poteka v upravitelju akterjev (razred ActorManager), ki prejme serializirane podatke in iz teh podatkov zgradi akterja. Upravitelj akterjev za njihovo grajenje uporablja tovarno komponent (razred ComponentFactory). Slednja iz
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 31
serializiranih podatkov zgradi posamezne komponente in jih doda v akterja. Med gradnjo akterja se ustvarjene komponente posredujejo upravitelju komponent (razred ComponentManager), kjer se razvrščajo v ustrezne sezname. Primer akterja v serializirani obliki je prikazan v naslednjem odseku kode:
6.11 Scena
Z razvojem strojne opreme napreduje tudi razvoj vizualnih efektov, ki jih zmore strojna oprema izrisati. Razvijalci iger želijo uporabiti čim več vizualnih učinkov, da bo igra bila videti lepše, zato pogosto ne moremo hkrati risati vseh objektov v igri z naivnim pristopom. Grafični objekti morajo biti dobro organizirani, da lahko v določenem trenutku prikažemo samo tiste, ki imajo vpliv (so v vidnem območju). Za ta namen imajo igralni pogoni upravitelja scen (angl. scene manager ali scene graph).
V našem igralnem pogonu je naloga upravitelja scen (razred Scene) razdeljena med dva sistema: scena in upravitelj komponent. Scena predstavlja nivo oz. svet, v katerem so zbrani akterji. Podpira funkcionalnosti, kot so shranjevanje in nalaganje scene ter dodajanje, brisanje in iskanje akterjev. Za posodabljanje in risanje akterjev skrbi upravitelj komponent. Kadar se ustvari nov akter, se njegove komponente
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 32
posredujejo upravitelju komponent, ki vsebuje dva seznama. V prvem se hranijo posodobitvene komponente, v drugem pa grafične. Posodabljanje, ki se izvede vsako iteracijo zanke, poteka tako, da se kliče posodobitvena metoda vsake posamezne komponente iz seznama. Postopek izrisovanja je bolj zapleten, saj uporabljamo postopek izločanja vidne geometrije. Postopek je prirejen za risanje 2D geometrije, pri katerem uporabljamo sloje. Za vsako grafično komponento preverimo, ali je vidna na zaslonu. Pri tem uporabljamo preverjanje prekrivanja osno poravnanih mejnih pravokotnikov (angl. Axis Aligned Bounding Box – AABB). Če grafična komponenta prestane test (je vidna na prikazovalniku), se doda v vrsto, kjer poteka urejanje po slojih. Urejanje nam omogoča lažje zaporedno risanje, pri katerem se najprej narišejo akterji v ozadju in nato akterji v ospredju. Posamezni sloji imajo tudi parameter vidnosti, s katerim določimo vidnost vseh akterjev na tistem sloju.
6.12 Igralni pogledi
Igralni pogled (razred GameView) predstavlja tretji nivo naše arhitekture igralnega pogona. Ukvarja se s predstavitvijo igre. Obstaja več izpeljank pogledov. Igralčev pogled (razred HumanView) je glavni predstavnik igralnih pogledov in skrbi za predstavitev igre uporabniku (igralcu). V njem opravimo postopek izrisovanja scene in prikaza uporabniškega vmesnika na prikazovalnik ter posodabljanje zvočnega sistema. Druge izpeljanke pogledov so AIView, ki se ukvarja z umetno inteligenco in NetworkView, ki se ukvarja z večigralstvom. Slednjih dveh pogledov naš igralni pogon še ne podpira, vendar je pripravljen na njune razširitve.
6.13 Avdio sistem
Razred AudioSystem je sistem, ki skrbi za zvok. Pri tem sistemu smo uporabili del knjižnice DirectXTK. V ozadju ta knjižnica uporablja XAudio [35], microsoftovo knjižnico za zvok, ki zelo poenostavi delo z zvočnimi vsebinami. Dostop do tega sistema smo potrebovali v drugih sistemih, zato je postal edinec.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 33
Sestavljajo ga glavni sistem, razred za zvok in nalagalnik zvokov (razred SoundLoader). Glavni sistem skrbi za inicializacijo in zaustavitev zvočnega sistema iz knjižnice. Za lažje predvajanje efektov posluša tudi za dogodek »OnPlaySound«, preko katerega prejme ime datoteke zvočnega efekta. Razred za zvok predstavlja zvočni vir in vsebuje zvočni efekt iz knjižnice DirectXTK. Nalagalnik zvokov poskrbi za nalaganje zvočnih datotek v pomnilnik. Predvajanje zvoka je prikazano v naslednjem odseku kode:
// pridobimo vmesnik do vira z določenim imenom auto soundHandle = ResourceManager::Get()->GetResource(Resource("sound.wav"));
// iz vmesnika pridobimo zvok auto sound = soundHandle->GetData
// predvajamo zvok sound->effect->Play();
6.14 Grafični uporabniški vmesnik
Predstavitev grafičnega uporabniškega vmesnika predstavlja povsem drugačen izziv kot predstavitev scene. Za ta namen skrbi upravitelj GUI (razred GUISystem) [36]. Implementiranih imamo nekaj osnovnih elementov: besedila, slike, gumbe in potrditvena polja. Pri gradnji uporabniškega vmesnika elemente postavljamo v razporeditve (angl. layout). Razporeditev lahko imamo poljubno mnogo in jih lahko menjujemo glede na stanje igre, npr. razporeditev gumbov v glavnem meniju ipd. Postavitev elementov je fleksibilna in jo lahko nastavimo z uporabo naslednjih parametrov:
- položaj (piksli ali odstotki),
- odmik od položaja v pikslih,
- izvor položaja (v kotih ali središču elementa),
- dimenzija (piksli ali odstotki) in
- razmerje stranic.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 34
Za določanje položaja uporabljamo relativne koordinate glede na položaj starša. Če starš ni definiran, uporabimo levi zgornji kot okna. Pri določitvi dimenzij ali položaja v odstotkih uporabimo ustrezen delež dimenzije starša. Če starš ni definiran, privzamemo dimenzije okna. Primer grafičnega uporabniškega vmesnika z gumbom in besedilom je prikazan na naslednjem odseku kode:
// ustvarimo gumb auto gumb = GUISystem::Get()->CreateElement(GUIElementType::Button); gumb->position.position = Vec2(0, 0); gumb->dimension.dimension = Vec2(100, 30); static_cast
// ustvarimo besedilo auto besedilo = GUISystem::Get()->CreateElement(GUIElementType::Label); besedilo->position.position = Vec2(0, 0); besedilo->dimension.dimension = Vec2(100, 30); static_cast
// dodamo oba v gui GUISystem::Get()->AddElement(gumb); GUISystem::Get()->AddElement(besedilo);
6.15 Podpora uporabi skriptnega jezika
Skozi zgodovino igre postajajo vedno bolj podatkovno usmerjene. Lastnosti akterjev, razni parametri in logika se lahko spreminjajo, ne da bi bilo treba prevesti in zgraditi celotno igro. Za tolikšno mero fleksibilnosti so zaslužni skriptni jeziki. Omogočajo nam hitro iteracijo razvojnega procesa vendar za ceno večje porabe pomnilnika in procesorske moči. Zaradi tega je priporočljivo kritične dele logike igre (deli, pri katerih je hitrost obdelave pomembna) razviti s splošnonamenskim programskim jezikom, ostalo pa s skriptnim jezikom. Uporaba skriptnega jezika nam omogoča, da v razvoj programske kode igre vključimo tudi osebe, ki niso tako vešče v programiranju, še posebej takrat, ko uporabljamo vizualni skriptni jezik.
Na trgu obstaja veliko že implementiranih skriptnih jezikov, primernih za vključitev v igre ali igralne pogone. Med njimi najdemo Lua [37], Python [38], C#, Boo [39], AngelScript [40], itd. Če noben obstoječi jezik ne ustreza našim merilom, pride v poštev razvoj lastnega, vendar je to le v redkih primerih stroškovno učinkovit način.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 35
Odločili smo se, da bomo v našem igralnem pogonu dodali podporo pisanju skript z jezikom Lua. Skriptni jezik Lua [37] je zelo popularna izbira pri igralnih pogonih, dobro je dokumentiran, njegova uporaba pa je preprosta. Uporabili smo knjižnico LuaPlus [41], ki v primerjavi z osnovno verzijo knjižnice Lua zelo poenostavi postopek izpostavljanja funkcij in razredov, napisanih v programskem jeziku C++, v skriptni jezik.
Za zagon skriptnega sistema, izvrševanje skript in upravljanje s skriptnimi komponentami, skrbi upravitelj skript (razred ScriptManager). Med njegovo inicializacijo se v skriptni jezik izpostavijo vsi sistemi, ki so ključni za uporabo funkcij igralnega pogona v skriptah. V Lua so vse spremenljivke predstavljene s tabelami. Zaradi tega moramo uporabiti posredniške metode in razrede (ovojnice), saj velikokrat neposredna preslikava funkcij ni mogoča. Implementirati smo morali naslednje sisteme:
- celoten dogodkovni sistem, ki nam omogoča sprejemanje in pošiljanje dogodkov,
- razred ScriptProcess, s katerim imamo predstavljeno glavno zanko,
- razred Actor, ki zajema osnovne operacije nad akterji (premik, rotacija, pridobivanje komponent),
- komponente (RigidBody in Animation),
- celoten sistem za izdelavo grafičnega uporabniškega vmesnika in
- razred Sound, ovojnico čez razred za predvajanje zvoka.
Primer funkcije, definirane v skripti, je prikazan v naslednjem odseku kode: function PlayerProcess:PlayerInput() if inputDirection ~= 0 then self:MoveActor(inputDirection) if inputDirection ~= self.animationSide then self.animationSide = inputDirection self.animationSwitch = true end end if Input:IsStateBeginActive("jump") and self.isGrounded then
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 36
self.isJumping = true self.isIdle = false PlaySound(self.soundJump) end -- ... end
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 37
7 Urejevalnik
Zamislimo si naslednji scenarij: precej časa smo že posvetili izdelavi igralnega pogona, zraven pa imamo narejen prototip igre. Prišli smo do točke, na kateri lahko začnemo izdelovati različne nivoje. Glavni problem pa je opis nivoja v tekstovni obliki. Tak opis je delno prikazan v poglavju 6.11 (prikaz akterja v obliki XML). Lahko si samo predstavljamo, kako mučen in časovno potraten bi bil postopek gradnje nivoja, če bi morali vse nivoje zapisati ročno. Pri izdelavi igre je lahko izdelava njene vsebine eden izmed najbolj časovno-potratnih postopkov. Zadevo omilimo tako, da ustvarimo orodja za izdelavo vsebine. Glavni predstavnik te skupine je urejevalnik svetov oziroma nivojev [42].
Najpomembnejša prednost uporabe urejevalnika je njegova priročnost. Deluje po principu WYSIWYG – kar vidimo v urejevalniku, bomo kasneje videli v igri. Naslednja prednosti je, da nam omogoča hitrejšo in učinkovitejšo iteracijo razvojnega procesa. Sestavimo nivo in ga preizkusimo. Če kaj ne deluje, hitro popravimo in preizkusimo še enkrat. Pomemben dejavnik uporabe urejevalnika je tudi ta, da naredi načrtovanje in izdelavo nivojev manj tehnično. Načrtovalci se lahko osredotočijo na njihovo glavno nalogo (izdelavo nivojev) in se jim ni potrebno ukvarjati s predstavitvijo vsebine v igralnem pogonu.
Pri razvoju urejevalnika se je modro držati naslednjih napotkov:
Ponovna uporaba kode: Kadar začnemo z izdelavo urejevalnika, imamo že izdelan igralni pogon. Nima smisla izdelovati drugega pogona za potrebe urejevalnika, če imamo že izdelanega in ga lahko uporabimo.
Učinkovitost pred estetiko: V večini izdelujemo urejevalnik zase ali za svojo ekipo, zato vizualni izgled nima velike vloge (razen v primeru, ko bomo igralni
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 38
pogon in urejevalnik prodajali). Osredotočimo se raje na njegove funkcionalnosti, da bomo čim prej lahko z njim učinkovito delali.
Specifičnost: Urejevalnik je namenjen za igro, ki jo izdelujemo. Nekatere funkcionalnosti, ki so specifične za igro, lahko neposredno vgradimo v urejevalnik.
Prijaznost do uporabnika: Pri uporabi aplikacij smo navajeni določenih funkcionalnosti (razveljavi, povrni, kopiraj, prilepi …). Implementacija teh običajno ne zahteva veliko dodatnega dela, njihova uporaba pa nam močno olajša delo z urejevalnikom. Prav tako nam olajša delo preprosto testiranje nivojev. Naj bo to oddaljeno le en miškin klik.
V grobem obstajata dva pristopa k implementaciji urejevalnika nivojev. Lahko ga imamo vgrajenega v igro ali pa kot povsem ločeno aplikacijo. Oba pristopa imata dobre in slabe lastnosti. Vgrajeni urejevalnik povsem zabriše mejo med načrtovanjem in testiranjem, vendar lahko zahteva veliko dodatnega dela z grafičnim uporabniškim vmesnikom, saj moramo dodati kup naprednejših gradnikov. Ločen urejevalnik je lahko izdelan s povsem drugačnim programskim jezikom in knjižnicami, kar lahko zelo olajša razvojni postopek. Testni cikel pa je v tem primeru daljši zaradi preklapljanja med aplikacijami, problematična pa je lahko tudi grafična predstavitev nivoja. Ta postane težavna pri mešanju programskih jezikov.
Pri razvoju urejevalnika nivojev smo se odločili za mešanico obeh pristopov. Izdelali smo dve aplikaciji, ki sta lahko na videz videti kot ena. Prva skrbi za grafično predstavitev nivoja in je narejena kot posebej prilagojena igra, druga pa predstavlja uporabniški vmesnik urejevalnika (slika 7.1). Za tak pristop smo se odločili iz več razlogov.
- Za vizualizacijo nivoja smo lahko uporabili obstoječ igralni pogon.
- Uporabniški vmesnik urejevalnika smo lahko izdelali v programerju prijaznejšem programskem jeziku, kar je prispevalo k prihranku časa.
- Vizualizacija igre lahko teče na povsem ločeni napravi.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 39
Prednost tega se pokaže, kadar razvijamo igro za konzole ali mobilne naprave, kjer lahko igro urejamo in preizkušamo neposredno na napravi. Slabost takšnega pristopa pa je potreba po komunikaciji med aplikacijama, saj moramo ponavadi to funkcionalost dodatno implementirati.
Slika 7.1: a) vizualizacijsko okno in b) uporabniški vmesnik urejevalnika
Grafični del aplikacije smo naredili v stilu urejevalnika, vgrajenega v igro. Uporabili smo igralni pogon in ga prilagodili za potrebe funkcij urejevalnika. Največ sprememb je bila deležna logika urejevalnika (specializacija igralne logike – razreda BaseGameLogic) in pogled urejevalnika (specializacija človeškega pogleda – razreda HumanView). Logika urejevalnika vsebuje odzivne metode raznih dogodkov, od katerih večina je namenjena komunikaciji, ki jo bomo podrobneje opisali v nadaljevanju. Znotraj pogleda urejevalnika pa imamo večji nadzor nad premikanjem kamere in označevalni gradnik. Označevalni gradnik je vizualni objekt, ki nam prikazuje, kateri akter je trenutno označen. Z njim lahko premikamo in obračamo izbrane akterje.
Uporabniški vmesnik urejevalnika je narejen v programskem jeziku C# z uporabo tehnologije WPF. Razdeljen je na menijsko in orodno vrstico, raziskovalca akterjev, raziskovalca komponent, okna za vizualizacijo, seznam akterjev, raziskovalec virov in seznam slojev (slika 7.2).
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 40
Slika 7.2: a) menijska in orodna vrstica, b) raziskovalec akterjev, c) raziskovalec komponent, d) vizualizacijsko okno in e) seznam akterjev, virov in slojev
Funkcionalnosti urejevalnika smo razdelili na več skupin:
- Upravljanje z nivojem: V to skupino spada kreiranje, shranjevanje in nalaganje nivoja ter spreminjanje nastavitev. Med nastavitvami najdemo gravitacijo, zagonske skripte in enote, prikazane na sliki 7.3. Pri gravitaciji lahko nastavimo njeno začetno vrednost ob inicializaciji nivoja. Zagonske skripte so sestavljene iz dveh delov. Prvi del oz. prva skripta se izvede pred nalaganjem akterjev, druga pa po končanem nalaganju akterjev. Igralni pogon uporablja lastne enote. Nastavljamo lahko pretvorbena razmerja. MeterToUnit je namenjen za pretvorbo osnovnih enot v enote fizikalnega pogona (Box2D uporablja metrične enote). PixelToUnit je namenjen za pretvorbo dimenzij slik (v pikslih) v osnovne enote.
- Raziskovalec akterjev in komponent: Vrhnji del raziskovalca akterjev vsebuje osnovne lastnosti akterja, med katere spadajo ime, oznaka ter sloj, v katerem se akter nahaja. Spodnji del pa je namenjen upravljanju s komponentami. Komponente lahko dodajamo v in odstranjujemo iz akterja.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 41
Nekatere komponente, kot so Script in RigidBody, lahko imamo v akterju le enkrat, druge pa večkrat. Raziskovalec komponent prikazuje njihove lastnosti, ki jih lahko urejamo.
Slika 7.3: Nastavitev parametrov scene
- Seznam akterjev, slojev in raziskovalec virov: Seznam akterjev prikazuje akterje (njihova imena), ki so v trenutnem nivoju. Sloje lahko v njihovem seznamu dodajamo, brišemo, urejamo, ali pa spreminjamo njihovo zaporedje. Raziskovalec virov nam prikazuje vse vire, ki se nahajajo v določeni mapi. Za njihovo organizacijo moramo poskrbeti sami.
- Okno za vizualizacijo: Za vizualizacijo skrbi ločeno okno, vendar imamo v grafičnem vmesniku prostor, v katerega lahko ujamemo vizualizacijsko okno. Tako je na videz videti kot ena aplikacija, vendar se opazi razlika pri fokusu. Če imamo fokus na vizualizacijskem oknu, ga nimamo na grafičnem vmesniku in obratno. Okno ujamemo s pomočjo gostiteljskega okna. V oknu imamo prikazan seznam procesov. Ko izberemo ustrezen proces iz seznama, ga ujamemo v aplikacijo uporabniškega vmesnika.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 42
Komunikacija med aplikacijama je narejena z uporabo vtičnic. Vtičnica je začetna ali končna točka v komunikacijskem toku skozi računalniško omrežje. Med aplikacijama prenašamo podatke v zapisu XML, kar ni učinkovito, vendar nam je olajšalo iskanje napak. Uporabili smo mrežno arhitekturo strežnik – odjemalec. Vizualizacijska aplikacija je strežnik, uporabniški vmesnik pa odjemalec. Skoraj ves promet poteka v smeri od odjemalca do strežnika, le posodabljanje položajev akterjev (ko akterje s pomočjo označevalnega gradnika premikamo po nivoju) poteka v obratni smeri. Podatki, ki se prenašajo med aplikacijama, so zbrani v tabeli 7.1.
Tabela 7.1: Podatki, ki se prenašajo med aplikacijama
Ime akcije Podatki Naloži akterje Seznam akterjev Ponovno ustvari akterja Seznam akterjev Posodobi položaj akterja Seznam akterjev Ustvari novega akterja Akterjev identifikator in ime Izberi akterje Seznam identifikatorjev akterjev Odstrani akterje Seznam identifikatorjev akterjev Počisti sceno Brez Spremeni stanje simulacije Stanje simulacije, inicializacijske skripte in seznam akterjev Spremeni lastnosti scene Inicializacijske skripte, gravitacijski vektor in pretvorniki enot Spremeni zaporedje slojev Seznam identifikatorjev slojev Spremeni vidnost slojev Identifikator slojev in vidnost
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 43
8 Uporaba v praksi
Funkcionalnosti igralnega pogona smo preizkusili z izdelavo igre. Odločili smo se, da bomo izdelali novo različico (angl. remake) igre, ki smo jo izdelali v okviru dogodka Global Game Jam 2014 s pomočjo igralnega pogona Unity. Razlog je bil ta, da smo že dodobra seznanjeni z mehaniko te igre, prav tako pa imamo na voljo tudi vse vire. Igra se imenuje StateShift in je stransko pomikajoča ploščadna igra, v kateri lahko lastnosti sveta prilagajamo lastnim potrebam (slika 8.1). Glavna mehanika je spreminjanje gravitacije sveta, da dosežemo portal na koncu nivoja. Med potjo do portala moramo rešiti uganko in se izogniti nevarnim objektom.
Slika 8.1: Igra StateShift, narejena na Global Game Jamu 2014
Izdelava igre se je začela z razširitvijo igralnega pogona. Ustvarili smo razreda StateShift in StateShiftLogic. Razred StateShift predstavlja ogrodje igre, saj specializira razred GameApp. Razred StateShiftLogic specializira razred BaseGameLogic in predstavlja logiko igre. Znotraj igralne logike imamo definirane specifične dogodke in odzivne metode, zbrane v tabeli 8.1.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 44
Tabela 8.1: Dogodki v igri
Ime dogodka Podatki Namen LoadLevel Ime Naloži nivo nivoja QuitGame Brez Zapre igro UnloadGame Brez Počisti sceno CompleteLevel Brez Končanje nivoja – nalaganje naslednjega nivoja ali prikaz konca igre Death Brez Ponastavi položaj igralca v primeru smrti
Pri razvoju igre smo v večini uporabljali skriptni jezik. To nam je omogočalo hitro prototipiranje in prikaz moči igralnega pogona. Razvoj smo nadaljevali z izdelavo glavnega lika in igralne mehanike. Glavni lik je sestavljen iz različnih komponent (slika 8.2). Komponenta RigidBody predstavi lik v fizikalnem svetu. Njegovo telo je predstavljeno z okroglim trkalnikom v komponenti CircleCollider. Pravokotne trkalnike iz komponent BoxCollider smo uporabili za pomožno logiko premikanja. Komponenta Script registrira akterja v skriptah. Za njegovo grafično predstavitev poskrbijo animacije iz komponente Animation. Komponenta Camera pa zagotavlja sledenje kamere.
Slika 8.2: Prikaz sestave igralčevega glavnega lika
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 45
Nadaljevali smo z implementacijo igralne mehanike v skriptnem jeziku. Da lahko akterja uporabimo v skriptah, ga moramo najprej registrirati, kot je prikazano v naslednjem odseku kode: function AddPlayer(scriptObject) playerWorker = PlayerProcess:Create({player = scriptObject}) AttachProcess(playerWorker) end
Znotraj te metode ustvarimo proces PlayerProcess za nadzor nad igralčevim akterjem. PlayerProcess je razširitev osnovnega procesa, opisanega v podpoglavju 6.9. Implementirano ima inicializacijsko in posodobitveno metodo. Znotraj posodobitvene metode poteka upravljanje glavnega lika, prikazano v naslednjem odseku kode: local inputDirection = 0 if Input:IsStateActive("left") then inputDirection = -1 elseif Input:IsStateActive("right") then inputDirection = 1 end local currentVelocity = self.rigidBody:GetLinearVelocity() if inputDirection ~= 0 then if currentVelocity.x * inputDirection < self.maxSpeed then self.rigidBody:ApplyForce( {x = self.moveForce * inputDirection, y = 0}, {x = 0, y = 0}) end if currentVelocity.x * inputDirection > self.maxSpeed then self.rigidBody:SetLinearVelocity( {x = self.maxSpeed * inputDirection, y = currentVelocity.y}) end end if Input:IsStateActive("jump") then self.rigidBody:ApplyForce( {x = 0, y = self.jumpForce}, {x = 0, y = 0}) end
Pri premikanju glavnega lika preverjamo, če je stanje »left« ali »right« aktivno. V pozitivnem primeru preverimo, ali še lik ni dosegel maksimalne hitrosti in nanj delujemo z določeno silo. Skok deluje po enakem principu. Postopek spreminjanja
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 46
gravitacije poteka podobno kot premikanje. Trenutno gravitacijo imamo shranjeno v spremenljivki in jo ob pritisku ustrezne tipke za »rotateGravityLeft« ali »rotateGravityRight« zavrtimo za 90 stopinj v ustrezno smer. Spremenjen vektor gravitacije posredujemo fizikalnemu pogonu. Nadzor nad gravitacijo prikazuje naslednji odsek kode: if Input:IsActionActive("rotateGravityLeft") then self.gravity:rotate(-90) Physics:SetGravity(self.gravity) end if Input:IsActionActive("rotateGravityRight") then self.gravity:rotate(90) Physics:SetGravity(self.gravity) end
Premikanje lika in spreminjanje gravitacije v prejšnjih odsekih kode je posplošena verzija, ki deluje v izoliranih okoliščinah. V končni verziji smo pri premikanju in skoku morali upoštevati še smer gravitacije. Znotraj posodobitvene metode se nahaja tudi nadzor nad animacijami. Imamo 3 različne animacije: prosto, skok in tek (slika 8.3). Sprememba animacij se proži v določenih trenutkih z uporabo metode »SetAnimation« npr. ob pritisku tipke za premik levo, desno, skok ali pri doskoku. Animacije so narejene za premik v desno, zato jih pri premiku v levo zrcalimo čez os y. Upravljanje z animacijami je prikazano v naslednjem odseku kode:
-- določitev animacije self.animation:SetAnimation("run")
-- zrcaljenje animacije self.animation:SetFlipX(true)
Slika 8.3: Animacije glavnega lika (prva vrsta: prosto, druga: tek, tretja: skok)
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 47
Nivoji so v grobem sestavljeni iz igralčevega lika, vizualnih blokov, portala in fizikalne geometrije. Akterji vizualnih blokov so sestavljeni le iz komponent SpriteRender. Interakcija z njimi je predstavljena z ločenim akterjem, ki vsebuje fizikalne komponente (RigidBody in BoxCollider). Portal je sestavljen iz komponente SpriteRender in fizikalnih komponent, ki v tem primeru predstavljajo senzor, ki se proži ob stiku z glavnim likom.
Uporabniški vmesnik igre je v celoti izdelan v skriptnem jeziku. Postavitev elementov je potekalo ročno, ker urejevalnik zaenkrat še ne ne podpira urejanja elementov uporabniškega vmesnika. Sestavili smo uporabniške vmesnike za glavni meni, izbiro nivojev, zmago nivoja, smrt v nivoju in zmago igre (slika 8.4).
Slika 8.4: Meniji igre: a) glavni meni, b) izbira nivojev, c) meni ob končanju nivoja, d) konec igre
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 48
9 Sklep
V magistrski nalogi smo predstavili izdelavo igralnega pogona in njegovo uporabo pri izdelavi preproste 2D igre. Uporabili smo enega izmed predstavljenih pristopov, pri katerem smo vnaprej spisali nabor funkcionalnosti in jih kasneje implementirali. Implementacija igralnega pogona se je izkazala kot uspešna, saj smo z njim brez težav in v kratkem času izdelali preprosto 2D stransko pomično ploščadno igro. Njegova fleksibilnost nam omogoča izdelavo 2D iger iz drugih žanrov, le z manjšimi posegi v izvorno kodo.
Igralni pogoni spadajo med programske produkte, katerih razvoj se nikoli ne konča. Implementirali smo le najnujnejše stvari in omenili le peščico funkcionalnosti, ki jih nismo vključili. Zraven tega se na vsakoletnih konferencah (Game Developers Conference in Siggraph) predstavljajo novi pristopi reševanja raznih problemov, ki so potencialni kandidati za vključitev v igralne pogone.
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 49
10 Literatura
[1]. Gregory J. Game Engine Architecture. Taylor and Francis Group, LLC. 2009.
[2]. Unreal Engine Technology. Dostopno na: https://www.unrealengine.com. [23. 11. 2014]
[3]. CryENGINE. Dostopno na: http://www.crytek.com/cryengine. [23. 11. 2014]
[4]. Source Game Engine. Dostopno na: http://en.wikipedia.org/wiki/Source_%28game_engine%29. [23. 11. 2014]
[5]. Unity Game Engine. Dostopno na: http://unity3d.com. [23. 11. 2014]
[6]. id Tech. Dostopno na: http://en.wikipedia.org/wiki/Id_Tech. [23. 11. 2014]
[7]. Havok Vision Engine. Dostopno na: http://www.havok.com/products/vision-engine. [23. 11. 2014]
[8]. Frostbite Game Engine. Dostopno na: http://www.frostbite.com. [23. 11. 2014]
[9]. Leadwerks Game Engine. Dostopno na: http://www.leadwerks.com/werkspace/page/home. [23. 11. 2014]
[10]. Torque 3D. Dostopno na: http://garagegames.com/products/torque-3d. [23. 11. 2014]
[11]. ShiVa 3D Game Engine with development tools. Dostopno na: http://www.stonetrip.com. [23. 11. 2014]
[12]. SAGE Game Engine. Dostopno na: http://en.wikipedia.org/wiki/SAGE_%28game_engine%29. [23. 11. 2014]
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 50
[13]. Ignite Game Engine. Dostopno na: http://www.easports.com/ignite. [23. 11. 2014]
[14]. EGO Game Engine. Dostopno na: http://en.wikipedia.org/wiki/EGO_%28game_engine%29. [23. 11. 2014]
[15]. GameMaker Studio. Dostopno na: https://www.yoyogames.com/studio. [23. 11. 2014]
[16]. Project Anarchy. Dostopno na: http://www.projectanarchy.com. [23. 11. 2014]
[17]. AGK App Game Kit. Dostopno na: http://www.appgamekit.com. [23. 11. 2014]
[18]. Marmalade SDK. Dostopno na: https://www.madewithmarmalade.com. [23. 11. 2014]
[19]. Cocos2d-x. Dostopno na: http://www.cocos2d-x.org. [23. 11. 2014]
[20]. GameSalad. Dostopno na: http://gamesalad.com. [23. 11. 2014]
[21]. Construct 2. Dostopno na: https://www.scirra.com/construct2. [23. 11. 2014]
[22]. Create a Game Engine. Dostopno na: http://www.gamedev.net/topic/633334-create-a-game-engine [23. 11. 2014]
[23]. Make Games, Not Engines, But how? Dostopno na: http://www.gamedev.net/topic/627759-make-games-not-engines-but-how/ [23. 11. 2014]
[24]. McShaffry M., Graham D. Game Coding Complete, 4. izdaja. Course Technology. 2013
[25]. Gold J. Object-oriented Game Development. Pearson Education Limited. 2004
[26]. Llopis N. C++ for Game Programmers. Charles River Media. 2003
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 51
[27]. Dickheiser M. Game Programming Gems 6. Charles River Media. 2006
[28]. Designing a Robust Input Handling System for Games. Dostopno na: http://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input- handling-system-for-games/ [23. 11. 2014]
[29]. DirectX Tool Kit domača stran. Dostopno na: https://directxtk.codeplex.com/ [23. 11. 2014]
[30]. Grafična knjižnica DirectX. Dostopno na http://en.wikipedia.org/wiki/DirectX[23. 11. 2014]
[31]. Lightweight Generic C++ Callbacks. Dostopno na: http://www.codeproject.com/Articles/136799/Lightweight-Generic-C- Callbacks-or-Yet-Another-Del [23. 11. 2014]
[32]. Winsock Networking Tutorial. Dostopno na: http://www.madwizard.org/programming/tutorials/netcpp/ [23. 11. 2014]
[33]. Box2D - A 2D Physics Engine for Games. Dostopno na: http://box2d.org/ [23. 11. 2014]
[34]. Elephant Game Framework. Dostopno na: http://elephant.codeplex.com/ [23. 11. 2014]
[35]. XAudio2 Introduction. Dostopno na: http://msdn.microsoft.com/en- us/library/windows/desktop/ee415813%28v=vs.85%29.aspx [23. 11. 2014]
[36]. Creating a Very Simple GUI System for Small Games. Dostopno na: http://www.gamedev.net/page/resources/_/technical/game- programming/creating-a-very-simple-gui-system-for-small-games-part-i- r3652 [23. 11. 2014]
[37]. The Programming Language Lua. Dostopno na: http://www.lua.org/ [23. 11. 2014]
[38]. Python C API. Dostopno na: https://docs.python.org/2/c-api. [23. 11. 2014]
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 52
[39]. Scripting with Mono. Dostopno na: http://www.mono- project.com/docs/advanced/embedding/scripting/. [23. 11. 2014]
[40]. AngelScript. Dostopno na: http://www.angelcode.com/angelscript/. [23. 11. 2014]
[41]. LuaPlus. Dostopno na: http://luaplus.org/ [23. 11. 2014]
[42]. Make Your Life Easier: Build a Level Editor. Dostopno na: http://gamedevelopment.tutsplus.com/articles/make-your-life-easier-build- a-level-editor--gamedev-356 [23. 11. 2014]
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 53
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 54
Izdelava igralnega pogona in urejevalnika za izdelavo 2D igrer 55