MREŽNA ARHITEKTURA POGONOV UNREAL

Velikost: px
Začni prikazovanje s strani:

Download "MREŽNA ARHITEKTURA POGONOV UNREAL"

Transkripcija

1 Matjaž Škorjanc MREŽNA ARHITEKTURA POGONOV UNREAL Diplomsko delo Maribor, september 2011

2 I Diplomsko delo univerzitetnega strokovnega študijskega programa MREŽNA ARHITEKTURA POGONOV UNREAL Študent: Matjaž Škorjanc Študijski program: Univerzitetni, Računalništvo in informacijske tehnologije Mentor: doc. dr. Simon Kolmanič Somentor: red. prof. dr. Nikola Guid

3 II

4 III ZAHVALA Zahvaljujem se mentorju za pomoč in vodenje pri opravljanju diplomskega dela. Hvala tudi Republiki Sloveniji za financiranje študija in študentskega statusa. Posebna zahvala velja staršem, ki so mi omogočili izobraževanje.

5 IV MREŽNA ARHITEKTURA POGONOV UNREAL Ključne besede: računalniške igre, mrežne igre, večigralstvo, pogon Unreal UDK: :004.92(043.2) Povzetek V svetu računalniških iger že dolgo časa prevladuje mrežno igranje z dvema ali več igralci hkrati. Učinkovitost, zanesljivost in dobro delovanje teh iger lahko dosežemo z uporabo primernega igralnega pogona ter s pravilnim razumevanjem konceptov programiranja mrežne podpore. To diplomsko delo razloži princip delovanja mrežne arhitekture pogona Unreal, osnovne parametre uporabljenih primitivov, sintakso in semantiko programiranja mrežne podpore in predstavi primer potezne igre Kamen s podporo za dva igralca.

6 V UNREAL NETWORKING ARHITECTURE Key words: computer games, online games, multiplayer, Unreal engine UDK: :004.92(043.2) Abstract Online games have been a dominant category of computer games for a long time already. Reliability, efficiency and good working of online games can be achieved by carefully choosing suitable game engine and correct understanding of programming concepts related to online gaming. This document explains basic concepts of Unreal networking, basic parameters of primitives, syntax and semantics for programming network support and presents simple turn based multiplayer game named Stone.

7 VI VSEBINA 1 UVOD MREŽNA ARHITEKTURA UNREAL ZGODOVINA MREŽNIH ARHITEKTUR V POGONIH POGON UNREAL SKRIPTNI JEZIK UNREALSCRIPT OSNOVNI KONCEPTI MREŽNO PROGRAMIRANJE AKTERJI POMEMBNOST PREDNOST REPLIKACIJA Replikacija akterjev Replikacija spremenljivk Replikacija funkcij REPLIKACIJSKI STAVEK REPLIKACIJSKI POGOJI SIMULIRANE FUNKCIJE IN STANJA PARADIGME MREŽNEGA PROGRAMIRANJA RAZREDI REPLICATIONINFO DOGODEK REPLICATEDEVENT LASTNOST MREŽNI NAČIN (ANGL. NETMODE) RAZRED GAMEINFO PREMIKANJE IN PREDIKCIJA IGRALCA OPTIMIZACIJSKA PRIPOROČILA IN TRIKI RAZLIKE MED GENERACIJAMI POGONOV VEČIGRALSKA IGRA KAMEN...22

8 VII 6.1 IGRA BREZ REPLIKACIJE IGRA Z REPLIKACIJO IGRA Z OPTIMIZIRANO REPLIKACIJO ZAKLJUČEK LITERATURA PRILOGE SEZNAM SLIK IZVORNA KODA IGRE KAMEN NASLOV ŠTUDENTA KRATEK ŽIVLJENJEPIS...33

9 VIII UPORABLJENE KRATICE UDK Razvojno okolje Unreal (Unreal Development Kit) LAN Lokalno omrežje (Local Area Network)

10 1 Stran 1 UVOD Prva igro, ki jo je lahko igralo več igralcev hkrati, je leta 1979 napisal Roy Trubshaw in sicer gre za pustolovščino Multi User Dungeons [1]. To so bili začetki mrežnega igranja. Preden je širokopasovni dostop do medmrežja postal razpoložljiv širši množici, je bilo večigralstvo omejeno samo na lokalno omrežje (LAN). LAN je omogočil večim igralcem, da so se pridružili igranju katerekoli računalniške igre. Za mrežno igranje sta potrebna dva ali več osebnih računalnikov, usmerjevalnik in več mrežnih kablov, s katerimi vsak računalnik priklopimo na omrežje. Danes večigralske igre v največji meri potekajo preko spleta. Te igre so med uporabniki dosegle priljubljenost zaradi večanja deleža širokopasovnega dostopa do spleta. Dostop do medmrežne povezave je namreč omogočil velikemu številu igralcev igranje skupaj (npr. Quake, Counter Strike, World of Warcraft, itd). Da igra podpira več igralcev, mora imeti posebej prilagojen pogon za mrežno igranje. V poglavju 2 bomo spoznali zgodovino in razvoj mrežnih iger in primerjali pogon Unreal z ostalimi. Predstavili bomo tudi skriptni jezik UnrealScript in osnovne koncepte, ki jih je potrebno razumeti za nadaljnje spoznavanje s pogonom Unreal. V tretjem poglavju bomo podrobno spoznali, kako mrežne parametre primitivov, ki gradijo igro, uporabiti in kaj pomenijo. Specifične primitive, paradigme in optimizacijske trike bomo spoznali v četrtem poglavju. Peto poglavje je namenjeno obrazložitvi razlik med generacijami pogonov. V šestem poglavju bomo predstavili igro Kamen, ji dogradili podporo za več igralcev in jo na koncu optimizirali.

