2D-spelprogrammering i C Tutorial Snake

Syftet med denna handledning är att lära 2D-spelprogrammering och C-språk genom exempel. Författaren brukade programmera spel i mitten av 1980-talet och var speldesigner på MicroProse i ett år på 90-talet. Även om mycket av det inte är relevant för programmeringen av dagens stora 3D-spel, kommer det för små casual-spel att fungera som en användbar introduktion.

Implementering av orm

Spel som orm där objekt rör sig över ett 2D-fält kan representera spelobjekten antingen i ett 2D-rutnät eller som en enda dimension av objekt. "Objekt" betyder här ett spelobjekt, inte ett objekt som används i objektorienterad programmering.

Spelkontroller

Knapparna flyttas med W = upp, A = vänster, S = ned, D = höger. Tryck på Esc för att avsluta spelet, f för att växla bildfrekvens (detta är inte synkroniserat till skärmen så kan vara snabbt), flikknappen för att växla felsökningsinfo och p för att pausa den. När det är paus ändras bildtexten och ormen blinkar,

I ormen är de viktigaste spelobjekten

  • Ormen
  • Fällor och frukt

För speländamål kommer en mängd ints att innehålla varje spelobjekt (eller del för ormen). Detta kan också hjälpa till när du renderar objekt i skärmbufferten. Jag har designat grafiken för spelet enligt följande:

  • Horisontell ormkropp - 0
  • Vertikal ormkropp - 1
  • Huvud i 4 x 90-graders rotationer 2-5
  • Svans i 4 x 90-graders rotationer 6-9
  • Kurvor för vägbeskrivning. 10-13
  • Apple - 14
  • Jordgubbe - 15
  • Banan - 16
  • Fälla - 17
  • Visa grafikfilen för ormen snake.gif

Så det är vettigt att använda dessa värden i en ruttyp definierad som block [WIDTH * HEIGHT]. Eftersom det bara finns 256 platser i rutnätet har jag valt att lagra det i en enhet med en enda dimension. Varje koordinat på rutan 16 x16 är ett heltal 0-255. Vi har använt ints så att du kan göra nätet större. Allt definieras av #definierar med WIDTH och HEIGHT båda 16. Eftersom ormgrafiken är 48 x 48 pixlar (GRWIDTH och GRHEIGHT #defines) definieras fönstret initialt som 17 x GRWIDTH och 17 x GRHEIGHT för att vara bara något större än rutnätet.

Detta har fördelar med spelhastighet eftersom användning av två index alltid är långsammare än ett men det betyder istället för att lägga till eller subtrahera 1 från ormens Y-koordinater för att röra sig vertikalt, subtraherar du WIDTH. Lägg till 1 för att flytta åt höger. Men när vi är smyga har vi också definierat ett makro l (x, y) som konverterar x- och y-koordinaterna vid kompileringstid.

Vad är en makro?

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

Den första raden är index 0-15, den andra 16-31 osv. Om ormen är i den första kolumnen och rör sig åt vänster måste kontrollen för att träffa väggen, innan du flyttar till vänster, kontrollera om koordinat% WIDTH == 0 och för höger väggkoordinat% WIDTH == WIDTH-1. % Är C-moduloperatören (som klockaritmetik) och returnerar resten efter delning. 31 div 16 lämnar återstoden av 15.

Hantera ormen

Det finns tre block (int arrays) som används i spelet.

  • orm [], en ringbuffert
  • form [] - Har grafikindex för orm
  • dir [] - Håller riktningen för varje segment i ormen inklusive huvud och svans.

Vid spelets start är ormen två segment lång med ett huvud och en svans. Båda kan peka i fyra riktningar. För norr är huvudet index 3, svansen är 7, för östhuvudet är 4, svansen är 8, för södra huvudet är 5 och svansen är 9, och för väster är huvudet 6 och svansen är 10 Medan ormen är två segment lång är huvudet och svansen alltid 180 graders isär, men efter att ormen växer kan de vara 90 eller 270 grader..

Spelet börjar med huvudet mot norr på plats 120 och svansen riktad söderut vid 136, ungefär centralt. Till en liten kostnad på cirka 1 600 byte lagring kan vi få en märkbar hastighetsförbättring i spelet genom att hålla ormens placeringar i ormen [].

Vad är en ringbuffert?

En ringbuffert är ett minnesblock som används för att lagra en kö som har en fast storlek och måste vara tillräckligt stor för att kunna hålla all data. I det här fallet är det bara för ormen. Uppgifterna skjuts på framsidan av kön och tas bort från baksidan. Om den främre delen av kön träffar slutet av blocket, slår det sig runt. Så länge blocket är tillräckligt stort kommer framkanten av kön aldrig att komma ihop med ryggen.

Varje plats för ormen (dvs den enda int-koordinaten) från svansen till huvudet (dvs bakåt) lagras i ringbufferten. Detta ger hastighetsfördelar eftersom oavsett hur lång tid ormen får, behöver bara huvudet, svansen och det första segmentet efter huvudet (om det finns) ändras när det rör sig.

Att lagra den bakåt är också fördelaktigt eftersom när ormen får mat kommer ormen att växa när den nästa flyttas. Detta görs genom att flytta huvudet en plats i ringbufferten och ändra den gamla huvudplatsen för att bli ett segment. Ormen består av ett huvud, 0-n-segment) och sedan en svans.

När ormen äter mat, ställs matvariabeln på 1 och kontrolleras i funktionen DoSnakeMove ()

Flytta ormen

Vi använder två indexvariabler, headindex och tailindex för att peka på huvud- och svansplatserna i ringbufferten. Dessa börjar vid 1 (headindex) och 0. Så plats 1 i ringbufferten håller platsen (0-255) för ormen på brädet. Plats 0 håller svansplatsen. När ormen förflyttar sig en plats framåt, så ökas både svansindex och huvudindex av en och lindas runt till 0 när de når 256. Så nu är platsen som var huvudet där svansen är.

Även med en väldigt lång orm som är lindad och invändig i säger 200 segment. endast huvudindex, segment bredvid huvudet och tailindex ändras varje gång det rör sig.

Observera att på grund av hur SDL fungerar måste vi dra hela ormen varje ram. Varje element dras in i rambufferten och sedan vänds så att det visas. Detta har dock en fördel genom att vi kan dra ormen smidigt att flytta några få pixlar, inte en hel rutnätposition.