BELTRUNNER: een game design postmortem

Vorige week deel ik een arcade-getrouwe versie van Asteroids om de volledig nieuwe manier van games maken te demonstreren waaraan ik heb gewerkt. Het heet Jinks en het is een lichtgewicht game-engine, een interactieve ontwikkelomgeving, een soort taal, en meer.

BELTRUNNER is een originele game die is gebouwd op dezelfde basis als Asteroids, en was mijn inzending voor de #GMTKjam 2026. Het geheel bestaat uit ongeveer 1.300 regels code en is, wat belangrijk is, een complete game die je kunt uitspelen. Het maakt gebruik van hetzelfde systeem en dezelfde beknopte taal als mijn Asteroids-recreatie, hoewel ik voorlopig nog wat vaag blijf over de mechanica omdat dit nog een werk in uitvoering is en dagelijks verandert. Ik moet wachten tot het voldoende gestabiliseerd is zodat ik kan beschrijven wat het is, in plaats van wat het was.

Voor nu wil ik praten over hoe de game zich heeft ontwikkeld: welke ideeën erin zijn gekomen, welke niet, en wat Jinks onderweg moest leren. Veel van de ontwerpmethoden zijn aangepast op basis van lessen die ik heb geleerd door oude games te spelen; dat is hoe ik game design het liefst benader.

Verkenningen

Het eerste prototype leek in niets op Asteroids. Ik bouwde een orbitale shooter, wellicht meer zoals Space Duel, met genummerde doelen op vaste schillen rondom een centrale zon. Zoals alles wat in Jinks is gemaakt, was het snel operationeel, maar het spelen ervan was simpelweg niet leuk. Ik moest beslissen of het genoeg potentie toonde om door te gaan, en dat denk ik niet. Ik zeg altijd dat geen enkel werk echt verloren is; het wordt simpelweg opzij gezet tot het nut of de geschiktheid duidelijk wordt.

De vaste banen maakten van de doelen 'sitting ducks'. Wat ik miste uit Asteroids was de onvoorspelbare drift van de rotsen en het ontwijken dat daarbij komt kijken. Ik liet de orbitale structuur vallen, maar behield de genummerde doelen, het markeren (highlighting) en het idee van een keten. Het zette me ook aan het denken over het nummer “Countdown” van Phoenix (Bankrupt!, 2013).

Dus bewoog ik terug richting Asteroids en veranderde ik de genummerde pickups in iets dat meer leek op de ringen in Pilotwings of de poorten in Wave Race 64. Ik leende ook een truc uit Donkey Kong '94: doe aanvankelijk alsof je een oud, bekend spel bent, om vervolgens te onthullen dat er iets anders en nieuws achter schuilging.

Maskerade

Wave 1 is pure Asteroids: één grote rots, klassieke thrust-and-drift, klassieke splitsingen, klassieke dood-en-respawn. De enige aanwijzing dat de dingen niet zijn wat ze lijken, is een raceklok die verschijnt zodra je de eerste hit scoort, en het feit dat je bij je eerste botsing geen levens verliest. Ruim het veld op en in plaats van een volgende wave rotsen, scheurt er een zwart gat open in het midden van het scherm. Zwaartekracht trekt je naar binnen, en je kunt een kleine dash gebruiken om dit te versnellen.

Vanaf wave 2 onthult het echte spel zich. Genummerde poorten zijn verspreid over hetzelfde driftende rotsveld en moeten in aflopende volgorde worden gepasseerd—5… 4… 3… 2… 1…—zodat de race een countdown zingt terwijl je erdoorheen vliegt. Neem de laatste poort en het zwarte gat opent opnieuw, waarna je naar de volgende wave warpt.

Door op deze manier te beginnen, kan de speler de besturing leren voordat de race hem vraagt om iets complexers te doen. Als je Asteroids al kent, kun je de eerste wave vliegen op basis van spiergeheugen. Zo niet, dan krijg je alsnog tijd met het centrale game-mechanisme.

Twee countdowns convergeren naar nul:

  1. De poortentelling, die jij bestuurt.
  2. De raceklok, die jou drijft—elke poort voedt deze en elke botsing put hem uit.