11 2 Stran 2 MREŽNA ARHITEKTURA UNREAL 2.1 Zgodovina mrežnih arhitektur v pogonih Za večigralstvo je pomembno, da imajo vsi sodelujoči igralci občutek, da so v istem svetu. Z razvojem večigralstva od osnovnih mrežnih iger za dva igralca (npr. Doom) pa do večjih, bolj svobodnih interaktivnih iger (kot so Quake in Unreal), se je spremenila tudi uporabljena tehnologija [2]. Na začetku večigralstva so obstajale le mrežne igre, kjer se je vsak igralec povezal z vsakim drugim igralcem (angl. Peer-to-peer). Vsak igralec je z ostalimi sinhroniziral svoje vhode in čase in vsak je izvajal celotno logiko igre. Prednost takšnega pristopa je enostavnost. Slabosti pa so: Slaba obstojnost. Vsi igralci morajo začeti igro hkrati. Novi igralci ne morejo vstopit med tekom igre. Velik vpliv mrežnih napak na stabilnost igre. Če pade en igralec iz igre, se celotna igra prekine. Fiksirana hitrost posodobitve igre. Vsem igralcem se mora igra posodabljat enako hitro igra dela tako hitro, kot jo je sposoben poganjati najpočasnejši računalnik v sistemu. Nadalje se je pojavila monolitska odjemalniško-strežniška arhitektura, ki jo je začel John Carmack v pogonu Quake. V tej arhitekturi je en subjekt strežnik, ki je odgovoren za vse odločitve glede toka igre. Ostali subjekti so odjemalci, katere obravnavamo kot neumne terminale, ki le pošiljajo pritiske tipk in izrisujejo podatke prejete od strežnika. Nadalje se je ta arhitektura razširila s Quake 2 pogonom, ki je prenesel simulacijo in logiko predvidevanja na stran odjemalcev. Kljub temu obstajajo slabosti te arhitekture predvsem v smislu slabe razširljivosti.

12 2.2 Stran 3 Pogon Unreal V večigralstvu pogon Unreal rešuje probleme z generaliziranim odjemalniško-strežniškem modelom. V tem modelu je strežnik avtoritativen subjekt stanja igre (ima popoln nadzor nad igro). Odjemalec pa v vsakem trenutku ohranja natančno podobo stanja igre lokalno in lahko predvidi tok igre z izvajanjem iste kode kot strežnik (odjemalec ima približno iste podatke kakor strežnik). To pomeni, da se potrebna količina podatkov potrebnih za sinhronizacijo igre zmanjša. Strežnik pošlje informacije odjemalcem - replicira informacije (angl. replication). Pošiljajo se le pomembni akterji in njihove lastnosti. Odjemalci in strežniki komunicirajo tudi preko repliciranih funkcij, ki pa so replicirane le med strežnikom in odjemalcem, ki je lastnik nad akterjem v katerem se kliče funkcija. 2.3 Skriptni jezik UnrealScript Pogon Unreal za opis stanja igre uporablja skriptni jezik UnrealScript, ki je bil ustvarjen za enostavno, visoko nivojsko programiranje iger [6]. Principi skriptnega jezika so podobni Javi, kar pomeni, da je UnrealScript objektno orientiran. Osnovni razred iz katerega so izpeljani vsi ostali se imenuje Object. Osnovna entiteta je akter (Actor), ki je izpeljan iz osnovnega razreda Object. Pogon v vsaki iteraciji časovne zanke posodablja akterje. Vsak akter ima dve inicializacijski rutini (PreBeginPlay in PostBeginPlay). PreBeginPlay se kliče, kadar je akter ustvarjen, PostBeginPlay pa kadar je akter pripravljen za interakcijo z igro. Kadar se poda zahteva za uničenje akterja, se kliče poslednja rutina akterja (Destroyed). Vsak akter ima tudi svojega lastnika (nek drug akter). Življenjski cikel akterja je prikazan na sliki 2.1. Vse definicije uporabljene za uspešno mrežno delovanje igre se v pogonu Unreal definirajo s pomočjo jezika UnrealScript. To omogoča visoko stopnjo razširljivosti in fleksibilnosti in možnost ustvarjanja poljubnih novih objektov brez omejitev (brez ponovnega prevajanja pogona).

13 Stran 4 Slika 2.1: Življenski cikel akterja 2.4 Osnovni koncepti Za razumevanje nadaljnje predstavitve mrežne arhitekture, je potrebno razumeti nekaj osnovnih pojmov: Akter je objekt, ki se lahko prosto premika v stopnji in vzpostavlja interakcije z ostalimi akterji v stopnji. Stopnja je objekt, ki vsebuje množico akterjev. Funkcija Tick je iteracija časovne zanke, ki posodobi celotno stanje igre kadar preteče določen čas imenovan DeltaTime. Stanje igre določene stopnje se nanaša na vse akterje, ki obstajajo v tej stopnji in njihove vrednosti spremenljivk.

14 Stran 5 Odjemalec v tem dokumentu je instanca programa UDK.exe, kateri obdeluje in ohranja približno stanje igre, tako da lahko približno simulira dogodke, ki se dogajajo v igri. Strežnik v tem dokumentu je instanca programa UDK.exe, ki je odgovorna za posodabljanje stopnje, avtoritativni nadzor nad stopnjo in komunikacijo z odjemalci. Subjekt je strežnik ali odjemalec. Celotna igra teče v zanki (slika 2.2), ki izvaja naslednje akcije: Če sem strežnik, sporoči trenutno stanje igre vsem odjemalcem. Če sem odjemalec, sporoči moje zahteve za premik strežniku, pridobi trenutno stanje igre od strežnika in izriši moj trenutni približni pogled na svet na zaslon. Na vseh akterjih kliči funkcijo Tick. Klic funkcije Tick posodobi vse akterje na stopnji, izvede njihovo fiziko, jih obvesti o pomembnih dogodkih in izvede potrebno skriptno kodo. V pogonu Unreal je vse načrtovano tako, da je čas med dvema klicema funkcije Tick lahko različen. To omogoči večjo fleksibilnost hitrosti sličic (angl. frame rate). Primer: če sta pozicija in hitrost objekta opisani s pomočjo vektorjev, potem se nova pozicija izračuna po naslednji enačbi: Pozicija = Pozicija + Hitrost * DeltaTime Stanje igre se lahko spremeni natanko na tri načine: Spremenljivka v akterju se spremeni. Novi akter je ustvarjen. Akter je uničen. Stanje igre je popolnoma določeno z vsemi spremenljivkami vseh akterjev stopnje. Vsak subjekt sistema mrežne igre (strežnik ali odjemalec) ima svojo stanje igre. Ker je strežnik nadzorni subjekt v sistemu, je stanje igre na strežniku vedno tisto, ki je popolnoma pravilno. Akterji in njihove spremenljivke na odjemalcih lahko imajo razlike oz. so vrednosti le približki.

