2D-Spielprogrammierung in C Tutorial Snake

Der Zweck dieses Tutorials ist es, 2D-Spieleprogrammierung und C-Sprache anhand von Beispielen zu lehren. Der Autor programmierte Mitte der 1980er-Jahre Spiele und war in den 90er-Jahren ein Jahr lang Spiele-Designer bei MicroProse. Obwohl vieles davon für die Programmierung der heutigen großen 3D-Spiele nicht relevant ist, wird es für kleine Casual Games als nützliche Einführung dienen.

Snake implementieren

Spiele wie Snake, bei denen sich Objekte über ein 2D-Feld bewegen, können die Spielobjekte entweder in einem 2D-Raster oder als ein eindimensionales Array von Objekten darstellen. "Objekt" bedeutet hier ein beliebiges Spielobjekt, kein Objekt, wie es in der objektorientierten Programmierung verwendet wird.

Spielsteuerung

Die Tasten bewegen sich mit W = hoch, A = links, S = runter, D = rechts. Drücken Sie Esc, um das Spiel zu beenden, f, um die Bildrate umzuschalten (dies ist nicht mit der Anzeige synchronisiert, kann also schnell sein), die Tabulatortaste, um die Debug-Informationen umzuschalten, und p, um sie anzuhalten. Wenn es angehalten ist, ändert sich die Beschriftung und die Schlange blinkt,

In der Schlange sind die Hauptspielobjekte

  • Die Schlange
  • Fallen und Obst

Für das Spiel enthält eine Reihe von Ints jedes Spielobjekt (oder einen Teil für die Schlange). Dies kann auch beim Rendern der Objekte in den Bildschirmpuffer hilfreich sein. Ich habe die Grafik für das Spiel wie folgt gestaltet:

  • Horizontaler Schlangenkörper - 0
  • Vertikaler Schlangenkörper - 1
  • Kopf in 4 x 90-Grad-Drehungen 2-5
  • Schwanz in 4 x 90-Grad-Drehungen 6-9
  • Kurven für Richtungsänderungen. 10-13
  • Apple - 14
  • Erdbeere - 15
  • Banane - 16
  • Falle - 17
  • Zeigen Sie die Schlangengrafikdatei snake.gif an

Daher ist es sinnvoll, diese Werte in einem als Block [WIDTH * HEIGHT] definierten Rastertyp zu verwenden. Da das Raster nur 256 Positionen enthält, habe ich beschlossen, es in einem eindimensionalen Array zu speichern. Jede Koordinate im 16 x 16-Raster ist eine Ganzzahl von 0 bis 255. Wir haben Ints verwendet, damit Sie das Raster vergrößern können. Alles wird durch #defines mit WIDTH und HEIGHT definiert. 16. Da die Schlangengrafiken 48 x 48 Pixel groß sind (GRWIDTH und GRHEIGHT #defines), wird das Fenster anfänglich als 17 x GRWIDTH und 17 x GRHEIGHT definiert und ist nur geringfügig größer als das Raster.

Dies hat Vorteile in Bezug auf die Spielgeschwindigkeit, da die Verwendung von zwei Indizes immer langsamer als einer ist. Dies bedeutet jedoch, dass Sie BREITE abziehen, anstatt 1 zu addieren oder von den Y-Koordinaten der Schlange zu subtrahieren, um sich vertikal zu bewegen. Addiere 1, um nach rechts zu gehen. Wir haben jedoch auch ein Makro l (x, y) definiert, das die x- und y-Koordinaten zur Kompilierzeit konvertiert.

Was ist ein Makro??

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

Die erste Zeile ist Index 0-15, die zweite 16-31 usw. Befindet sich die Schlange in der ersten Spalte und bewegt sie sich nach links, muss vor dem Bewegen nach links überprüft werden, ob die Koordinate% WIDTH == 0 und für die rechte Wandkoordinate% WIDTH == WIDTH-1. Das% ist der C-Modul-Operator (wie die Taktarithmetik) und gibt den Rest nach der Division zurück. 31 div 16 hinterlässt einen Rest von 15.

Die Schlange verwalten