Er zijn geen levens, dus tijd wordt de enige hulpbron. Dit maakte veel van de klassieke arcade-administratie overbodig en liet me over met één ding dat in één oogopslag leesbaar is. Dit werkte goed en bleef behouden tijdens elke playtest.

Golven met stapsgewijze moeilijkheidsgraad (op de Nintendo-manier)

Het spel heeft zestien waves, verdeeld in vier acten van vier. Ik wilde geen eindeloze moeilijkheidscurve die stilletjes plateauy gaat; ik wilde een spel maken dat je daadwerkelijk kunt voltooien.

Elke act leert één nieuw poortgedrag, en besteedt vervolgens drie waves aan het laten bezinken daarvan:

ActWavesWat het leert
I1–4De onthulling—pure Asteroids, zwart gat, elliptische poorten—introduceert pathfinding-vaardigheid
II5–8Directionele (eenrichtings) poorten—introduceert routing-vaardigheid
III9–12Draaiende poorten—introduceert timing-vaardigheid
IV13–16Beiden tegelijk—alles tegelijk, waarna het spel eindigt

Ik zie dit ritme van "introduceren → oefenen → oefenen → afsluiten" als een zeer typische Nintendo-manier van doen. Een mechaniek die één keer wordt getoond en vervolgens direct wordt begraven onder de volgende, is nooit echt aangeleerd. Vier waves geven je genoeg tijd om het op te merken, erdoor gevangen te worden en vervolgens eromheen te gaan plannen.

In de gamecode schrijf ik alleen op wat er verandert van de ene wave naar de volgende. Een oefenwave die de vorige setup behoudt, is bewust leeg. Introductieteksten verschijnen alleen wanneer er daadwerkelijk iets nieuws op het scherm staat. Het is geen tutorial, maar een kleine melding (toast) die je net genoeg aanwijzingen geeft.

Vroege poorten waren ronde ringen. Zijwaartse grazers voelden verkeerd, dus elliptische slots werden de basis na de eerste playtest. Directionele en draaiende poorten kwamen later als routing- en timingproblemen, niet als decoratie.

Deterministische gameplay (op de arcade-manier)

Ik hou ervan om patronen te leren in Pac-Man, de ton-logica in Donkey Kong en de routes door Flicky. Die games voelen als parcoursen: leer de indeling en je kennis begint zich op te stapelen. Beltrunner leunt tegen dezelfde aanpak aan.

Elke "willekeurige" trekking—rots-spawnranden en richtingen, poortplaatsing, verspreiding van power-ups—komt uit één seeded stream. Eén enkel getal beschrijft de hele indeling. Start het spel opnieuw en de eerste run van de ene sessie zal overeenkomen met de eerste run van de volgende, mits er dezelfde input is. Verander de seed en je krijgt een volledig ander, maar even eerlijk parcours vanuit dezelfde regels.

Dat is belangrijk voor het leerproces (het veld is een plek die je kunt kennen), voor replays (een opname werkt alleen als de stream overeenkomt) en voor tuning (je past regels en een seed aan, in plaats van te hopen op geluk).

Power-up drops volgen ook een vast ritme. Elke achtste rots-kill activeert er één, cyclend door [T] → [I] → [S], in plaats van een "1-op-8 kans" zoals de diamantdrop in Flicky. Het wapen voelt nog steeds genereus, maar het schema is leerbaar en voorspelbaar.

Ontdekbare geheimen (op de Bomb Jack-manier)

Bomb Jack heeft een sequentiële bonus die je beloont voor het verzamelen van de verlichte doelen in de juiste volgorde. Ik denk dat dit nog steeds een van de beste voorbeelden is van een game die iets leert zonder een handleiding te gebruiken, dus ik heb een soortgelijk idee toegepast op het rotsveld.

Vanaf wave 2 is er telkens één grote rots gemarkeerd (highlighted). Splits ze in volgorde en een aanhoudende stem voegt zich bij een akkoord: grondtoon, terts, kwint, octaaf. Splits er één buiten de volgorde en de stemmen stoppen. Je merkt dat je iets fout hebt gedaan omdat het koor niet langer zingt. Een ontbrekende pip is makkelijk te missen, maar een akkoord dat plotseling stopt, niet.