15 Stran 6 Prepustnost mrežnih povezav je omejena. Zato je pomembno, da pogon komunicira med subjekti tako, da se pošilja minimalna količina podatkov. Pogon Unreal sinhronizira med strežnikom in odjemalcem le tiste akterje, ki so pomembni za odjemalca (npr. za prikaz) in replicira spremenljivke, ki se spremenijo. Slika 2.2: Zanka igre

16 3 Stran 7 MREŽNO PROGRAMIRANJE 3.1 Akterji Vsak akter ima spremenljivko Role in RemoteRole, ki imata različne vrednosti na strežniku in odjemalcu. Vsak akter ima na strežniku Role nastavljen na ROLE_Authority. Akterji na strežniku lahko imajo RemoteRole nastavljen kot: ROLE_AutonomousProxy. To sta akterja PlayerController in Pawn, kadar sta replicirana lastniku odjemalcu. ROLE_SimulatedProxy imajo vsi ostali replicirani akterji. ROLE_None imajo akterji, ki niso nikoli replicirani odjemalcem. Spremenljivki Role in RemoteRole, opisujeta koliko nadzora ima strežnik oz. odjemalec: Če ima Role vrednost ROLE_SimulatedProxy, pomeni, da je akter začasen, približen in simulira fiziko in predvaja animacije. Na odjemalcih ti akterji izvajajo le osnovno fiziko (gravitacijsko gibanje in trki). Odjemalci lahko izvajajo le funkcije, ki so označene z besedo simulated in lahko vstopijo le v stanja, ki so označena z besedo simulated. Če ima Role vrednost ROLE_AutonomousProxy pomeni, da je akter lokalni igralec. Namesto simuliranja fizike, imajo ti akterji vgrajeno specialno logiko za napovedi premikanja (angl. client-side prediction). Na odjemalcu lahko izvedejo katerokoli funkcijo in vstopijo v katerokoli stanje. Če ima Role vrednost ROLE_Authority pomeni, da ima subjekt popolno pooblastilo za upravljanje in nadzor nad akterjem. Na odjemalcih so to ponavadi akterji, ki jih odjemalec ustvari in se ne replicirajo (vizualni efekti, ki nimajo vpliva na tok igre).

17 3.2 Stran 8 Pomembnost Stopnje v igri so lahko ogromne, vendar pa igralec ponavadi vidi le majhen delež vseh akterjev. Vsi ostali akterji niso vidni, slišni in nimajo vpliva ali interakcije z igralcem. Akterji, ki so vidni, slišni ali na nek način vplivajo na igralca so "pomembni akterji". Takšen način klasifikacije akterjev pomembno vpliva na potrebno količino prenesenih podatkov med strežnikom in odjemalcem (igralcem). Ali je akter pomemben ali ne, je določeno po naslednjem zaporedju: Če je akterjeva spremenljivka RemoteRole nastavljena na ROLE_None, potem akter ni pomemben. Če je akter pripet na skelet akterja X, potem je pomembnost določena glede na pomembnost akterja X. Če ima akter spremenljivko balwaysrelevant nastavljeno na vrednost TRUE, potem je pomemben. Če ima akter spremenljivko bonlyrelevanttoowner nastavljeno na vrednost TRUE, potem je potencialno pomemben le, če ima igralec lastništvo nad spremenljivko. Če ima igralec lastništvo nad akterjem, potem je pomemben. Če je akter skrit, nima kolizije in ne predvaja ambijentnega zvoka, potem je akter nepomemben. Če je akter viden glede na linijo vidnosti (angl. line-of-sight) od lokacije igralca, potem je pomemben. Akter je pomemben, če je bil pomemben pred manj kot t sekundami. Spremenljivka t je spremenljivka odvisna od nastavitev pogona. Diagram določanja pomembnosti je viden na sliki 3.1.

18 Slika 3.1: Določanje pomembnosti akterja Stran 9

19 3.3 Stran 10 Prednost Ker internetne povezave tudi v današnjih časih niso dovolj hitre, da bi lahko strežnik sporočal odjemalcem (igralcem) vse podatke v realnem času, pogon Unreal uporablja posebno tehniko razporeditve obremenitve (angl. load-balancing technique), ki daje prednost akterjem, ki so za tok igre pomembnejši. Vsak akter ima spremenljivko NetPriority. Višja kot je vrednost te spremenljivke, več podatkovne prepustnosti dobi akter glede na ostale akterje. Te vrednosti so relativne; Akter, ki ima NetPriority vrednost 2.0 se posodablja oz. sinhronizira dvakrat hitreje kot akter z NetPriority 1.0. Privzeta vrednost je 1.0. Akterji Pawn imajo vrednost 2.0, PlayerController 3.0, akterji projektilov 2.5, itd. 3.4 Replikacija Replikacija je pošiljanje informacij med strežnikom in odjemalcem. Mrežna komunikacija med strežnikom in odjemalci temelji na replikaciji treh primitivov: 1. replikacija akterjev, 2. replikacija spremenljivk in 3. replikacija funkcij Replikacija akterjev Strežnik določi niz pomembnih akterjev za vsakega odjemalca in jim pove, da jih ustvarijo in ohranjajo replicirano kopijo (kopije so lahko le približki). Repliciran akter se uniči na odjemalcu, kadar strežnik zapre komunikacijski kanal za ta akter, razen če ima akter spremenljivki bnettemporary in btearoff nastavljeni na vrednost TRUE Replikacija spremenljivk Replikacija spremenljivk (oz. lastnosti) akterja je vedno pomembna. To pomeni, da bodo lastnosti odjemalčeve (ali strežniške) verzije akterja v končni fazi imele enake vrednosti kot lastnosti istega akterja na strežniku (ali odjemalcu). Seveda pa se lastnosti replicirajo

