Hobby: simpele 2D RTS game maken in C

Door Gamebuster op donderdag 19 december 2013 12:44 - Reacties (10)
Categorie: Hobby RTS, Views: 4.042

Waarschuwing: Deze blogpost bevat veel tekst.

Ik ben op dit moment veel bezig in mijn vrije tijd met de ontwikkeling van een 2D RTS game.

Al sinds 2010 heb ik een bepaald idee in mijn hoofd en het was al die tijd al mijn droom geweest om die game uit te werken. Al die jaren heb ik projectjes gestart om mijn game uit te werken; allen zijn echter niet ver gekomen.

Oorspronkelijk wilde ik de game maken in Javascript met HTML5 canvas, maar mijn ideeen waren veruit te complex om in Javascript uit te werken. Ik ging me verder verdiepen naar andere omgevingen om mijn game in te maken. Flash, Javascript + Flash canvas, Lua met Love2D, Java + diverse 2D/3D game engines, Unity3D en vast nog wel meer. Alle frameworks en talen die ik geprobeerd had vond ik niet geschikt voor mijn game.

http://i.imgur.com/WFdcIFv.png

In al die omgevingen en talen had ik diverse prototypes gemaakt. Om het interessant te houden voor mezelf had ik per prototype de focus gelegd op iets nieuws. De ene focuste ik me op rendering, de ander op pathfinding, ga zo maar door. In de 3 jaar dat ik eraan heb gewerkt heb ik vele simpele prototypes gemaakt die allen samen een zeer basic game kunnen vormen. Ik was echter nog steeds niet tevreden met alle frameworks gecombineerd met de talen waarin ik ze het gebruikt.

Niet gerelateerd aan mijn game had ik me tussendoor ook verdiept in berekeningen uitvoeren op de videokaart met OpenCL. In combinatie met Java + LWJGL had ik wat simpele demo's geschreven in OpenCL voor mezelf. Het begon met wat simpele berekeningen en later maakte ik een demo waarbij ik OpenCL gebruikte om naar vertex buffers te schrijven en te renderen met OpenGL.

Mijn game knaagde nog steeds aan me. Ik besloot mijn game te maken in C + OpenGL + OpenCL. Geen grote frameworks, geen IDE's, geen tutorials, gewoon alleen C + OpenGL + OpenCL.

Ik heb me vaker verdiept in C, OpenGL en OpenCL. Ik heb ze echter nooit allemaal in 1 project tegelijk gebruikt. OpenGL en OpenCL kan ik iets werkend mee maken. Ik ben er verre van een expert in, maar ik probeer altijd uit te zoeken wat iedere functie doet en wat de beste manier is om deze te gebruiken. Verder probeer ik altijd de code zodanig te schrijven dat er ruimte is voor optimalisatie. (proberen kan altijd he)

C is echter een ander verhaal. De basis syntax is niet zo spannend, maar er zitten een hoop dingen in C die je niet terugziet in andere talen die ik ken. Ik heb meerdere pogingen gedaan om C te snappen, maar echt begrijpen deed ik het nooit. Ik kon er *iets* in schrijven, ik had geen moeite met de syntax, maar de functie van header-files waren mij een raadsel en zelf compilen was pure magie voor mij.

Dit veranderde toen ik simpelweg 3 dingen ging doen:
- Mijn IDE wegflikkeren en Makefile tutorial opzoeken.
- "Learn C The Hard Way".
- C11 specificatie lezen. Ja echt, ik heb een groot deel van de C11 specificatie vlugjes doorgelezen. D.w.z. dat ik niet woord voor woord heb gelezen maar wel globaal dingen heb opgepikt.

Al gauw leerde ik waarom header files nuttig zijn, de basics van compilen en wat ongeveer de gedachten rondom pointers is.

Met deze kennis ging ik ambitieus aan de slag met mijn game in C, OpenGL en SDL voor crossplatform window/input management. Ik begin mijn project zo simpel mogelijk. Ik bepaal een stap die ik wil behalen, schrijf de stap zo simpel mogelijk uit, maak het werkend, debug het om te kijken of alles goed draait, dump het in GIT en bepaal de volgende stap.

Per stap schrijf ik pseudocode uit. Ik omschrijf wat er moet gebeuren, maak er een functienaam van en bepaal vervolgens in pseudocode wat er in die functie moet gebeuren. Dit blijf ik recursief toepassen tot de functie dusdanig simpel is dat ik 'm direct kan uitschrijven.

Wanneer de code werkt pak ik een debugger erbij, ga ik de code een klein beetje optimaliseren zonder de leesbaarheid veel te verminderen. Ik ga alle code nog eens na of alles een beetje doet wat ik wil. Als alles goed lijkt te werken ga ik verder met de volgende stap.