Volg de lichten en de laatste stop is altijd de klaver; een rots die een beetje lijkt op een geluksklavertje van vier bladeren en verborgen in het zicht tussen de anderen ligt. Voltooi de sequentie en hij laat [E] vallen, de geheime jackpot die tijd, onkwetsbaarheid en freeze tegelijk bevat.

Niets hiervan wordt op het scherm uitgelegd. De legenda voor power-ups toont ???? totdat je het eenmaal hebt gevonden; daarna staat er EXTRA als een klein eerbetoon.

Ik probeerde nummers op de rotsen zelf te plaatsen, maar het resultaat was veel te onoverzichtelijk. Ze markeren was de simpelere oplossing waar ik later op kwam.

Er is een tweede geheim een laag hoger. Voltooi alle zestien waves en de attract-mode ontgrendelt het voorouderspel, waardoor PUSH START verandert in een Beltrunner/Asteroids-selector. Ik vind het mooi dat de beloning voor het uitspelen van het spel is dat je het spel kunt spelen waar het uit voortkwam.

Los verzamelbare power-ups (op de Pang-manier)

Pang, en een lange reeks arcade-games ervoor en daarna, begreep dat een power-up die je moet gaan halen een beslissing op zich is. Beltrunner volgt dezelfde traditie door tokens direct in het speelveld te plaatsen. Elke token heeft een omlijnde ring, een gekleurde letter, een langzame drift en een versnellende knipper tijdens de laatste twee seconden, zodat je eerlijk gewaarschuwd wordt dat hij bijna verdwijnt.

Drops—deze worden gecreëerd door het wapen tijdens het spelen:

  • [T] TIME: klok +10. De belangrijkste valuta van het spel.
  • [I] INVINCIBLE: een paar seconden rammen zonder dood te gaan; rotsen splitsen bij contact zoals biljartballen.
  • [S] STOP: rotsen bevriezen en de raceklok bevriest. Een vroege versie bevroor alleen de rotsen en liet de klok lopen, wat stilletjes gemeen was tijdens het enige moment dat bedoeld was als adempauze.

Placed—deze verschijnen altijd onder specifieke omstandigheden:

  • [E] EXTRA: de klaver-sequentie beschreven hierboven. Een tijdje gaf dit slechts iets meer tijd dan [T], wat een anticlimax was voor een geheim. Nu geeft het je tijd, onkwetsbaarheid en freeze tegelijk.
  • [W] WARP: verschijnt alleen in de laatste wave van acten I–III, zodra de mechaniek van die act is aangeleerd. Hij draait als een satelliet om een gastrots en kan worden opgeëist door er vlak langs te vliegen. Schiet je de gastrots neer, dan komt de token los en begint hij te driften. Het verzamelen ervan opent onmiddellijk het zwarte gat, waardoor de resterende poorten worden overgeslagen en de tijd wordt veiliggesteld. Een eerdere versie plaatste dit in de wave die een mechaniek introduceerde, wat absoluut het verkeerde moment was om een ontsnappingsroute aan te bieden. Er is geen warp in wave 16 omdat er niets meer is om naar te ontsnappen.

De loop waar ik op mikte was om het veld vol te maken voor waardevollere poorten, te vissen naar een drop, en vervolgens de route te nemen terwijl je beschermd bent. De tools zijn nuttig, maar het verkrijgen ervan verhoogt ook het risico.

Andere elementen die bewaard zijn gebleven