20 Stran 11 le, če so definirane, da se naj replicirajo (v replikacijskem stavku). Replikacijska definicija definira replikacijske pogoje, ki opisujejo kdaj in če sploh se naj lastnost replicira. Četudi je akter vedno pomemben, se nekatere lastnosti nikoli ne replicirajo. Natančna in dobra replikacijska definicija lastnosti akterjev je zelo pomembna za zmanjšanje mrežnega prometa. Obstajajo tri lastnosti akterjev v povezavi s povedanim: bnetdirty spremenljivka se nastavi na vrednost TRUE, če se katerakoli replikacijska lastnost oz. spremenljivka spremeni. bnetinitial spremenljivka ima vrednost TRUE dokler niso vse replicirane lastnosti akterja dokončno replicirane (vendar le ob inicializaciji akterja). bnetowner ima vrednost TRUE, če je vrhnji lastniški akter PlayerController, katerega lastnik je trenutni odjemalec. Po vsakem klicu funkcije Tick, strežnik preveri vse akterje, ki so pomembni za določenega odjemalca. Spremenljivke, ki se spremenijo in so definirane v replikacijski definiciji ter ustrezajo replikacijskim pogojem, se pošljejo odjemalcu (oz. strežniku). Replikacija spremenljivk se zgodi po končanem klicu funkcije Tick. Če se spremenljivka v času klica funkcije Tick večkrat spremeni, potem se replicira le zadnja vrednost. Spremenljivke se replicirajo le, če spremenijo vrednosti in odjemalec pozna vrednosti repliciranih spremenljivk le za tiste akterje, ki so mu pomembni. Omejitve glede replikacije spremenljivk so naslednje: Vektorji in rotatorji: Pogon Unreal skrči vrednosti vektorjev in rotatorjev. Komponente X, Y, Z vektorja so pretvorjene v 16-bitno predznačeno število preden se pošljejo, kar pomeni da so vse decimalne vrednosti in vrednosti izven intervala od do izgubljene. Rotacija okoli osi X - Pitch, rotacija okoli osi Z Yaw in rotacija okoli osi Y - Roll se pretvorijo v zloge s pomočjo izraza ((X >> 8) & 255) pri čemer je X Pitch, Yaw ali Roll. Če je potrebna natančnost, potem je smiselno posamezne komponente ročno pretvoriti v podatkovni tip int ali float in jih replicirat.

21 Stran 12 Strukture se replicirajo tako, da se pošljejo vse vsebovane spremenljivke (po principu vse ali nič). Polja spremenljivk se lahko replicirajo, vendar le do velikosti polja 448 zlogov. Dinamična polja se ne replicirajo Polja se replicirajo učinkovito; če se spremeni en element polja, se replicira le ta. Replikacija funkcij Funkcija, ki se kliče na strežniku, se lahko dejansko izvede na odjemalcu oz. funkcija, ki se kliče na odjemalcu, se lahko dejansko izvede na strežniku. Funkcije imajo replikacijsko definicijo definirano v obliki rezerviranih besed server, client, reliable in unreliable. Večino funkcij izvedenih na strežniku ni potrebno replicirati. Za odjemalce je pomembno le, da se izvedejo funkcije, ki imajo vizualni ali zvočni efekt. Nepomembne funkcije (imajo v definiciji rezervirano besedo unreliable) se v primeru izgube mrežnih paketkov ali prevelike obremenitve mrežne prepustnosti ne izvedejo ali pa se lahko izvedejo v nepravem vrstnem redu. Pomembne funkcije se vedno izvedejo in se izvajajo v istem vrstnem redu kot so klicane. Postopek replikacije funkcij je naslednji: Pogon preveri ali replikacijski pogoji ustrezajo, da se funkcija izvede na oddaljenem subjektu: Ime funkcije in vsi parametri se pošljejo oddaljenemu subjektu za izvajanje. Lokalno se funkcija vrne takoj in izvajanje kode se nadaljuje - pogon ne čaka na vrnjen rezultat izvedene funkcije na oddaljenem subjektu. V primeru, da funkcija po definiciji vrača rezultat, je le ta nastavljen na privzeto vrednost (0 za števila, 0,0,0 za vektorje, none za objekte, itd). Če replikacijski pogoji ne ustrezajo, se funkcija izvede lokalno. Za razliko od spremenljivk, se funkcije lahko replicirajo le za akterje, katerih zgornji lastnik je odjemalec, ki sodeluje v procesu replikacije. Drugače povedano; ista funkcije ne more izvajati hkrati več odjemalcev (saj je lastnik vsakega akterja le en).

22 Stran 13 Replikacija funkcij ima prednost pred replikacijo spremenljivk, zato je potrebna previdnost pri implementaciji (preveč klicev repliciranih funkcij lahko obremeni mrežno prepustnost). Funkcije se replicirajo takoj, ko so klicane (pogon ne čaka na konec klica funkcije Tick). 3.5 Replikacijski stavek V skriptnem jeziku UnrealScript programer definira kateri akterji, spremenljivke in funkcije se replicirajo in kako se replicirajo. Večina replikacije je definirana znotraj replikacijskega stavka (primer na sliki 3.2). Vsak akter ima v jeziku UnrealScript definiran po en replikacijski stavek. Znotraj replikacijskega stavka je ena ali več replikacijskih definicij. Vsaka definicija vsebuje replikacijski pogoj (TRUE ali FALSE) in seznam ene ali več spremenljivk (lahko tudi akterjev). Spremenljivke, ki nastopajo v pogoju in spremenljivke, ki se replicirajo, morajo biti predhodno definirane kot spremenljivke akterja. Vsaka spremenljivka, ki ni definirana znotraj replikacijskega stavka ni nikoli replicirana. To je tudi smiselno, saj večine spremenljivk ni potrebno replicirati. Slika 3.2: Primer replikacijskega stavka iz razreda PlayerReplicationInfo