Dit heb ik nu enkele keren gedaan. Mijn stappen waren:
- Maak een basis project met een game-loop die enkel een venstertje toont.
- Teken enkele vierkantjes in beeld die heen-en-weer gaan. Hiermee test ik de timers in de game-loop.
- Voeg textures to aan de vierkantjes.
- Stap af van OpenGL direct mode naar VBO's.
- Handel basic input af, reageer op "sluiten" en laat pijltoetsen de "view" verplaatsen. (panning)

Deze stappen zijn allemaal uitstekend verlopen. Ik heb nu een venster met 100.000 bewegende sprites vloeiend draaiend en kan rondkijken met pijltoetjes.

De volgende stap is het toevoegen van wat logica. Ik ben teruggestapt naar 4 "sprites". Iedere sprite stelt een unit voor. Het doel van deze units is elkaar vernietigen. Dit heb ik verdeeld in 2 stappen:
- Maak logica waarbij de units kunnen bepalen op welke ze moeten schieten, hoe ze daarheen moeten vliegen en wanneer ze in bereik zijn om te schieten.
- Laat de units daadwerkelijk schieten. Dit zorgt voor het eerst voor een variabel aantal sprites in mijn code en is daarom een aparte stap. In het ontwerp van mijn code ben ik uitgegaan van een variabel aantal "entities", maar in praktijk is het aantal nog altijd "vast" gebleven.

De 1e stap hiervoor is bijna compleet: De units lijken wat te doen en zoeken elkaar daadwerkelijk op. De code hiervoor kon ik vrijwel 1-op-1 uit een oud prototype van mij overnemen. Toen ik de code draaide kwam ik erachter dat er iets niet helemaal goed ging: alle vliegtuigjes vlogen zijwaards, namen verre van optimale paden naar hun vijand toe of vlogen in eindeloze cirkels. Waarom wist ik niet, omdat de code wel werkt in mijn prototype.

Na wat testen kwam ik erachter: omdat ik telkens code baseer uit oudere prototypes, werkt de code niet goed samen. In het ene prototype was 0,0 de linker bovenhoek van het scherm, in de ander was het de linker onderhoek. Ik heb daarom besloten 1 standaard aan te houden en alle code gerelateerd aan hoekberekeningen en de coordinaten van units langs te gaan om te kijken of die zich goed houdt aan de standaard.

In deze blog zal ik regelmatig updates plaatsen van mijn game. Ik heb veel projecten gestart en niet afgemaakt, maar omdat ik al 3 jaar met deze bezig ben, heb ik blijkbaar een passie voor dit project. Wie weet blijft die passie en maak ik het deze keer wel af. Het zit al maanden lang vrijwel dagelijks in mijn hoofd.

Het uiteindelijke plan is een 2D RTS game waarbij snelle actie en grote hoeveelheden units mijn primaire doel is. Ik probeer dan ook zoveel mogelijk logica zodanig te ontwerpen dat het te parallelliseren is in OpenCL. Ik hoop op die manier tot in de 10.000 units in beeld te kunnen tonen en in de 100.000 units in-game te hebben.

Multiplayer ga ik me nog niet aan wagen, maar ik hoop dat wel ooit werkend te krijgen.

Volgende: 2D RTS: Sterrenhemel 12-'13 2D RTS: Sterrenhemel
Volgende: A4 afrit Hoofddorp 12-'13 A4 afrit Hoofddorp

Reacties


Door Tweakers user RoadRunner84, donderdag 19 december 2013 14:34

"C is portable assembler"

ofwel, in C denk je in functies van je processor, niet in functionele blokken. Deel daarvan zijn de oh zo charmante (maar helaas gevaarlijke) pointers; je processor kent geen referenties, objecten of arrays, dus moet je werken met geheugenadressen.

Headers of .h bestanden zijn vaak niet meer dan beloften: "bij dezen beloof ik plechtig dat dit gecompileerde stukje code straks gelinked wordt aan een stukje gecompileerde code dat functie abc met parameters d en e van type f en g accepteert".

Ik geloof trouwens niet dat je iets maakt dat te complex is om in JavaScript uit te drukken. JavaScript heeft alle mogelijkheden die je zoekt, al ken je wellicht niet alle constructies en paradigmen om het te realiseren.

Door Tweakers user Gamebuster, donderdag 19 december 2013 14:45

Javascript is een leuk taaltje, het ligt ook niet aan de taal Javascript (of LUA, Java). Het is puur dat de environment (browser) niet echt geschikt is voor wat ik ermee wil doen. WebGL, 2D Canvas of DOM; ik kan er geen vloeiende game-ervaring in maken.