Es gibt drei Blöcke (Int-Arrays), die im Spiel verwendet werden.

  • Schlange [], ein Ringpuffer
  • shape [] - Enthält grafische Schlangenindizes
  • dir [] - Hält die Richtung jedes Segments in der Schlange einschließlich Kopf und Schwanz.

Zu Beginn des Spiels ist die Schlange zwei Segmente lang mit einem Kopf und einem Schwanz. Beide können in 4 Richtungen zeigen. Für den Norden ist der Kopf Index 3, der Schwanz ist 7, für den Osten ist der Kopf 4, der Schwanz ist 8, für den Süden ist der Kopf 5 und der Schwanz ist 9, und für den Westen ist der Kopf 6 und der Schwanz ist 10 Während die Schlange zwei Segmente lang ist, sind Kopf und Schwanz immer um 180 Grad voneinander entfernt, aber nachdem die Schlange gewachsen ist, können sie 90 oder 270 Grad betragen.

Das Spiel beginnt mit dem Kopf nach Norden bei Position 120 und dem Schwanz nach Süden bei 136, ungefähr in der Mitte. Bei geringen Speicherkosten von ca. 1.600 Byte können wir eine erkennbare Geschwindigkeitsverbesserung im Spiel erzielen, indem wir die Positionen der Schlange im oben erwähnten Snake [] -Ringpuffer halten.

Was ist ein Ringpuffer??

Ein Ringpuffer ist ein Speicherblock, der zum Speichern einer Warteschlange mit fester Größe verwendet wird und groß genug sein muss, um alle Daten aufzunehmen. In diesem Fall ist es nur für die Schlange. Die Daten werden auf die Vorderseite der Warteschlange verschoben und von der Rückseite abgenommen. Wenn die Vorderseite der Warteschlange das Ende des Blocks erreicht, wird ein Umlauf ausgeführt. Solange der Block groß genug ist, wird die Vorderseite der Warteschlange niemals die Rückseite einholen.

Jeder Ort der Schlange (d. H. Die einzelne int-Koordinate) vom Schwanz zum Kopf (d. H. Rückwärts) wird im Ringpuffer gespeichert. Dies bietet Geschwindigkeitsvorteile, da unabhängig von der Länge der Schlange nur der Kopf, der Schwanz und das erste Segment nach dem Kopf (falls vorhanden) während der Bewegung geändert werden müssen.

Es ist auch vorteilhaft, es rückwärts zu lagern, denn wenn die Schlange Futter bekommt, wächst sie, wenn sie das nächste Mal bewegt wird. Dies geschieht, indem der Kopf um eine Position im Ringpuffer verschoben und die alte Kopfposition in ein Segment geändert wird. Die Schlange besteht aus einem Kopf, 0-n Segmenten und einem Schwanz.

Wenn die Schlange Nahrung isst, wird die Variable atefood auf 1 gesetzt und in der Funktion DoSnakeMove () überprüft.

Die Schlange bewegen

Wir verwenden zwei Indexvariablen, headindex und tailindex, um auf die Positionen head und tail im Ringpuffer zu verweisen. Diese beginnen bei 1 (Kopfindex) und 0. Die Position 1 im Ringpuffer enthält also die Position (0-255) der Schlange auf dem Brett. Position 0 enthält die Endposition. Wenn sich die Schlange um eine Stelle vorwärts bewegt, werden sowohl der Schwanzindex als auch der Kopfindex um eins erhöht und auf 0 umwickelt, wenn sie 256 erreichen. Nun ist die Stelle, an der sich der Kopf befand, die Stelle, an der sich der Schwanz befindet.

Selbst bei einer sehr langen Schlange, die sich in etwa 200 Segmenten windet und windet. bei jeder Bewegung ändern sich nur der Kopfindex, das Segment neben dem Kopf- und dem Schwanzindex.

Beachten Sie, dass wir aufgrund der Funktionsweise von SDL bei jedem Frame die gesamte Schlange zeichnen müssen. Jedes Element wird in den Bildspeicher gezogen und dann gespiegelt, damit es angezeigt wird. Dies hat jedoch den Vorteil, dass wir die Schlange gleichmäßig um einige Pixel bewegen können und nicht um eine ganze Gitterposition.