2D-spelprogrammering in C Tutorial Snake

Het doel van deze tutorial is om 2D-spelprogrammering en C-taal te onderwijzen aan de hand van voorbeelden. De auteur programmeerde games halverwege de jaren tachtig en was in de jaren 90 een game-ontwerper bij MicroProse. Hoewel veel van dat niet relevant is voor de programmering van de grote 3D-spellen van vandaag, zal het voor kleine casual spellen als een nuttige introductie dienen.

Snake implementeren

Games zoals snake waarbij objecten over een 2D-veld bewegen, kunnen de game-objecten vertegenwoordigen in een 2D-raster of als een reeks dimensies van objecten. "Object" betekent hier elk spelobject, geen object zoals gebruikt in objectgeoriënteerd programmeren.

Spelbesturing

De toetsen worden verplaatst met W = omhoog, A = links, S = omlaag, D = rechts. Druk op Esc om het spel te stoppen, f om de beeldsnelheid te wisselen (dit is niet gesynchroniseerd met het display, dus kan snel zijn), tab-toets om foutopsporingsinformatie te schakelen en p om het te pauzeren. Als het is gepauzeerd, verandert het bijschrift en knippert de slang,

In snake zijn de belangrijkste spelobjecten

  • De slang
  • Vallen en fruit

Voor gameplay zal een reeks ints elk spelobject (of een deel voor de slang) bevatten. Dit kan ook helpen bij het renderen van de objecten in de schermbuffer. Ik heb de graphics voor de game als volgt ontworpen:

  • Horizontaal slangenlichaam - 0
  • Verticaal slangenlichaam - 1
  • Hoofd in 4 x 90 graden rotaties 2-5
  • Staart in 4 x 90 graden rotaties 6-9
  • Curven voor routebeschrijving wijzigen. 10-13
  • Appel - 14
  • Aardbei - 15
  • Banaan - 16
  • Trap - 17
  • Bekijk het grafische slangbestand snake.gif

Het is dus zinvol om deze waarden te gebruiken in een rastertype dat is gedefinieerd als blok [BREEDTE * HOOGTE]. Omdat er slechts 256 locaties in het raster zijn, heb ik ervoor gekozen om het op te slaan in een array met één dimensie. Elke coördinaat op het 16 x16-raster is een geheel getal van 0-255. We hebben ints gebruikt, zodat je het raster groter kunt maken. Alles wordt gedefinieerd door #defines met BREEDTE en HOOGTE beide 16. Omdat de slangenafbeeldingen 48 x 48 pixels zijn (GRWIDTH en GRHEIGHT #defines), is het venster aanvankelijk gedefinieerd als 17 x GRWIDTH en 17 x GRHEIGHT om net iets groter te zijn dan het raster.

Dit heeft voordelen in spelsnelheid omdat het gebruik van twee indexen altijd langzamer is dan één, maar het betekent dat in plaats van het optellen of aftrekken van 1 van de Y-coördinaten van de slang om verticaal te bewegen, je de WIDTH aftrekt. Voeg 1 toe om naar rechts te gaan. Omdat we stiekem zijn, hebben we ook een macro l (x, y) gedefinieerd die de x- en y-coördinaten converteert tijdens het compileren.

Wat is een macro?

 #define l (X, Y) (Y * WIDTH) + X

De eerste rij is index 0-15, de 2e 16-31 enz. Als de slang in de eerste kolom staat en naar links beweegt, moet de vinkje om de muur te raken, voordat hij naar links gaat, controleren of coördinaat% WIDTH == 0 en voor de rechter muurcoördinaat% WIDTH == WIDTH-1. Het% is de C-modulus-operator (zoals klokrekenen) en retourneert de rest na deling. 31 div 16 laat een rest van 15 over.

De slang beheren

Er zijn drie blokken (int arrays) in het spel gebruikt.

  • slang [], een ringbuffer
  • shape [] - Bevat Snake grafische indexen
  • dir [] - Houdt de richting van elk segment in de slang inclusief kop en staart vast.

Bij het begin van het spel is de slang twee segmenten lang met een kop en een staart. Beide kunnen in 4 richtingen wijzen. Voor het noorden is de kop index 3, de staart is 7, voor de oostkop is 4, de staart is 8, voor de zuidkop is 5 en de staart is 9, en voor het westen is de kop 6 en de staart is 10 Terwijl de slang twee segmenten lang is, staan ​​de kop en de staart altijd 180 graden uit elkaar, maar nadat de slang groeit, kunnen ze 90 of 270 graden zijn.

Het spel begint met het hoofd naar het noorden op locatie 120 en de staart naar het zuiden op 136, ruwweg centraal. Tegen een kleine kostprijs van ongeveer 1600 bytes aan opslag, kunnen we een merkbare snelheidsverbetering in het spel bereiken door de locaties van de slang in de bovengenoemde slangringbuffer te houden.

Wat is een ringbuffer?

Een ringbuffer is een geheugenblok dat wordt gebruikt voor het opslaan van een wachtrij die een vaste grootte heeft en groot genoeg moet zijn om alle gegevens te bevatten. In dit geval is het alleen voor de slang. De gegevens worden aan de voorkant van de wachtrij geplaatst en aan de achterkant verwijderd. Als de voorkant van de wachtrij het einde van het blok raakt, loopt het rond. Zolang het blok groot genoeg is, zal de voorkant van de wachtrij nooit de achterkant inhalen.

Elke locatie van de slang (d.w.z. de enkele int-coördinaat) van de staart naar de kop (d.w.z. achteruit) wordt opgeslagen in de ringbuffer. Dit levert snelheidsvoordelen op, want hoe lang de slang ook wordt, alleen het hoofd, de staart en het eerste segment na het hoofd (als deze bestaat) moeten tijdens het verplaatsen worden gewijzigd.

Achteruit bewaren is ook handig omdat wanneer de slang voedsel krijgt, de slang zal groeien wanneer hij de volgende keer wordt verplaatst. Dit wordt gedaan door de kop één locatie in de ringbuffer te verplaatsen en de oude koplocatie te veranderen in een segment. De slang bestaat uit een kop, 0-n segmenten) en vervolgens een staart.

Wanneer de slang voedsel eet, wordt de variabele atefood ingesteld op 1 en ingeschakeld in de functie DoSnakeMove ()

De slang verplaatsen

We gebruiken twee indexvariabelen, headindex en tailindex om naar de head en tail-locaties in de ringbuffer te wijzen. Deze beginnen bij 1 (headindex) en 0. Dus locatie 1 in de ringbuffer bevat de locatie (0-255) van de slang op het bord. Locatie 0 bevat de staartlocatie. Wanneer de slang één locatie naar voren beweegt, worden zowel de staartindex als de kopindex met één verhoogd, waarbij ze rond 0 lopen wanneer ze 256 bereiken. Dus nu is de locatie waar de kop was de staart.

Zelfs met een zeer lange slang die wikkelt en ingewikkeld is in bijvoorbeeld 200 segmenten. alleen de kopindex, het segment naast de kop en de staartindex veranderen elke keer dat deze beweegt.

Opmerking vanwege de manier waarop SDL werkt, moeten we de hele slang elk frame tekenen. Elk element wordt in de framebuffer getrokken en vervolgens omgedraaid zodat het wordt weergegeven. Dit heeft echter één voordeel: we kunnen de slang vloeiend een paar pixels laten tekenen, niet een hele rasterpositie.