In C heb ik nu voor het eerst een volledig vloeiende animatie in beeld gekregen. Ik heb zoveel mogelijk memory allocations uit de main-loop gehaald waardoor er geen micro-stutter meer voorkomt in de game. Ik hou van die controle over mijn applicatie.

Ik moet echter nog gaan werken met variable aantal units in mijn game, dus dat zal een memory heap worden. Ik ga nog kijken of ik in 1 keer een mega-heap reserveer of dat ik on-the-fly de heap zo-nodig vergroot/verklein en in hoeverre ik die heap-resize in een andere thread kan/moet doen om "stutter" te voorkomen.

Mijn units zijn vrij simpel in geheugengebruik. Als ik de benodigde ruimte per unit op 1kB zou krijgen dan kan ik makkelijk 120.000 units kwijt op 128MB. Veel meer ruimte mag ik denk niet gebruiken: Ik wil het uiteindelijk grotendeels in OpenCL zetten en dat betekend dat de gehele set aan units op de videokaart gezet moet worden. Ik wil videokaarten met 256MB vram ondersteunen. Er zal vast de nodige overhead zijn dus ik denk dat 128MB een veilige gok is als memory heap. De rest is dan voor sprites, textures en vast nog wel meer dingen.

Op dit moment gebruik ik iets minder dan 200 bytes per "unit", incl. de vertex buffers.

- Maar dat is allemaal een zorg voor later. :)

[Reactie gewijzigd op donderdag 19 december 2013 16:30]



Door Tweakers user Gamebuster, donderdag 19 december 2013 16:54

Ik ontwikkel het in OSX op mijn macbook uit 2009, maar het wordt cross-platform. Alles wat OpenGL2+, OpenCL1+ ondersteund en waar SDL2 ondersteuning voor biedt zou moeten draaien.

Linux valt daar ook onder. Ik ben een grote voorstander van crossplatform software dus ik zal zeker mijn best doen om het allemaal goed te laten werken, ongeacht welk OS je ook gebruikt. Ik ga daar echter nu nog niet naar kijken; ik wil eerst een werkend iets hebben dat op een game lijkt. Mocht dat al ooit lukken, dan ben ik zeker al wel een jaartje verder.

[Reactie gewijzigd op donderdag 19 december 2013 16:58]


Door Tweakers user lennart111, vrijdag 20 december 2013 01:22

even voor de lezers: wat wat is de stijl van de rts? hoe ga je om met unit creation en resource gathering?
of stel ik nu vragen die nog helemaal niet aan de orde zijn?
een andere mogelijkheid die wat meer in de lijn van de screenshot ligt is een predefined missie met objectives, zijn er trouwens nog verschillende soorten units of is dat voor later zorg.

ik weet nu al dat ik dit project met veel interesse ga volgen.

Door Tweakers user Nimrodx, vrijdag 20 december 2013 09:33

Leuk initiatief, erg interessant ook.
..., dump het in GIT en bepaal de volgende stap.
Is je RTS opensource? Gebruik je toevallig Github of iets dergelijks? Zo ja dan wil ik wel eens een kijkje nemen in je code als je het goed vindt.
In C heb ik nu voor het eerst een volledig vloeiende animatie in beeld gekregen. Ik heb zoveel mogelijk memory allocations uit de main-loop gehaald waardoor er geen micro-stutter meer voorkomt in de game. Ik hou van die controle over mijn applicatie.
Iets waar ik in C altijd veel moeite mee heb is memory management. Je hebt geen Garbage collector zoals in .NET. Houd er rekening mee dat je constant moet profilen of je applicatie geen geheugen lekt. Je kunt er niet vroeg genoeg mee beginnen want je core moet stabiel zijn en goed performen(zowel processor als geheugen gebruik).

Veel succes.

edit: typo

[Reactie gewijzigd op vrijdag 20 december 2013 09:34]


Door Tweakers user Gamebuster, vrijdag 20 december 2013 10:52

lennart111 schreef op vrijdag 20 december 2013 @ 01:22:
even voor de lezers: wat wat is de stijl van de rts? hoe ga je om met unit creation en resource gathering?
of stel ik nu vragen die nog helemaal niet aan de orde zijn?
een andere mogelijkheid die wat meer in de lijn van de screenshot ligt is een predefined missie met objectives, zijn er trouwens nog verschillende soorten units of is dat voor later zorg.

ik weet nu al dat ik dit project met veel interesse ga volgen.
Ik wilde het allemaal toevoegen, maar dan zou mijn blogpost nog langer worden. Ik heb best veel ideeen die je niet terugziet in andere RTS games, laat staan allemaal in 1 game. Hier ga ik nog een keer een uitgebreide blogpost aan wijden.

