Een inleiding tot het programmeren van Ultimate Stunts
Inhoud
Inleiding
Dit document is geschreven voor mensen die geïnteresseerd zijn in het lezen van de
Ultimate Stunts broncode, of die willen bijdragen aan Ultimate Stunts door nieuwe
features te programmeren. De reden om dit te schrijven is om nieuwe Ultimate Stunts
ontwikkelaars aan te trekken, en om het nieuwe ontwikkelaars zo makkelijk mogelijk
te maken om aan de slag te gaan.
Voordat u code begint toe te voegen
Wees u ervan bewust dat Ultimate Stunts wordt uitgegeven onder de GNU General Public
License. U hoort uw code-veranderingen ook onder deze licentie uit te geven. Let
er op dat alle voorwaarden in deze licentie aanvaardbaar zijn voor u, voordat u
code uitgeeft. Het kan nuttig zijn om deze licentie minimaal één keer door te lezen.
U kunt deze licentie vinden in uw Ultimate Stunts pakket als een tekst-bestand met
de naam 'COPYING'. U kunt het ook verkrijgen op de website van de Free Software
Foundation.
Wat u al moet weten
Als u slechts aan een beperkt deel van Ultimate Stunts gaat werken, dan hoeft u
misschien geen ervaring te hebben met al deze onderwerpen. Ik stel voor dat u dit
hele hoofdstuk doorleest, en dan voor uzelf beslist hoe bruikbaar u bent voor het
Ultimate Stunts project, en indien noodzakelijk, zorg dan eerst voor wat extra
ervaring voordat u zich bij het project aansluit.
C en C++
Bijna de hele Ultimate Stunts broncode is geschreven in de programmeertaal C++, en
de rest is geschreven in C. Als u de broncode wilt bewerken, dan heeft u beslist
ervaring nodig met deze talen. Er zijn voldoende gratis online cursussen beschikbaar
op het internet, en er zijn ook vrij verkrijgbare compilers voor deze talen.
Zorg ervoor dat u ervaring heeft (niet alleen kennis) met onderwerpen zoals afgeleide
klassen, virtuele functies en het overloaden daarvan, operator overloading, en C++
templates. Als u om te oefenen moet kiezen tussen verschillende compilers, dan stel ik
voor om de zelfde compiler te gebruiken als Ultimate Stunts: de GNU Compiler Collection
(gcc). In windows kunt u deze verkrijgen door Cygwin te installeren, maar het is
aan te bevelen om Linux te gebruiken, of een ander vrij UNIX-achtig besturingssysteem.
UNIX/Linux
Ultimate Stunts is ontworpen om voornamelijk op UNIX-achtige platforms te werken,
zoals Linux; de windows-versie wordt gecompileerd door de UNIX-emulatiesoftware Cygwin
te gebruiken. Het is daarom nuttig om te weten hoe u kunt werken met deze systemen.
Het bouw-proces van Ultimate Stunts is gebaseerd op programma's als autoconf en
automake, dus als u daaraan iets moet veranderen (bijv. voor het linken met een
nieuwe bibliotheek, of voor het fixen van het bouwsysteem voor een bepaald platform),
dan moet u weten hoe u deze programma's kunt gebruiken. Zelfs als u nieuwe
broncodebestanden toe voegt aan Ultimate Stunts, dan moet u weten hoe de juiste
Makefile.am aangepast moet worden (maar dat is niet echt moeilijk).
Voor het vertalen van de gebruikersinterface in andere talen gebruikt Ultimate Stunts
de GNU gettext bibliotheek. Als het goed is ingesteld, dan is het niet zo moeilijk
om gettext te gebruiken, maar u moet zich ervan bewust zijn dat gettext gebruikt
wordt.
OpenGL
Alle grafische uitvoer in Ultimate Stunts (2D en 3D graphics, menus, teksten, enz.)
zijn gebaseerd op OpenGL. Dus, als u iets in Ultimate Stunts wilt aanpassen dat te
maken heeft met grafische uitvoer, dan heeft u OpenGL ervaring nodig. Zelfs als u
op een hoger niveau werkt, waar u niet direct OpenGL-aanroepen hoeft te doen, kan
het handig zijn om het gedrag van OpenGL te kennen. U heeft wat basale theoretische
kennis nodig over hoe 3D rendering werkt in OpenGL, wat de verschillende
transformatiematrices doen, en natuurlijk hoe u wat lijnen en driehoeken kunt tekenen.
Het is ook nuttig om wat ervaring te hebben met het schrijven van uw eigen OpenGL
programma's.
SDL
SDL wordt gebruikt voor het maken van een venster voor de OpenGL context, voor
gebruikers-invoer (toetsenbord/muis/joystick) en voor multithreading. Als uw werk
niet direct is gerelateerd aan deze onderwerpen, dan hoeft u SDL niet te leren. Zelfs
als u in een later stadium iets met SDL moet doen, dan is dit geen groot probleem,
omdat SDL vrij eenvoudig te leren is.
De extra bibliotheek SDL_image wordt ook gebruikt. Deze bibliotheek heeft zelfs een
nog eenvoudigere programmeerinterface, maar als u niet betrokken bent bij de functies
die textures uit bestanden laden, dan hoeft u het niet te kennen.
FMOD / OpenAL
De bibliotheken FMOD en OpenAL worden beide gebruikt voor geluids-uitvoer. Als u niet
aan het geluids-subsysteem werkt, dan hoeft u ze niet te kennen, maar als u er wel
aan werkt, dan heeft u kennis van beide nodig. De reden is dat op sommige platforms
FMOD wordt gebruikt, en op andere OpenAL, zodat als er iets verandert aan het
geluidssysteem, u er voor moet zorgen dat beide nog steeds werken. Het is ook nuttig
om in staat te zijn om met beide te compileren (en iets af te weten van autoconf om
tussen de twee te kunnen kiezen).
Wiskunde
Ultimate Stunts is een erg driedimensionaal programma, dus in het grootste deel van
de broncode zult u moeten werken met 3D vectoren en hun operatoren, en vaak ook met
3*3 transformatiematrices. Dus, zorg ervoor dat uw kennis van lineaire algebra
up-to-date is, en dat u het concept van een rotatiematrix begrijpt (dat kunt u op het
internet opzoeken). Voor enkele delen van de broncode, vooral de physics engine,
heeft u ook kennis nodig van andere wiskundige concepten, zoals analyse, of
numerieke integratie.
Natuurkunde (van de auto)
U heeft dan en slechts dan gedetailleerde kennis van natuurkunde nodig, als u gaat
werken aan de physics engine. Ultimate Stunts gebruikt veel trucs en benaderingen
in de simulatie om het makkelijker te spelen te maken, maar er wordt nog steeds een
boel echte fysica gebruikt in de simulatie. U moet de wetten van Newtoniaanse mechanica
kennen (u weet wel, over snelheden, acceleratie, massa, krachten enz.). en
rotatie-mechanica (krachtmomenten, traagheidsmomenten enz.). Het is ook handig om iets
te weten van hoe een auto werkt, met dingen als de motorkoppel-curve,
versnellings-verhoudingen, wielophanging, gewichtsverdeling tussen de wielen, het
gedrag van de banden, enz..
Ultimate Stunts
Het is essentieel om te weten wat er al aanwezig is in Ultimate Stunts. Dit document
zal u helpen om uw weg te vinden in de broncode, maar het bevat niet genoeg detail om
alles uit te leggen. Zorg ervoor dat u het spel genoeg gespeeld heeft om alle
mogelijkheden te kennen, speel een beetje met de instellingen, maak misschien uw
eigen tracks om wat gevoel te krijgen voor hoe Ultimate Stunts tracs 'werken', en
lees op de Ultimate Stunts website over de geschiedenis van het project, en de
geplande features. De manier hoe dingen geïmplementeerd zijn zal duidelijker zijn als
u weet wat het moet doen, en wat het zal moeten doen in de toekomst.
Programmeergewoonten
Als verschillende programmeurs aan het zelfde stuk code werken, dan is het vaak
frustrerend wanneer de code van anderen niet je eigen gewoonten volgt. Daarom
beschrijft deze sectie enkele dingen die u moet doen om geen ergernis te veroorzaken
bij andere programmeurs. De onderstaande regels zijn bedoeld als algemene richtlijnen,
niet als regels die altijd gevolgd moeten worden, zelfs als het resultaat er dom uit
ziet. Er worden geen voorbeelden gegeven: de bestaande broncode bevat voldoende
voorbeelden.
Bestandsformaat
Alle broncode-bestanden moeten UNIX-stijl tekstbestanden zijn, niet DOS/windows-stijl
of Macintosh-stijl. Als u in windows werkt (niet aanbevolen), zorg er dan voor dat
uw tekst-editor documenten opslaat als UNIX-stijl tekstbestanden. De meeste tekst
editors in windows doen dat niet. De tekenset zou zo veel mogelijk ASCII moeten zijn,
dus alleen de eerste 128 tekens mogen gebruikt worden, en unicode dient niet
gebruikt te worden. In de enkele gevallen dat andere tekens nodig zijn, wordt de
ISO 8859-1 standaard gevolgd. Alle variabele-namen, functienamen, commentaar enz.
moeten in het engels geschreven zijn. Een uitzondering zijn natuurlijk korte
variabele-namen zoals x, y of i. Houd als regel aan dat er geen andere natuurlijke
talen gebruikt mogen worden in de broncode.
Indentatie
Indentatie wordt gedaan met tabs, niet met spaties. Zorg ervoor dat u geen
tekst-editor gebruikt die tabs omzet in spaties. Voor elk indentatie-niveau wordt
één extra tab gebruikt. Er wordt geen aanname gedaan over de breedte van een tab.
Een mogelijke uitzondering op de regel voor het gebruik van tabs is er voor het
uitlijnen van expressies die in op elkaar volgende regels staan, en die een
overeenkomst hebben. Het uitlijnen van zulke expressies maakt het duidelijker om
de overeenkomst te zien, wat de code leesbaarder maakt. Omdat er geen aanname gedaan
wordt over de breedte van een tab, kan zulke uitlijning vaak alleen gedaan worden
met spaties.
De plaatsing van accolades ('{' en '}') moet op de zelfde manier gedaan worden als
in de bestaande code. Dus, de openende en de sluitende acollade moeten beide op
hun eigen regel geplaatst worden, die niets bevat behalve de acollade en mogelijk
een commentaartekst. De indentatie hoort het zelfde te zijn als die van het statement
waar het code-blok onderdeel van uit maakt; de code binnen de acollades moet
een indentatie meer krijgen. Er wordt een uitzondering gemaakt voor erg kleine
code-blokken die op een enkele regel passen. Zie de broncode hoe dit gedaan wordt.
Namen van identifiers
Voor namen van identifiers, zoals functienamen, class namen en variable-namen, worden
de volgende conventies gebruikt:
- Class namen hebben een 'C' prefix, maar de overeenkomstige bestandsnamen
van de broncode hebben deze prefix niet.
- Struct namen hebben een 'S' prefix. Wanneer methods worden toegevoegd aan een
struct, wordt het geconverteerd naar een class, en wordt de prefix veranderd in
een 'C'.
- Member variabelen van classes hebben een "m_" prefix.
- Enum typen hebben een 'e' prefix
- Indien mogelijk, bevatten functie- en method-namen een werkwoord. Dus het is
CTrack::getWidth() en niet CTrack::width(). Functie- and method-namen
beginnen altijd met een kleine letter. Hoofdletters worden gebruikt voor de eerste
letter van elk woord in de naam, behalve het eerste woord.
Hergebruik van code
Veel classes in Ultimate Stunts zijn gemaakt met als doel om herbruikbaar te zijn.
Het gebruik van deze classes zal uw code er netter uit laten zien, en uw code zal
makkelijker te begrijpen zijn voor Ultimate Stunts ontwikkelaars die bekend zijn met
deze classes. Het kan u ook tijd besparen omdat u niet het wiel opnieuw hoeft uit te
vinden, en toekomstige verbeteringen aan deze classes, zoals bug fixes en
snelheidsverbeteringen zullen ook uw code verbeteren.
Als u een class wilt gebruiken die nuttig lijkt te zijn, maar uiteindelijk blijkt dat
er wat functionaliteit mist die essentieel is voor uw code, dan kan het nog steeds
nuttig zijn om de class te gebruiken, en de missende functionaliteit te implementeren
door een afgeleide class te maken of door de class zelf aan te passen. Die laatste
mogelijkheid wordt aangemoedigd als er een goede mogelijkheid is dat de nieuwe
functionaliteit ook nuttig zal zijn in andere delen van het programma. Als u
twijfelt over waar u nieuwe functionaliteit moet plaatsen, vraag het aan de
project-beheerder. Het is veel beter om te veel te vragen over
architectuurbeslissingen dan om te weinig te vragen.
Er zijn een aantal algemene classes die altijd gebruikt moeten worden wanneer ze
van toepassing zijn:
- De CString class. Haal het niet in uw hoofd om char * strings intern
in Ultimate Stunts te gebruiken! char * strings mogen alleen gebruikt
worden binnen CString en in aanroepen naar externe bibliotheken.
- De CVector en CMatrix classes. Gebruikze voor elke 3D vector en 3*3
transformatie-matrix. Zelfs kleuren (rood/groen/blauw componenten) kunnen
behandeld worden als CVector objecten.
- De standard template library (STL) classes. Het gebruik van std::vector
templates in plaats van arrays kan u een boel geheugenbeheer-problemen
besparen.
Toevoegen van nieuwe broncode-bestanden
In het grootste deel van de Ultimate Stunts broncode is elke class in zijn eigen
bestanden geplaatst. Elke class heeft twee bestanden: een headerbestand dat de
class definitie zonder memberfunctie-implementaties bevat (behalve voor de meest
triviale functies), en een .cpp bestand dat de memberfunctie-implementaties bevat.
Het wordt op prijs gesteld als u deze gewoonte volgt. C++ broncodebestanden hebben
de .cpp extensie, niet .cxx, .c++, .cc of wat dan ook. Headerbestanden (zowel C als
C++) hebben de .h extensie. Geef bij het toevoegen de nieuwe bestanden de zelfde
opmaak als de bestaande broncodebestanden, met een bestandsnaam, een korte
beschrijving, de auteursnaam enz. (u kunt uw eigen naam invullen).
Let er elke keer op bij het toevoegen van nieuwe broncodebestanden dat ze toegevoegd
worden aan het Makefile.am bestand in de directory van het broncodebestand, en aan
het ultimatestunts.kdevelop.filelist bestand in de hoofddirectory van de Ultimate
Stunts broncode. Toevoegen aan de Makefile.am voegt ze toe aan het bouwproces, zodat
ze daadwerkelijk gecompileerd worden, en toevoegen aan ultimatestunts.kdevelop.filelist
laat gebruikers van de KDevelop omgeving zien dat deze bestanden onderdeel zijn van
de broncode.
Schoon houden van de code
Er zijn wat algemene richtlijnen voor C++ programmeurs voor het schoon houden van
hun code. Het gebruik van goed gestructureerde code houdt het beter leesbaar en
begrijpelijk voor uzelf en andere programmeurs in de toekomst, en het helpt u om
geen bugs te maken. Er zijn door anderen al een boel richtlijnen geschreven: ik zal
niet elke herhalen. Er zijn een paar belangrijke:
- Geheugencorrupties zijn een belangrijke categorie van bugs in C en C++
programma's. Zorg ervoor dat voor elk dynamisch gealloceerd ding in uw code
het helemaal duidelijk is welke code verantwoordelijk is voor het alloceren en
voor het vrijgeven van dat geheugen. Het is gebruikelijk om een class
verantwoordelijk te maken voor het geheugenbeheer van al zijn membervariabelen.
Op deze manier zorgt het aanroepen van de constructor en/of destructor van
instanties van een class automatisch voor het juiste geheugenbeheer-gedrag.
Als u ooit het geheugenbeheer op een andere manier wilt doen (zoals het ene
object aanpassingen laten doen aan de geheugenallocatie van members van een
ander object), zorg er dan voor dat u een erg goed excuus heeft.
- Bescherm membervariabelen van classes door ze protected of private te maken.
In het ideale geval kan de data van een object nooit gecorrumpeerd raken door het
aanroepen van publieke memberfuncties. In de praktijk van de Ultimate Stunts
broncode worden veel consistentie-controles niet gedaan, en membervariabelen
zijn soms public. Dit is vaak gedaan om snelheidsredenen, of uit luiheid.
- Bescherm zo veel mogelijk dingen met het const keyword. Dit maakt het
makkelijker om mogelijkheden te reduceren bij het debuggen.
Layout van de broncode
Op dit punt zou u klaar moeten zijn om een inleiding te krijgen in hoe de Ultimate
Stunts broncode is georganiseerd, zodat u uw weg er in kunt vinden.
Directories
De Ultimate Stunts broncode is opgedeeld in verschillende directories. De reden
hiervoor is dat Ultimate Stunts bestaat uit meerdere programma's bestaat (het
hoofdprogramma, het server-programma, de track editor, de 3D editor en de AI client).
Het was mogelijk om al hun broncode in een enkele directory te stoppen, maar dit zou
het niet makkelijk gemaakt hebben om de juiste bestanden te vinden waar je naar op
zoek bent. Sommige van deze directories bevatten code die specifiek is voor één van
deze programma's, en de andere directories bevatten code die gedeeld wordt tussen
programma's. Deze gedeelde code wordt gecompileerd naar statische bibliotheken, en de
programma's die deze code gebruiken worden gelinkt tegen deze bibliotheken.
Dit zijn de broncode-directories:
- shared: deze code wordt gedeeld tussen alle programma's.
Ze bevatten classes voor algemeen gebruik, bijv. voor string-manipulatie,
bestanden laden of opslaan, of data management.
- simulation: deze code wordt gedeeld tussen alle programma's die iets
doen met simulatie. Aangezien de CTrack class ook in deze directory staat, wordt
de track editor ook gelinkt met deze code. Deze directory bevat ook veel van de
netwerkcommunicatie-code.
- graphics: deze code wordt gedeeld tussen alle programma's die iets met
graphics doen. Alleen de AI client en het serverprogramma gebruiken deze code
niet. Deze code bevat zowel 3D graphics classes (zoals textures of 3D geometrie)
en 2D objecten zoals menu interface widgets.
- stuntsserver: deze code is specifiek voor het ustuntsserver programma.
Dit bevat veel delen van de server-kant van de netwerkcommunicatie, en ook het
multi-threaded model van de server.
- stuntsai: deze code is specifiek voor het ustuntsai programma.
- stunts3dedit: deze code is specifiek voor het ustunts3dedit programma.
Bevat veel functies voor het bewerken van 3D geometrie, en wat importeer-functies
voor verschillende 3D formaten.
- trackedit: deze code is specifiek voor het ustuntstrackedit programma.
- ultimatestunts: deze code is specifiek voor het ustunts programma. Dit
bevat ook alle broncode voor het geluids-subsysteem.
Classes
Ultimate Stunts bevat veel classes, en tijdens de ontwikkeling worden voortdurend
nieuwe classes gemaakt, en soms worden classes hernoemd of verwijderd. Daardoor is
het niet mogelijk om een complete lijst te geven van alle classes in de meest recente
Ultimate Stunts versie. In deze sectie worden een paar belangrijke classes genoemd.
CGameCore/CUSCore
CGameCore is waarschijnlijk de belangrijkste class om te begrijpen als u gaat werken
binnen de "game loop". Deze class levert een eenvoudige programmeerinterface aan andere
code voor het initialiseren van een spel, het starten ervan, stoppen, unloading enz..
De CGameCore class zelf behandelt alleen de simulatie, en is aanwezig in
simulation/gamecore.*. De CUSCore class in ultimatestunts/uscore.*, die is afgeleid
van CGameCore, behandelt ook geluid en graphics.
CFile/CDataFile/CFileControl
CFile is een generieke class voor het laden en opslaan van bestanden. Het kan zowel
tekstbestanden als binaire bestanden aan. Bij het lezen van tekstbestanden met de
readl() method kan het zowel UNIX-stijl als windows-stijl tekstbestanden inlezen.
De shared/cfile.h header definieert ook een paar bestandssysteem-hulpfuncties.
CDataFile, afgeleid van CFile, levert de zelfde programmeerinterface. Het verschil is
dat CDataFile niet de absolute bestandsnaam als parameter krijgt. In plaats daarvan
geeft de programmeur het pad relatief t.o.v. de Ultimate Stunts datadir. Daarna
bepaalt CDataFile automatisch de echt locatie van het bestand, en haalt het daar
vandaan. Het is in staat om op meerdere locaties op het locale bestandssysteem te
zoeken, maar het kan ook het bestand downloaden van een Ultimate Stunts game server.
Het uiteindelijke gedrag kan aardig ingewikkeld zijn, dus het is belangrijk dat dit
maar één keer geïmplementeerd wordt. Aangezien bijna elk databestand zich ergens in
de datadir bevindt, zou CDataFile bij het laden van al deze bestanden op de één of
andere manier betrokken moeten zijn. Als het laden van een bestand wordt gedaan met
een library call, zodat het fysieke pad nodig is i.p.v. een CDataFile object, dan
is de useExtern method nuttig.
Het gedrag van CDataFile wordt aangestuurd door een instantie van de CFileControl
class. Er hoort maar één instantie te zijn van deze class. Het gedrag moet veranderd
worden bijv. als er een verbinding gemaakt wordt met een Ultimate Stunts game server,
om het mogelijk te maken om automatisch bestanden te downloaden.
CDataObject/CDataManager/CWorld/CGraphicWorld
Ultimate Stunts moet een boel verschillende data objecten laden: tracks, textures,
tiles, auto's enz., en er zijn veel afhankelijkheden tussen deze bestanden. Om deze
afhankelijkheden efficiënt te beheren, en om er voor te zorgen dat objecten niet
twee keer geladen worden, zijn alle object typen afgeleid van CDataObject
(shared/dataobject.*). Classes zoals CTexture of CTrack zijn allemaal afgeleid van
CDataObject.
Een CDataObject object wordt geïdentificeerd met het type (een enum-type variabele),
de bestandsnaam en een aantal parameters. Normaal gesproken zijn CDataObject objecten
verbonden aan een CDataManager object. Het object wordt aangemaakt door het
CDataManager object als het een aanvraag krijgt voor het object, en het wordt
verwijderd door het CDataManager object wanneer dat nodig is. Ook wordt er een pointer
naar het CDataManager object meegegeven aan de CDataObject constructor, zodat het
CDataObject object aan het CDataManager object kan vragen om bestanden waar het van
afhankelijk is. Als de opgevraagde bestanden al geladen zijn, dan wordt een pointer
naar het al eerder geladen object teruggegeven, zodat objecten nooit twee keer geladen
worden.
Er zijn verschillende instanties van CDataManager-afgeleide classes in een Ultimate
Stunts spel-sessie. De meest centrale is de instantie van CWorld (simulation/world.*).
In CWorld::createObject kunt u zien hoe het type-enum verbonden wordt aan class typen
in CWorld. De meeste zijn verbonden met een bepaalde class, maar bijv. het eSample
type is verbonden met een leeg CDataObject. Dit is zo omdat Ultimate Stunts op dit
niveau niets doet met de informatie uit geluidsbestanden, het slaat alleen de
bestandsinformatie op zodat het geluids-subsysteem weet welke bestanden in een later
stadium geladen kunnen worden.
Een andere CDataManager-afgeleide instantie is van het type CGraphicWorld
(graphics/graphicworld.*). Terwijl CWorld alleen de data laadt die nodig is voor het
simulatie-subsysteem, laadt CGraphicWorld alle data die nodig is voor het
graphics-subsysteem. Dat is waarom het enum-type van objecten in CGraphicWorld is
verbonden met andere classes dan in CWorld: in CWorld zou eTileModel verbonden zijn
met een tile botsings-model, maar in CGraphicsWorld is het verbonden met een
CGraphObj, dat de grafische mesh-data van een tile bevat. Vanwege deze scheiding is
het mogelijk om het server-programma alle simulatie-data te laten laden zonder
dat het nodig is om alle grafische data te laden.
CWinSystem
In Ultimate Stunts levert de CWinSystem class (graphics/winsystem.*) een interface
naar het graphics venster. Het initialiseert het venster en beheert een aantal events,
zoals resize events, toetsenbord- en muis-invoer events, en joystick invoer. Het
heeft twee methods met de naam runLoop; beide starten een loop die reageert op events.
Één van deze communiceert met de rest van het programma via een callback-functie: dez
wordt gebruikt in het spel zelf. De andere is gebaseerd op een CWidget object, en wordt
gebruikt in de menu-gebruikersinterface.
CWinSystem heeft ook twee systemen voor het bepalen van de toestenbord-toestand (drie
als we het widget-systeem meetellen, maar die hoort niet op het zelfde moment gebruikt
te worden als de andere twee systemen). Één methode, de getKeyState(..) method,
geeft de huidige toestand van een toets (ingedrukt of losgelaten), wat handig is
voor toetsen die gebruikt worden als spel-invoer, zoals de pijltjestoetsen voor het
besturen van een auto. De andere method, wasPressed(..), geeft terug of een toets was
ingedrukt sinds de laatste aanroep van wasPressed. Dit is handig voor toetsen die een
meer event-achtige aard hebben, zoals de escape-toets voor het verlaten van een spel.
En, om dingen nog wat ingewikkelder te maken, er is een afgeleide class CGameWinsystem
(ultimatestunts/gamewinsystem.*) die een aantal functies levert voor het bepalen van
de toestand van gebruiker-gedefinieerde toetsen.
CWidget/CGUIPage/CGUI
De menu-gebruikersinterface is gebaseerd op een event-gebaseerd widget-systeem.
Elg gebruikersinterface-element, zoals een lijst van opties, of een message box,
is gebaseerd op een van CWidget afgeleide class. Zo'n widget is verantwoordelijk
voor het updaten van zijn deel van het scherm, en voor het op de juiste manier
afhandelen van invoer-events. Events worden aan een widget doorgegeven door methods
van het object aan te roepen, en een widget kan deze events afhandelen door
overloaded versies van deze methods te hebben. Bijvoorbeeld, de meeste widget-types
zullen een overloaded onRedraw method hebben, die aangeroepen zal worden als de
widget opnieuw getekend moet worden. Als een widget sub-widgets bevat, dan is de
widget verantwoordelijk voor het aanroepen van de event-methods van de sub-widgets.
Alle grafische uitvoer wordt gedaan met OpenGL, alle invoer-parameters (bijv.
toetsenbord toetsen-waarden) worden gedaan met SDL constanten.
Een veelgebruikt widget-type is de CGUIPAge class. Het bevat een achtergrond en
een titel, en een aantal sub-widgets. De sub-widgets zijn van onder naar boven
gesorteerd en worden in die volgorde getekend, en de bovenste widhet heeft de
toetsenbord- en muis-invoerfocus. Meestal is de top-level widget van het CGUI type,
en zijn enige sub-widget is van het CGUIPage type.
CGUI is een widget-type dat slechts één enkele full-screen CGUIPage sub-widget heeft.
Het heeft enkele features die het nuttig maken als een top-level widget. Bijvoorbeeld,
het bevat methods om te switchen tussen 2D en 3D mode in OpenGL, en het bevat methods
voor het eenvoudig maken van message boxes en andere veelgebruikte
gebruikersinterfacecomponenten.
CLConfig
De CLConfig class (shared/lconfig.*) wordt gebruikt voor .ini-stijl
configuratiebestanden in Ultimate Stunts, zoals ultimatestunts.conf en de
auto-configuratiebestanden. Doordat Ultimate Stunts in een vroeg ontwikel-stadium
was toen dit werd geïmplementeerd, is het niet gebaseerd op CFile. Het is ook niet
gebaseerd op CDataFile, omdat ultimatestunts.conf geladen moet worden voordat bekend
is waar de datadir locaties zijn, omdat deze informatie in ultimatestunts.conf is
opgeslagen.
Een Ultimate Stunts spel in de broncode volgen
U zult waarschijnlijk veel leren over de Ultimate Stunts broncode door te zien
welke functies waar aangeroepen worden in een Ultimate Stunts spelsessie. Het
scenario dat in deze sectie gevolgd wordt is als volgt: een speler start Ultimate
Stunts, klikt op "Race!" om een spel te starten, speelt totdat hij/zij finisht, en
verlaat dan het programma. Het is sterk aangeraden om de bijbehorende broncodebestanden
te lezen bij het lezen van deze tekst.
ultimatestunts/main.cpp
Net zoals bij elk ander C/C++ programma begint de uitvoering in de main functie. Voor
het ustunts-programma bevindt deze zich in ultimatestunts/main.cpp. U kunt zien dat
het in feite vrij kort is:
- shared_main wordt aangeroepen
- een CGameWinSystem en CGameGUI object worden gemaakt
- De start method van het CGameGUI object wordt aangeroepen
- De CGameWinSystem en CGameGUI objecten worden vernietigd
- main functie wordt verlaten
De shared_main functie (in shared/usmisc.cpp) stelt een paar algemene dingen in.
Bijvoorbeeld, het vindt ultimatestunts.conf en het laadt het, en het stelt de
gettext localisatie-instellingen in. Het maken van het CGameWinSystem object
(afgeleid van CWinSystem in graphics/winsystem.cpp) creëert een venster voor
Ultimate Stunts, overeenkomstig de instellingen in het theMainConfig object, dat
een CLConfig object is dat geladen is van ultimatestunts.conf. Het venster zal
vernietigd worden zodra het CGameWinSystem object vernietigd wordt, aan het einde
van de main functie.
Al het interessante werk gebeurt in de CGameGUI::start method. Deze functie zal pas
verlaten worden wanneer de speler beslist om Ultimate Stunts af te sluiten.
ultimatestunts/gamegui.cpp
Het eerste dat we deden met het CGameGUI object was het aanroepen van de constructor.
CGameGUI::CGameGUI(..) doet verschillende dingen, maar het meeste heeft te maken met
het initialiseren van de verschillende pagina's van het spel-menu en de menu-teksten.
Een ander belangrijk ding om te zien is dat het een instantie maakt van de CUSCore
class, die pas zal worden vernietigd wanneer het CGameGUI object wordt vernietigd.
Het tweede wat we deden was het aanroepen van CGameGUI::start(). Deze method brengt
OpenGL eerst in 2D mode door CGUI::enter2DMode() aan te roepen. Daarna voert het een
while-loop uit. Binnen deze loop worden de verschillende delen van de menus
geïdentificeerd met string-waarden, en ze zijn allemaal geïmplementeerd met hun eigen
CGameGUI method. Elke menu-method retourneert de string identifier van het volgende
menu dat moet worden weergegeven. In ons voorbeeld is section in eerste instantie
"mainmenu", zodat viewMainMenu() aangeroepen wordt. Wanneer de speler de "Race!"
knop selecteert, geeft viewMainMenu() het resultaat "playgame" terug, zodat de
playGame() method aangeroepen wordt. Zodra het spel is afgelopen, komt playGame()
terug met de waarde "hiscore", enz., totdat de speler "Afsluiten" selecteert in het
hoofdmenu. Als de speler dan bevestigt dat hij/zij echt wil afsluiten, dan wordt de
loop verlaten, leave2DMode() wordt aangeroepen, en start() method wordt verlaten.
CGameGUI::viewMainMenu() laat een eenvoudige implementatie van een spelmenu zien.
Eerst wordt het juiste child widget geselecteerd, dan wordt het CGameWinSystem object
geïnstrueerd om de event loop te starten, en wanneer die klaar is, wordt het
resultaat geïnspecteerd en verwerkt. De CWinSystem::runLoop(CWidget *widget) method
die hier gebruikt wordt bevat een loop die continu pollt naar SDL events, en die de
event handler methods van de widget aanroept wanneer ze zich voor doen. De loop wordt
verlaten als een event handler een return-waarde terug geeft die de WIDGET_QUIT vlag
bevat. Dit gebeurt bijvoorbeeld als de speler op de "Race!" menu-optie klikt. Dan
wordt runLoop verlaten, en de uitvoering gaat verder in viewMainMenu, waar deze keuze
verwerkt wordt.
CGameGUI::playGame() is wat anders dan de andere menus, omdat het een spel opzet,
het spel laat draaien, en dan het spel weer opruimt. De method zelf is vrij eenvoudig:
- CGameGUI::load() wordt aangeroepen
- CGUI::leave2DMode() wordt aangeroepen
- Het spel draait door de CWinSystem::runLoop method
- CGUI::enter2DMode() wordt aangeroepen
- CGameGUI::unload() wordt aangeroepen
CGameGUI::load() doet het vuile werk van het opzetten van het spel, gebaseerd op
de invoer van de speler in de verschillende sub-menus. Het roept een boel andere
dingen aan in het m_GameCore object, en soms ook in het m_Server object (dit is in
ons scenario niet het geval, omdat we een lokaal spel spelen). CGameGUI::unload()
doet het minder vuile werk van het opruimen van alles.
We dalen steeds dieper af in de kern van Ultimate Stunts. De aanroep van
CWinSystem::runLoop in playGame() krijgt de kleine functie game_mainloop() mee als
een callback-parameter. game_mainloop() (in ultimatestunts/gamegui.cpp) doet niets
anders dan het aanroepen van de update() method in het CUSCore object. Zolang deze
functie true teruggeeft, gaat CWinSystem::runLoop verder mett het pollen naar events
en het aanroepen van game_mainloop(). Dus, nu is alle controle in handen van het
CUSCore object.
ultimatestunts/uscore.cpp
Het laden van alle bestanden wordt gedaan door een aanroep van CUSCore::readyAndLoad()
vanuit CGameGUI::load(). Als eerste roept CUSCore::readyAndLoad() de zelfde method
aan van de base class, CGameCore::readyAndLoad(). Deze method initialiseert dingen
zoals netwerk-communicatie, en laadt dan alle data door loadTrackData en loadMovObjData
aan te roepen. Deze methods, die ook overloaded zijn in CUSCore, laden de data van de
track en de bewegende objecten. In de overloaded versies van CUSCore wordt de
bijbehorende graphics en geluids-data ook geladen. Het laden van de track data in
CGameCore::loadTrackData() wordt eenvoudigweg gedaan door het m_World object de
opdracht te geven om het track-object te laden. Alle afhankelijkheden, zoals tiles,
worden automatisch opgelost doordat CWorld is afgeleid van CDataManager. Het laden
van bewegende objecten is iets gecompliceerder, omdat in een multiplayer-spel de
remote spelers ook auto's kunnen toevoegen. Het managen van het laden van bewegende
objecten is de verantwoordelijkheid van CPlayerControl en de daarvan afgeleide classes.
In ons scenario wordt alleen een lokaal spel gespeeld, dus m_PCtrl is van het type
CPlayerControl, en de implementatie van CPlayerControl::loadObjects() is vrij
eenvoudig. Ook hier is het laden van objecten weer gebaseerd op de
CDataManager::loadObject method van de CWorld class.
Het laden van de grafische data wordt gedaan wanneer CUSCore::loadTrackData() en
CUSCore::loadMovObjData() klaar zijn met het aanroepen van hun CGameCore equivalenten,
en de methods loadTrackdata() en loadObjData() van het m_Renderer object aanroepen.
Zoals u kunt zien in ultimatestunts/gamerenderer.cpp, roepen deze vergelijkbare methods
aan in een CGraphicWorld object. En in graphics/graphicworld.cpp ziet u hoe deze
methods alle grafische data laden op basis van de al eerder geladen data in het
CWorld object.
Zodra alles is geladen, zal CUSCore::update() herhaald worden aangeroepen.
CUSCore::update() doet alle dingen die nodig zijn in een spel-periode. Het
controleert de toestand van sommige toetsen, het updatet de spel-simulatie door
CGameCore::update() aan te roepen, en tot slot updatet het alle uitvoer-elementen,
zoals graphics en geluid. Dus, de kern van de simulatie is in CGameCore::update().
CGameCore::update() doet veel dingen die alleen relevant zijn voor netwerkcommunicatie
in multiplayer-spellen, maar de kern-activiteiten zijn het aanroepen van de update()
methods van alle objecten in m_Players en m_Simulations. De methods van de m_Player
objecten zorgen er voor dat de verschillende spelers hun acties toepassen.
Dit kan bijvoorbeeld de routines van een AI speler bevatten, of het controleren van
de toestand van invoer-apparaten voor een menselijke speler. De update() methods van
objecten in m_Simulations bepalen het gedrag van de bewegende objecten. In ons
scenario zijn er drie simulatie-objecten: één CRuleControl object, dat alle
spelregels implementeert, zoals penalty-tijd of finishen, één CPhysics object dat
alle fysische berekeningen doet, en een CReplayer object dat de toestanden van alle
objecten vastlegt in een replay-bestand.
Als de speler finisht, wordt dit gedetecteerd door het CRuleControl object
(simulation/rulecontrol.*), en een korte tijd na het finishen zal zijn update method
false terug geven. Dit zorgt er voor dat CGameCore::update() en CUSCore::update()
false terug geven. Dit zorgt er voor dat het CWinSystem object de runLoop-functie
verlaat, en de speler zal terug keren in de menu interface.