23 3.6 Stran 14 Replikacijski pogoji Primer replikacijskega pogoja znotraj replikacijskega stavka (slika 3.3) nam pove: Če je spremenljivka Role nastavljena na ROLE_Authority, potem se naj akterjeva spremenljivka MyWeapon replicira vsem odjemalcem, katerim je akter pomemben. Slika 3.3: Replikacijski pogoj Replikacijski pogoj je izraz, ki je ovrednoten kot resničen ali neresničen (TRUE ali FALSE) - napiše se lahko vsak izraz, ki je možen v UnrealScript. Naslednje spremenljivke se pogosto uporabljajo v replikacijskih pogojih: bisplayer: Je nastavljen na vrednost TRUE, če je ta akter igralec. bnetowner: Je nastavljen na vrednost TRUE, če je zgornji lastnik tega akterja igralec za katerega se pogoj preverja. bnetinitial: Je nastavljen na vrednost TRUE v začetnem stadiju replikacije akterja (veljaven samo na strani strežnika). Spremenljivke so ponavadi replicirane v eno smer (iz strežnika proti odjemalcem ali obratno), zato pogoji začnejo s primerjavo spremenljivke Role. Če je ta ROLE_Authority, potem se spremenljivka replicira od strežnika proti odjemalcem. Če je ta ROLE_AutonomousProxy ali ROLE_SimulatedProxy pa od odjemalca proti strežniku. Replikacijske pogoje pogon Unreal preverja zelo pogosto, zato morajo biti enostavni in hitro izvedljivi.

24 3.7 Stran 15 Simulirane funkcije in stanja Funkcije v jeziku UnrealScript so lahko definirane s pomočjo rezervirane besede simulated. Ta rezervirana beseda je zanimiva za akterje s spremenljivko Role nastavljeno na ROLE_SimulatedProxy (torej za večino akterjev na odjemalcih). Na odjemalcih se bo izvajala le tista koda, ki je v funkcijah oz. stanjih označenih z rezervirano besedo simulated. Vse ostale funkcije odjemalec preskoči oz. ne izvede.

25 4 Stran 16 PARADIGME MREŽNEGA PROGRAMIRANJA 4.1 Razredi ReplicationInfo Ti razredi (akterji) nosijo splošne informacije o igri (razred GameReplicationInfo) in o igralcih (razred PlayerReplicationInfo). ReplicationInfo akterji imajo spremenljivko balwaysrelevant nastavljeno na TRUE, kar pomeni, da so vedno pomembni, ne glede na njihovo lokacijo, vidnost oz. slišnost. Replicirajo se vsem igralcem. 4.2 Dogodek ReplicatedEvent Spremenljivke akterja lahko označimo z rezervirano besedo repnotify, ki nam omogoči, da se ob dogodku replikacije (torej, kadar nam strežnik pošlje spremenjeno vrednost spremenljivke) kliče funkcija z imenom ReplicatedEvent. Parameter funkcije je ime spremenljivke, ki se spremeni. Z uporabo dogodka ReplicatedEvent se na enostaven način ugotovi, kdaj se katera spremenljivka replicira. 4.3 Lastnost mrežni način (angl. NetMode) Akter WorldInfo ima spremenljivko z imenom NetMode. Ta nam pove, kakšna je naša vloga v sistemu igre in sicer, če spremenljivka ustreza: NM_Standalone: samostojna igra (ni mrežno podprta) - enoigralski način, NM_DedicatedServer: strežnik brez lokalnega igralca, NM_ListenServer: strežnik z lokalnim igralcem (igralec hkrati igra in gosti igro), NM_Client: odjemalec (igralec, ki igra na strežniku). Spremenljivka NetMode je velikokrat uporabljena za usmerjanje izvajanja kode.

26 4.4 Stran 17 Razred GameInfo Ta razred implementira pravila igre. Za vsak tip igre obstaja drug razred, ki lahko ima različna pravila. Tega razreda ni na odjemalcu (se ne replicira odjemalcem). Odjemalec oz. igralec torej nikoli ne izvaja kode, ki oblikujejo pravila igre. V razredu GameInfo so implementirane tudi funkcionalnosti za prepoznavanje novih igralcev in odhod le teh. Funkcije oz. dogodki povezani z mrežno funkcionalnostjo so naslednji: Dogodek InitGame se kliče ob inicializaciji strežnika, da se naloži željen tip igre, stopnja in ostala pravila igre. Dogodek PreLogin se kliče preden se novi odjemalec prijavi na strežnik. Tukaj lahko strežnik zavrne zahtevo za prijavo (v primeru, da igralec ne vpiše pravilnega gesla, če se le to zahteva, ali če njegov mrežni naslov ni dovoljen za igranje na tem strežniku). Dogodek Login se kliče ob prijavi novega odjemalca (igralca) na strežnik. Ta funkcija ustvari nov akter PlayerController, katerega uporablja igralec in vrne referenco. Dogodek PostLogin se kliče po prijavi novega odjemalca na strežnik. Po tem dogodku se lahko kličejo replicirane funkcije. 4.5 Premikanje in predikcija igralca Če bi pogon Unreal uporabljal le odjemalsko-strežniški model, potem bi se premikanje igralcev izvajalo z zamikom. Na povezavi med odjemalcem in strežnikom, ki ima zamik 300 milisekund, bi igralec po pritisku tipke za premik čakal 300 milisekund, da bi se premaknil. Za rešitev tega problema, pogon Unreal uporablja predikcijo, ki je v celoti implementirana s pomočjo skript (v razredu PlayerController). Odjemalec preveri vhodne naprave (tipkovnice, miške,...) in fizične sile gravitacije ter opiše premik s pomočjo tri-