"Kort" samengevat van de ideeen:
- Veel features zijn geinspireerd uit TA/SupCom. Denk aan de besturing van de camera en de verschillen in grootte tussen de units in het begin van de game en later in de game.
- Je kan je eigen units ontwerpen met een unit-editor a la spore's creature editor of Kingdom Hearts's Gummi Ship designer, alleen dan een stuk simpeler en 2D. (drag 'n drop op een grid) De componenten die in je unit zitten bepalen hoe sterk, snel e.d. een unit is, hoe duur het is om deze te maken en in wat voor "fabriek" je deze kan maken. Er zijn geen gebouwen; alles is een unit, incl. je fabrieken e.d.
- Er is geen "game-wide" opslag van resources. Alles wat je verzameld moet je zelf verspreiden onder al je units. Dit kan je grotendeels automatiseren, maar dit betekend wel dat als je unit een shitload aan resources aan boord heeft, de vijand deze resources zou kunnen onderscheppen. (een beetje zoals bij de Anno-reeks) Met name Mass is vrijwel altijd voor een groot deel uit "dode" units op te nemen, zowel de mass die nodig was om de unit te maken als de mass die opgeslagen was in de unit's mass storages. Het terugwinnen van die mass zal ingame een grotere rol spelen dan in TA/SupCom en zal dan ook goed te automatiseren zijn.
Nimrodx schreef op vrijdag 20 december 2013 @ 09:33:
Leuk initiatief, erg interessant ook.
Tnx :)
Is je RTS opensource? Gebruik je toevallig Github of iets dergelijks? Zo ja dan wil ik wel eens een kijkje nemen in je code als je het goed vindt.
Nu nog niet. Ik ben er nog niet uit of ik het opensource ga maken of niet. Ik wil eerst kijken hoever ik kom. Bovendien is de code nu nog niet zo interessant, gezien het nog niet eens fatsoenlijk werkt.

Zodra ik iets werkend heb, zal ik zeker overwegen om het in github te zetten.
Iets waar ik in C altijd veel moeite mee heb is memory management. Je hebt geen Garbage collector zoals in .NET. Houd er rekening mee dat je constant moet profilen of je applicatie geen geheugen lekt. Je kunt er niet vroeg genoeg mee beginnen want je core moet stabiel zijn en goed performen(zowel processor als geheugen gebruik).
Ik heb het nu al een paar keer door een profiler gehaald en kwam toen inderdaad al wat lekjes tegen. Na enkele iteraties door de profiler heb ik het aantal memory allocations tot 0 gebracht in mijn main-loop, alleen de videokaart driver doet nog wat allocations bij enkele gl* calls, maar die leek het ook weer netjes op te ruimen.

Uiteraard heb ik nu nog geen game. Het huidige plan is te werken met een fixed-size memory heap. Dit is conform het idee dat ik het zwaarste rekenwerk op de videokaart wil neerleggen met OpenCL. Doordat je zelf je units kan maken die allemaal bestaan uit een vooraf bepaalde set aan componenten, kan ik de logica achter ieder component (indien nodig) parallel uitvoeren in OpenCL. Ik moet nog zien hoe dit in praktijk gaat uitvallen, maar ik heb de psuedocode voor dit scenario grotendeels uitgewerkt.

[Reactie gewijzigd op vrijdag 20 december 2013 10:57]


Door Tweakers user Rick_A, vrijdag 20 december 2013 13:12

Hoi,

Ik ben benieuwd waar dit heen gaat.

Een kleine tip, met nieuwe OpenGL versies kun je ook compute shaders maken. Ik weet niet zeker in hoeverre dat al door OSX wordt gesupport, maar afhankelijk van je tijdlijn, is dat misschien een makkelijkere optie dan om data heen en weer tussen OpenCL en OpenGL te schuiven.

Door Tweakers user Gamebuster, vrijdag 20 december 2013 13:43

Ik weet niet precies wat "compute shaders" zijn, maar het heen en weer schuiven van data tussen OpenCL en OpenGL gaat vrij makkelijk; ik kan me niet voorstellen hoe dit makkelijker zou kunnen gaan. Ik zal er toch eens naar kijken. Bedankt voor de tip.

Door Tweakers user Gmount, zondag 22 december 2013 14:03

Gaaf, Ik heb zelf altijd veel Command & Conquer gespeeld en vind het nog steeds leuk om spelletjes strategisch aan te pakken ipv bruut binnenknallen :)

Reageren is niet meer mogelijk