Hier zijn enkele andere details die het hebben gehaald:

  • FIELD als maatstaf voor risico. Elke rots heeft een onzichtbaar gewicht (groot 1 / middel 2 / klein 3), en FIELD is de live som van die waarden. De uitbetalingen van poorten schalen mee met dit getal. Het splitsen van rotsen verhoogtFIELD, dus schieten is niet simpelweg een manier om een baan vrij te maken; het verhoogt ook de inzet. Als je per ongeluk een rots raakt, splitst deze nog steeds (zoals in Asteroids), dus een onvoorzichtige stoot kan het veld waardevoller maken. FIELD verhoogt de basisscore terwijl COMBO deze vermenigvuldigt, waardoor de twee waarden aparte functies op de HUD hebben.
  • Wrap-edge racing. Het speelveld is een echte torus: rotsen en het schip kunnen beide zijden van een wrap overbruggen, en botsingen vinden over die rand heen plaats. In de originele Asteroids teleporteren rotsen wanneer hun middelpunt een rand kruist, waarbij ervoor en daarna tot de helft van de rots onzichtbaar is. In Beltrunner is de snelste route naar de volgende poort vaak via de rand van het scherm, dus moet de wrap iets zijn waar de speler op kan vertrouwen.
  • Black hole sun. Het zwarte gat trekt vanuit het hele arena aan, maar heeft een piepkleine event horizon in zijn centrum. Je kunt erlangs vliegen, de trek voelen, de ringen raken en pas warpen wanneer je door het oog gaat. Dit laat ruimte over om een losse power-up te pakken voordat je vertrekt, wat de moeite waard kan zijn omdat de effecten worden meegenomen naar de volgende wave. Het openingsgeluid stijgt naar een hangende dominant en de duik lost deze op.
  • Attract mode. Ik controleerde de originele Asteroids en vond één continue simulatie: rotsen driften onder wisselende tekstoverlays, er is geen schip of zelfspel tijdens inactiviteit, en de high-score tabel wordt niet getekend als deze leeg is. Beltrunner hanteert dezelfde aanpak, zodat de wereld nooit stopt.
  • Design System. HUD-tekst met dezelfde intentie verschijnt altijd op dezelfde y-positie en in dezelfde grootte, waardoor de speler de betekenis door vertrouwdheid leert kennen. Drie verschillende tekstgroottes versterken de hiërarchie verder. Tabellen worden beschreven met een toppositie en een stapgrootte, zodat het verplaatsen van het high-score blok één beslissing is in plaats van tien. Het BELTRUNNER-logo is zelfs handmatig gekernd omdat L+T een gapend gat laat in het vector monospaced font.
  • High scores die de economie begrijpen. De high-score tabel in attract-mode bestaat niet uit tien handmatig ingevoerde getallen die verouderen. Hij is afgeleid van wat het spel daadwerkelijk kan uitbetalen, dus het herstellen van een poort of warp bank verschuift de hele tabel mee. De Jinks IDE toont ook de theoretische maximale score in zijn inspector.
  • Audio als instructie. Greenwich Time Signal pips klinken tijdens de laatste zes seconden. B5 ligt dicht genoeg bij 1000Hz en is ook de tonica van de audiobed. De lit-rock sequentie gebruikt de enige aanhoudende klankkleur die de bed bewust vermijdt, wat helpt om op te vallen in plaats van begraven te raken.
  • Invincible ramming. Terwijl [I] actief is, splitst rammen de rots en stuurt beide lichamen weg van het contactpunt. Je stuitert terug bij een grote rots, maar kunt door een kleine heen ploegen. Eerdere versies lieten je óf helemaal niet stuiteren, óf stuurden het schip ongecontroleerd weg. De rebound is nog zo'n leuke verrassing die verborgen zit in de gamesystemen.

Wat de game aan het systeem afdwong

Asteroids paste al comfortabel in de Jinks-engine, maar het maken van een race door hetzelfde veld betekende dat het systeem op een paar punten moest groeien. Ik heb geprobeerd elke toevoeging algemeen genoeg te maken, zodat de volgende game die dezelfde functie nodig heeft er ook van profiteert.

Betere vectorondersteuning. Beltrunner is een white-on-black vector game in plaats van een rastergame met een CRT-filter. Het had stroke glyphs nodig die elke shell op dezelfde manier tekent, een correcte beam, dikkere lijnen die overleven bij phosphor bloom, en verschillende ontbrekende karakters die ik onderweg heb toegevoegd. Intensiteit is nu een eigenschap van het display, in plaats van iets dat handmatig moet worden verminderd voor elke vorm. Andere vector games werden hierdoor als bijeffect helderder. Iedereen is winnaar!

Torus wrapping die de waarheid spreekt. De gebruikelijke screen wrap teleporteert een object naar de andere kant, wat prima is voor veel games, maar niet wanneer de snelste lijn naar een poort door de schermrand loopt. Een wereld kan nu kiezen voor een echte torus: een entiteit die een naad overbrugt wordt aan beide zijden getekend (tot vier kopieën in een hoek), en botsingen bereiken de grens via de kortste gewrapte afstand. Beltrunner en mijn versie van Asteroids gebruiken dit beiden, terwijl de originele Asteroids rotsen wraptte wanneer hun middelpunt de schermrand kruiste.