27 Stran 18 dimenzionalnega vektorja v prostoru. Odjemalec pošlje ta vektor (kot pospešek) in ostale pomembne podatke strežniku s pomočjo klica funkcije ServerMove. Nato odjemalec kliče funkcijo MoveAutonomous, ki izvede lokalni premik in shrani ta premik v povezan seznam preteklih premikov. Na tak način se lahko igralec premika po stopnji, brez da bi dobil potrdilo s strani strežnika. Kadar strežnik prejme klic funkcije ServerMove, izvede isti premik na strežniku. Vendar se lahko zgodi, da strežnik vidi lokacije akterjev malo drugače; na primer, igralec se v resnici zaleti v mimoidočega igralca, katerega lokacija na strani odjemalca je drugačna, kot na strežniku. Ker je strežnik nadzorni subjekt (odjemalci prikazujejo le približno sliko igre), v tem primeru kliče na odjemalcu funkcijo ClientAdjustPosition s katero popravi lokacijo odjemalca. Kadar odjemalec prejme klic te funkcije, nastavi lokacijo igralca na lokacijo ukazano s strani strežnika. Ker pa je igralec bil na tej lokaciji enkrat v preteklosti, se ponovno izvedejo tudi vsi shranjeni premiki v povezanem seznamu preteklih premikov, ki so se zgodili po popravljenem premiku. Na tak način odjemalec vedno predvideva vnaprej svojo lokacijo in za njegovo lokalno premikanje ni zamika. Diagram določanja premikanja igralca na strežniku in odjemalcu je prikazan na sliki 4.1. Prednosti omenjenega pristopa rešitve problema premikanja so: Strežnik ostane nadzornik glede lokacije igralcev, kar onemogoči goljufe, ki bi spremenili hitrost ali lokacijo svojega igralca. Lokalno premikanje je tekoče, brez zamikov, kakor da se igra v eno-igralskem načinu.

28 Stran 19 Slika 4.1: Izvedba premikanja igralca 4.6 Optimizacijska priporočila in triki Cilj optimiziranega skriptiranja mrežne podpore je doseči maksimalno količino vizualnih detajlov z omejeno količino mrežne prepustnosti. To pomeni, da je naš cilj pri pisanju skript za akterje, ki so uporabljeni v mrežnih igrah, da ohranjamo porabo mrežne prepustnosti na najnižjem možnem nivoju.

29 Stran 20 Najbolje je uporabljati simulirano premikanje, kjerkoli je to možno - odjemalci lokalno simulirajo lokacijo akterja glede na hitrost in pretečen čas. V tem primeru strežniku ni potrebno pošiljati lokacije akterja vsak ob vsakem klicu funkcije Tick, ampak pošlje na začetku le začetno lokacijo in hitrost. Posebni vizualni in zvočni efekti se naj ustvarijo le na odjemalcih. Ker takšni efekti ne vplivajo na tok igre, jih je nesmiselno ustvarjati na strežniku. Vsak repliciran akter mora imeti dobro nastavljeno vrednost spremenljivk NetPriority in NetUpdateFrequency. Akterji projektilov in igralcev oz. karakterjev naj imajo visoke vrednosti, akterji, ki skrbijo večinoma za vizualne efekte pa naj imajo nižje vrednosti. Izogibati se je potrebno replikaciji struktur, saj se replicirajo vsi elementi, čeprav se spremeni samo eden. Minimizacija uporabe procesorja strežnika se lahko doseže s pomočjo: Minimizacije števila potencialno repliciranih akterjev. Minimizacija števila akterjev, ki morajo biti preverjeni ali so pomembni za odjemalca. Minimizacija števila pomembnih akterjev za odjemalca. Minimizacija števila repliciranih spremenljivk. Minimizacija ustvarjanja novih akterjev na strežniku. Ne izvajanje kode, ki nima pomena igralnosti. Minimizacija količine repliciranih klicev funkcij. Z rastjo števila igralcev oz. odjemalcev na strežniku se časovna zahtevnost replikacije akterjev povečuje in postane prevladujoče opravilo za procesor. Časovna zahtevnost se veča eksponentno in ne linearno kot bi pričakovali. Minimizacija porabe mrežne prepustnosti se lahko doseže s pomočjo: Manjšega števila pomembnih akterjev za odjemalca. Manjšo frekvenco posodobitev.

30 5 Stran 21 RAZLIKE MED GENERACIJAMI POGONOV Obstajajo tri generacije pogonov Unreal [3]: Unreal Engine 1 Unreal Engine 2 Unreal Engine 3 (UDK) Skriptni jezik UnrealScript in replikacija sta bila predstavljena že s prvim pogonom. Vendar se je replikacija od časa prvega pogona pa do trenutno aktualnega tretjega pogona malenkost spremenila. Najbolj očitne in pomembne spremembe so naslednje [4]: V starejših pogonih je replikacija funkcij definirana znotraj replikacijskega stavka (podobno kot spremenljivke). Zraven replikacijskih pogojev se uporabljata tudi rezervirani besedi reliable in unrealiable. Starejša replikacija ima dodatno možno vrednost za spremenljivki Role in RemoteRole imenovano ROLE_DumbProxy. Takšen akter na odjemalcih ne simulira fizike, ampak se posodobil le, kadar strežnik pošlje nove vrednosti lokacije, rotacije, itd. V starejši replikaciji ne obstaja dogodek ReplicatedEvent, kar naredi zaznavanje sprememb repliciranih spremenljivk za težje opravilo.

31 6 Stran 22 VEČIGRALSKA IGRA KAMEN Za demonstracijo uporabe replikacije pri programiranju mrežne igre bomo ustvarili enostavno potezno igro, kjer moramo kamen spraviti do cilja, brez da izvedemo napačno potezo, ki bi povzročila, da naš kamen pade iz ploskve na kateri se giblje. Kamen se ustavi, kadar zadane ob zid. Po tej akciji, lahko kamnu nastavimo novo smer (kamen se giblje v tej smeri s konstantno hitrostjo). Naslednje slike prikazujejo način gibanja kamna. Igro začnemo na začetni poziciji (kamen je krog črne barve), kjer imamo na voljo premik navzgor (slika 6.1). Slika 6.1: Začetna poteza Po izvedenem ukazu za premik navzgor, se naš kamen začne gibati navzgor s konstantno hitrostjo, dokler ne doseže zidu (črn pravokotnik). V tem času kamnu ne moremo posredovati novih premikov (npr., da bi se ustavil ali spremenil smer). Ko kamen doseže zid, lahko posredujemo nov ukaz za premik in sicer v tem primeru lahko gre kamen levo, desno ali dol. V tem primeru se odločimo desno, tako da pritisnemo tipko za smer desno (slika 6.2).

32 Stran 23 Slika 6.2: Druga poteza Ko kamen doseže drugi zid, imamo ponovno na voljo tri odločitve, tokrat: navzgor, dol, levo. Če izberemo ukaz dol, kamen pade iz igralne ploskve, kar pomeni da igro izgubimo (slika 6.3). Slika 6.3: Napačna poteza

