2D-spilprogrammering i C-tutorial: Snake

Formålet med denne tutorial er at lære 2D-spilprogrammering og C-sprog gennem eksempler. Forfatteren plejede at programmere spil i midten af ​​1980'erne og var spildesigner hos MicroProse i et år i 90'erne. Selvom meget af det ikke er relevant for programmeringen af ​​dagens store 3D-spil, vil det for små afslappede spil tjene som en nyttig introduktion.

Implementering af slange

Spil som slange, hvor genstande bevæger sig over et 2D-felt, kan repræsentere spilobjekterne enten i et 2D-gitter eller som en enkelt dimension af objekter. "Objekt" betyder her ethvert spilobjekt, ikke et objekt, som det bruges i objektorienteret programmering.

Spilkontrol

Tasterne bevæges med W = op, A = venstre, S = ned, D = højre. Tryk på Esc for at afslutte spillet, f for at skifte billedhastighed (dette er ikke synkroniseret til skærmen, så det kan være hurtigt), fanetasten for at skifte fejlfindingsinfo og p for at sætte den på pause. Når det er sat på pause, ændres billedtekst, og slangen blinker,

I slange er de vigtigste spilobjekter

instagram viewer
  • Slangen
  • Fælder og frugt

Med henblik på gameplay vil en række ints indeholde hvert spilobjekt (eller en del til slangen). Dette kan også hjælpe, når du gengiver objekterne i skærmbufferen. Jeg har designet grafikken til spillet som følger:

  • Vandret slangekrop - 0
  • Lodret slangekrop - 1
  • Hoved i 4 x 90-graders rotationer 2-5
  • Hale i 4 x 90-graders rotationer 6-9
  • Kurver for retningsændring. 10-13
  • Apple - 14
  • Jordbær - 15
  • Banan - 16
  • Fælde - 17
  • Se slangegrafikfilen snake.gif

Så det giver mening at bruge disse værdier i en gittertype defineret som blok [WIDTH * HEIGHT]. Da der kun er 256 placeringer i gitteret, har jeg valgt at gemme det i en enkelt dimensionsgruppe. Hver koordinat på 16 x16 gitteret er et heltal 0-255. Vi har brugt ints, så du kan gøre gitteret større. Alt er defineret af #definer med WIDTH og HEIGHT begge 16. Da slangegrafikken er 48 x 48 pixels (GRWIDTH og GRHEIGHT #defines) defineres vinduet oprindeligt som 17 x GRWIDTH og 17 x GRHEIGHT for at være bare lidt større end gitteret.

Dette har fordele ved spilhastighed, da brug af to indekser altid er langsommere end et, men det betyder, i stedet for at tilføje eller trække 1 fra slangens Y-koordinater til at bevæge sig lodret, trækker du WIDTH. Tilføj 1 for at flytte til højre. Men når vi er snige, har vi også defineret en makro l (x, y), der konverterer x- og y-koordinaterne på kompileringstidspunktet.

Hvad er en makro?

 #definér l (X, Y) (Y * WIDTH) + X

Den første række er indeks 0-15, 2. 16-31 osv. Hvis slangen er i den første kolonne og bevæger sig til venstre, skal kontrollen for at ramme væggen, inden den bevæger sig mod venstre, kontrollere, om koordinat% WIDTH == 0 og for den højre vægkoordinat% WIDTH == WIDTH-1. % Er C-moduloperatøren (som ur-aritmetik) og returnerer resten efter opdeling. 31 div 16 efterlader en rest af 15.

Håndtering af slangen

Der er tre blokke (int arrays), der bruges i spillet.

  • slange [], en ringbuffer
  • form [] - Indeholder grafiske indekser for slange
  • dir [] - Holder retningen for hvert segment i slangen inklusive hoved og hale.

Ved starten af ​​spillet er slangen to segmenter lang med et hoved og en hale. Begge kan pege i 4 retninger. For nord er hovedet indeks 3, halen er 7, for det østlige hoved er 4, halen er 8, for det sydlige hoved er 5, og halen er 9, og for vest er hovedet 6 og hale er 10. Mens slangen er to segmenter lang, er hovedet og halen altid 180 grader fra hinanden, men efter at slangen vokser, kan de være 90 eller 270 grader.

Spillet starter med hovedet mod nord på placering 120 og halen mod syd ved 136, nogenlunde centralt. Til en mindre omkostning på ca. 1.600 byte opbevaring kan vi få en markant hastighedsforbedring i spillet ved at holde slangens placeringer i slangen [] ringbufferen nævnt ovenfor.

Hvad er en ringbuffer?

En ringbuffer er en hukommelsesblok, der bruges til at gemme en kø, der er af en fast størrelse, og skal være stor nok til at kunne indeholde alle data. I dette tilfælde er det bare for slangen. Dataene skubbes på forsiden af ​​køen og fjernes bagfra. Hvis fronten af ​​køen rammer slutningen af ​​blokken, vikles den rundt. Så længe blokken er stor nok, vil fronten af ​​køen aldrig indhente ryggen.

Hver placering af slangen (dvs. den enkelte int-koordinat) fra halen til hovedet (dvs. bagud) gemmes i ringbufferen. Dette giver hastighedsfordele, uanset hvor lang tid slangen får, er det kun hovedet, halen og det første segment efter hovedet (hvis det findes), der skal ændres, når det bevæger sig.

Opbevaring baglæns er også fordelagtigt, fordi når slangen får mad, vil slangen vokse, når den næste flyttes. Dette gøres ved at flytte hovedet et sted i ringbufferen og ændre det gamle hovedplacering til at blive et segment. Slangen består af et hoved, 0-n segmenter) og derefter en hale.

Når slangen spiser mad, indstilles atefood-variablen til 1 og kontrolleres i funktionen DoSnakeMove ()

Flytning af slangen

Vi bruger to indeksvariabler, headindex og tailindex til at pege på hoved- og haleplaceringer i ringbufferen. Disse starter ved 1 (headindex) og 0. Så placering 1 i ringbufferen holder placeringen (0-255) for slangen på brættet. Placering 0 holder halens placering. Når slangen bevæger sig et sted fremad, forøges både halesindeks og hovedindeks af en, hvor de vikles rundt til 0, når de når 256. Så nu er placeringen der var hovedet, hvor halen er.

Selv med en meget lang slange, der er viklet og indviklet i siger 200 segmenter. kun headindex, segment ved siden af ​​head og tailindex ændres hver gang det bevæger sig.

Bemærk på grund af vejen SDL fungerer, vi er nødt til at tegne hele slangen hver ramme. Hvert element trækkes ind i rammebufferen og derefter vippes, så det vises. Dette har dog en fordel ved, at vi kunne tegne slangen, der bevæger sig glat nogle få pixels, ikke en hel gitterposition.