Colliders die overeenkomen met wat je ziet. Cirkels zijn mijn eerste keuze voor collision, en de game jam-versie werd ermee geleverd, maar uitgebreid spelen toonde aan dat ze onfaire botsingen veroorzaakten. Het schip was een slanke dart gehuld in een dikke disc, terwijl de dunne elliptische poorten te ver weg collideerden. Ik heb polygoon colliders toegevoegd met echte concave decompositie, in plaats van een convex hull die de inkeping in de staart opvult, en georiënteerde ellipse colliders die meedraaien met hun richting. Het systeem had ook ellipse-versus-polygoon contact nodig. Zonder dit vielen de poorten door elke narrow-phase branch heen en werden ze opnieuw oversized AABBs, waardoor spelers poorten konden pakken waar ze nergens in de buurt van waren gevlogen.

Een echte deflect. Dit heb ik na de jam toegevoegd. Rotsen rammen terwijl je onkwetsbaar bent moet voelen als snooker: grazende klappen draaien je, frontale botsingen laten je stuiteren, kleine rotsen geven mee en grote rotsen niet. Het systeem voert nu een correcte 2D elastische deflectie uit langs de contactnormaal, waarbij beide lichamen worden bijgewerkt en massa wordt afgeleid van hun geometrie. Er is geen magische knockback-tabel, en ik ben er zeker van dat andere games zullen profiteren van het feit dat dit ingebouwd is.

Een paar kleinere noodzakelijkheden. Grace time—het moment van tijdelijke onkwetsbaarheid na een respawn—knippert nu zodat je het respawn-schild kunt zien, terwijl pickups er nog steeds doorheen kunnen gaan. Tokens knipperen sneller naarmate ze hun vervaldatum naderen. Een satelliet power-up kan losraken en driften wanneer zijn gast sterft. Point gravity maakt het zwarte gat sterk genoeg om vanaf de rand van de arena te voelen, terwijl het oog klein genoeg blijft zodat je er nog steeds langs kunt vliegen. Thrust kan worden gekoppeld aan zowel de d-pad als een actieknop. Ten slotte kan het verslaan van het spel Asteroids ontgrendelen—zijn voorouder—selecteerbaar in attract mode, met een CRT power-off shrink-to-a-dot die de overgang tussen de twee games verbergt.

Niets hiervan is Beltrunner-specifieke code. Het is allemaal onderdeel geworden van Jinks omdat het ontwerp en de implementatie nuttig genoeg waren om te generaliseren.

Nieuwe platformen na de jam

Geen van het bovenstaande is platformwerk, en dat is bewust, maar die kant van het project is ook doorgegaan. Sinds de jam heeft een tweede engine shell met SDL2 en GLES2 dezelfde ongewijzigde core naar Linux desktops en handhelds gebracht, inclusief die op muOS, en verder terug naar de Nintendo Wii en Sega Dreamcast. Dit staat naast het native macOS/iOS pad en een raylib desktop shell.

Beltrunner draait op al deze platformen zonder geporteerd of opnieuw gebouwd te worden. Het gebruikt dezelfde 1.300 regels, tick loop en deterministische seed stream, waarbij het hetzelfde per-frame contract leest waaraan elke nieuwe shell moet voldoen. Dit is waarom ik heb geprobeerd de core onwetend te houden van elk specifiek platform: zodra een machine aan dat contract kan voldoen, krijgt hij de hele bibliotheek, inclusief deze game.

Over ongeveer 1.300 regels code

Dat is de hele game, in plaats van een prototype met de "echte" code elders of een dunne shell over een bespoke mode. Waves, attract mode, high scores, power-ups, geluiden, overwinning en de geheime ontgrendeling van Asteroids leven allemaal op één plek. De engine features zijn de gedeelde botten; de 1.300 regels zijn hoe Beltrunner ze arrangeert.

Ik vind het mooi dat het begint als Asteroids, verandert in een countdown race, ideeën leent van Nintendo, Bomb Jack en Pang, en vervolgens daadwerkelijk eindigt na zestien waves die uit het hoofd geleerd kunnen worden.

Ik zal meer schrijven over hoe games worden gecreëerd in Jinks zodra het systeem is gestopt met zo snel veranderen.

Tot die tijd... veel plezier!