33 Stran 24 Pravilna izbira je navzgor, in tako pridemo do zelenega pravokotnika, ki označuje cilj (slika 6.4). Slika 6.4: Zadnja poteza 6.1 Igra brez replikacije Igro implementiramo v skriptnem jeziku UnrealScript pogona Unreal s pomočjo UDK [5]. Najprej inštaliramo UDK, ki ga pridobimo iz uradne strani ( Nato spišemo skripte (podane na priloženi zgoščenki) v beležnici in jih postavimo v mapo Diplomska\Classes znotraj mape Src. Datoteko DiplomskaMap.udk (podano na priloženi zgoščenki) skopiramo v mapo Content\Maps. Nastaviti je potrebno tudi datoteko DefaultEngine.ini, ki se nahaja v mapi Config. Pod sekcijo [UnrealEd.EditorEngine] dodamo vrstico +EditPackages=Diplomska. Po prvem zagonu igre, se prevedejo vse skripte, zato zagon ponovimo. Zaganjamo UDK.exe, ki se nahaja v mapi Binaries\Win32. Parameter za zagon igre kot samostojnega igralca je DiplomskaMap? Game=Diplomska.DGame. Po prijavi igralca v igro, mora le ta pritisniti tipko ENTER vstopi v stanje pripravljenosti na začetek igre. Igra preveri, ali so vsi sodelujoči igralci pripravljeni. Če so, se igra začne. Vsakemu igralcu se ustvari akter z imenom DObject (kamen, ki ga igralec premika). Vse

34 Stran 25 želene poteze igralca se posredujejo temu akterju preko funkcije MoveMe. Ta funkcija preveri možnost premika v smeri želene poteze in premik tudi izvede. Akterju se nastavi smerna hitrost in nato pogon sam poskrbi za posodabljanje lokacije akterja glede na pretečen čas. V primeru trka z zidom se kliče dogodek HitWall, ki akterja ustavi, pregleda možne naslednje poteze in čaka na naslednji klic MoveMe funkcije, da se ponovno izvede premik. Akter med drugim tudi preverja ali ima pod sabo ploskev. Če kamen zaide izven ploskve, smo igro izgubili. Igra se konča z zmago, če pridemo do cilja označenega z zeleno barvo to je akter DGoalVolume, ki ima določeno prostornino. V akterju DGoalVolume se kliče dogodek Touch, kadar se kamen dotakne tega akterja. Implementirana igra brez replikacije deluje popolnoma normalno za enega igralca (slika 6.5). Slika 6.5: Igra za enega igralca

35 6.2 Igra z replikacijo Igro tokrat zaženemo kot Stran 26 strežnik (zagon UDK.exe s parametrom: server DiplomskaMap?Game=Diplomska.DGame ) in se nanj povežemo z odjemalcem (zagon UDK.exe s parametrom: ). Problemi nastanejo že na samem začetku; igra za začetek potrebuje vhod igralca, da je le ta pripravljen. To se zgodi tako, da se spremenljivka breadytoplay nastavi na vrednost TRUE (odjemalec kliče funkcijo ReadyGame) in potem igra (akter DGame) preveri, ali so vsi igralci pripravljeni za igro (klic funkcije CheckGameStart). Tukaj imamo dva problema, zakaj se to ne zgodi: 1. breadytoplay spremenljivka spremeni vrednost le na odjemalcu. Strežnik posodobljene vrednosti nikoli ne prejme. 2. CheckGameStart se ne kliče, saj na odjemalcu, ki izvaja klic funkcije akter DGame ne obstaja. DGame je namreč podrazred GameInfo razreda, kar pomeni, da ga odjemalec nima. Problem lahko rešimo z uvedbo pravilne replikacije. Zadostuje, če odjemalec kliče funkcijo ReadyGame na strežniku, ki ga pripravi v stanje pripravljenosti (nastavi spremenljivko breadytoplay na TRUE) in nadalje kliče CheckGameStart. Da dosežemo omenjeno, funkcijo ReadyGame definiramo s pomočjo rezerviranih besed reliable server. Nadalje moramo replicirati tudi akter DObject, sicer ga igralec nikoli ne vidi in obstaja le na strežniku. To dosežemo tako, da v bloku defaultproperties razreda DObject definiramo osnovne replikacijske spremenljivke: RemoteRole nastavimo na ROLE_SimulatedProxy (akter se replicira, na strani odjemalca simulira fiziko in svojo pozicijo lahko kliče tudi funkcije označene z rezervirano besedo simulated). NetPriority nastavimo na 3.0 (akterju določimo relativno količino mrežne prepustnosti). bupdatesimulatedposition nastavimo na TRUE (akter naj simulira pozicijo na odjemalcu replicira se lokacija in hitrost).

36 Stran 27 Znotraj DPlayerController razreda definiramo replikacijo spremenljivk MovingObject, breadytoplay, DisplayText pod replikacijskim pogojem, če ima spremenljivka Role vrednost ROLE_Authority (repliciranje od strežnika proti odjemalcu). Pomen je naslednji: MovingObject je spremenljivka reference na akter DObject. breadytoplay se replicira odjemalcu, da lahko le ta izpiše stanje pripravljenosti na zaslon. DisplayText je spremenljivka tipa string, katero odjemalec prikaže na zaslon (uporablja za prikaz odštevanja začetka igre, obvestilo o zmagi in porazu). V razredu DPlayerController je potrebno replicirati še eno funkcijo, in sicer funkcijo zahtevanega premika igralca (MoveMyObject). Le ta se replicira od odjemalca proti strežniku, zato jo definiramo s pomočjo rezerviranih besed reliable server. Igra nato deluje v večigralskem načinu in podpira do dva igralca (slika 6.6). Slika 6.6: Igra z dvema igralcema

37 6.3 Stran 28 Igra z optimizirano replikacijo V igri uporabimo ukaz net stat, ki nam prikaže trenutno stanje mrežnih parametrov oz. mrežno statistiko (slika 6.7). To je ukaz vgrajen v pogon Unreal in ga ni potrebno programirati. Za igro, ki smo ji dogradili večigralski način, je stanje vidno na sliki. Nekateri pomembni parametri so: Channels je število trenutno repliciranih akterjev. In Rate je število zlogov na sekundo poslanih od strežnika proti odjemalcu. Out Rate je število zlogov na sekundo poslanih od odjemalca proti strežniku. Pri optimizirani replikaciji težimo k temu, da so vrednosti teh parametrov čim nižje. Število repliciranih akterjev več ne moremo zmanjšati, saj smo dodali le enega (DObject), pa še ta je pomemben (če ga ni, igralci ne vidijo kamnov). Slika 6.7: Prikaz mrežne statistike

38 Stran 29 Optimiziramo pa lahko mrežni promet. To storimo tako, da izvajamo lastno predvidevanje lokacije kamnov glede na podano hitrost in čas. V defaultproperties razreda DObject tokrat nastavimo spremenljivko bupdatesimulatedposition na FALSE. Uvedemo novo spremenljivko tipa vector z imenom MyVelocity, ker je spremenljivka Velocity že definirana v replikacijskem stavku nadrazreda Actor. Za našo novo spremenljivko definiramo tudi replikacijski pogoj (replicira se, če je spremenljivka Role nastavljena na ROLE_Authority). Vse kar preostane je delo, ki ga strežnik in odjemalec izvajata v klicu funkcije Tick. Če smo strežnik, potem spremenljivko MyVelocity nastavimo na vrednost Velocity. Če smo odjemalec pa Velocity nastavimo na vrednost MyVelocity. Na tak način dosežemo, da strežnik pošlje le vektor hitrosti, kadar se ta spremeni. In ker imamo potezno igro, se to zgodi le ob izvedbi poteze igralca in kadar kamen zadane zid. V času premikanja kamna se pri neoptimizirani replikaciji mrežni pretok v smeri strežnik proti odjemalcu giblje nekje okoli 1000 zlogov na sekundo (slika 6.7). Pri optimizirani replikaciji pa okoli 150 zlogov na sekundo (slika 6.8). Slika 6.8: Prikaz mrežne statistike na optimizirani igri

39 7 Stran 30 ZAKLJUČEK Programiranje iger s podporo večim igralcem je z razvojem novih tehnologij, kot so pogon Unreal, postalo enostavno in nič kaj težavnejše od programiranja iger brez mrežne podpore. Z razvojem lastnega pogona, bi zraven implementacije prikazovalnika (angl. renderer) in implementacije logike igre morali implementirati tudi mrežno podporo, ki lahko ima veliko konceptualnih problemov, kot so problem časovne zakasnitve mrežnih komunikacij. Ogromno dela bi porabili tudi z razvojem tolmača (angl. interpreter) za lastni skriptni jezik (še posebej, če bi le ta podpiral definicije mrežnih parametrov za objekte, kakor to omogoča pogon Unreal). Razvoj lastnega pogona bi trajal ogromno časa in zahteval veliko skupino programerjev. Z uporabo pogona Unreal teh skrbi ni. Mrežna arhitektura Unreal pa ima eno pomanjkljivost. Celotna zanka za obdelovanja logike igre teče v eni niti. V končni fazi to pomeni, da je število akterjev, ki jih je pogon zmožen obdelati v dovolj hitrem času, da se to ne pozna na slabši odzivnosti igre, omejeno in odvisno od eno-nitne hitrosti procesorja. Ker obstaja zgornja meja eno-nitne hitrosti procesorja (za razliko od več-nitne hitrosti, ki je lahko poljubna raste s številom vezanih procesorjev v skupino), ima pogon Unreal zgornjo mejo največjega števila igralcev, ki lahko prisostvujejo v eni igri (na enem strežniku). To pomeni, da je Unreal pogon neprimeren za razvoj danes popularnih tako imenovanih masivnih večigralskih iger (angl. Massively multiplayer online game). Kljub tej pomanjkljivosti, pa je UDK še vedno odlična izbira za razvoj prvo-osebnih akcijskih, fantazijskih, strateških in tudi simulacijskih iger (tako enoigralskih kot tudi večigralskih).

40 8 Stran 31 LITERATURA [1] Wikipedia, Računalniška igra, 2011, %C5%A1ka_igra, zadnji obisk v septembru 2011 [2] Epic Games, Unreal Networking Architecture, zadnji obisk v septembru 2011 [3] Wikipedia, Unreal Engine, 2011, zadnji obisk v septembru 2011 [4] Epic MegaGames, Inc., Unreal Networking Architecture, 2011, zadnji obisk v septembru 2011 [5] Unreal Development Kit, Unreal Development Kit, 2011, zadnji obisk v septembru 2011 [6] Wikipedia, UnrealScript, 2011, zadnji obisk v septembru 2011

41 9 9.1 Stran 32 PRILOGE Seznam slik Slika 2.1: Življenski cikel akterja...4 Slika 2.2: Zanka igre...6 Slika 3.1: Določanje pomembnosti akterja...9 Slika 3.2: Primer replikacijskega stavka iz razreda PlayerReplicationInfo...13 Slika 3.3: Replikacijski pogoj...14 Slika 4.1: Izvedba premikanja igralca...19 Slika 6.1: Začetna poteza...22 Slika 6.2: Druga poteza...23 Slika 6.3: Napačna poteza...23 Slika 6.4: Zadnja poteza...24 Slika 6.5: Igra za enega igralca...25 Slika 6.6: Igra z dvema igralcema...27 Slika 6.7: Prikaz mrežne statistike...28 Slika 6.8: Prikaz mrežne statistike na optimizirani igri...29

42 9.2 Stran 33 Izvorna koda igre Kamen Izvorna koda za vse tri verzije igre Kamen se nahaja na priloženi zgoščenki. 9.3 Naslov študenta Matjaž Škorjanc Goriška ulica Maribor E-pošta: 9.4 Kratek življenjepis Rojen: v Mariboru Šolanje: OŠ Franca Lešnika-Vuka, Slivnica pri Mariboru Prva Gimnazija Maribor, Maribor Medicinska fakulteta, Univerza v Mariboru, Maribor Fakulteta za elektrotehniko, računalništvo in informatiko, Univerza v Mariboru, Maribor

43 Stran 34

44 Stran 35

45 Stran 36