GM Tank Combat Teil 1

23. Juni 2018

Nachdem es auf ByteGame.de in letzter Zeit zahlreiche Tutorials gab, möchte ich ein Spiel in einer Serie erstellen. Der Schwerpunkt liegt darin, gelerntes in der Praxis umzusetzen. Zudem gibt es bei einem kompletten, wenn auch überschaubaren Spiel, verschiedene Überlegungen, die uns in anderen Tutorials bisher erspart blieben.

Ein Panzerspiel?

Nachdem ich die hier vorliegende Version fertig hatte habe ich mich umgeschaut und festgestellt, dass es für den GameMaker bereits einige Panzerspiel-Tutorials gibt. Mir selbst kam die Idee dazu, als ich vor längerer Zeit die Serie »Halt and Catch Fire« schaute. In einer Folge haben die Darsteller in den 1980er Jahren ein ähnliches Spiel entwickelt, wie ich das hier vorhabe.

Das Vorbild ist dabei Combat vom Atari 2600. In einigen Punkten werde ich mich daran orientieren, bei der Auflösung und dem Gameplay aber abschweifen. Wer wissen will, wie das alte Spiel von 1977 funktionierte, kann einen Blick in dieses Video werfen.

Dem Tutorial liegt ein Download von der aktuellen GameMaker 2 Version vor. Außerdem findet ihr als Ergänzung unten noch ein kleines Video, in dem ich das Projekt grob erkläre.

Grundgerüst

Im ersten Teil bauen wir zunächst ein Grundgerüst. Das beinhaltet ein kleines Level und lediglich einen Panzer, der bereits alle wesentlichen Features hat. Der Spieler kann fahren und schießen, das Geschoss tut, was es tun soll. Im Verlauf dieser Serie fügen wir weitere Features hinzu und werden sicher einige Dinge noch verbessern. Da wir wissen, dass noch einiges kommen wird, versuchen wir den Code relativ flexibel zu halten und bereiten heute einiges für zukünftige Folgen vor. Geplant sind u. A. ein Zweispielermodus und das Spiel gegen eine KI, dazu noch frei wählbare Level und ein entsprechendes Menü. Und wer weiß, vielleicht bekommen wir auch ein Netzwerkspiel hin.

Da ich mich an das Atari 2600 Vorbild anlehne, habe ich zunächst eine Farbpalette gesucht. Im NTSC Format standen dem Atari 2600 128 Farben zur Verfügung. Im Prinzip sind es nur 16 Farben mit je acht Schattierungen. Im ganzen Spiel verwende ich nur diese Farben, ihr könnt natürlich gerne davon abweichen.

Sprites

Wir brauchen zunächst nur drei Sprites. spr_wall01 ist, wie der Name sagt, unsere Wand und zugleich die Levelbegrenzung. Sie ist 32×32 groß, Origin liegt bei 0, 0. Als Farbwert verwende ich #646410.

spr_tank01 ist unser erster Panzer. Panzer werden im Spiel unterschiedliche Eigenschaften haben, aber sollten von der Form her alle gleich aussehen. Ihr könnt dies bei euch gerne anders handhaben. Ein Panzer hat bei mir eine Größe von 55×55 Pixel. Origin liegt bei 27, 27. Die Maske ist im Mode Automatic, Type ist Precise. Das garantiert uns, dass wir mit den Wänden genau kollidieren.

spr_bullet01 ist unser erstes Geschoss. Im Verlauf der Serie soll es unterschiedliche Panzer mit eigenen Geschossen geben. Dieses Geschoss hat eine Größe von 10×10 Pixel. Origin: 5, 5.

Fonts

Wirklich nötig haben wir momentan noch keine Schrift. Ich gebe gerne Informationen auf dem Bildschirm aus, wie etwa die globale FPS, manchmal auch Positionen. Deshalb baue ich immer eine kleine Debug-Funktion in meinen Spielen ein, für die wir eine eigene Schrift brauchen.

fnt_debug ist bei mir Arial 12.

Raum

Es ist ratsam, mit dem Gameplay zu beginnen. Auch bei größeren Spielen ist es ratsam, mit der Spielmechanik zu starten um dies in einem Prototyp abbilden zu können. Menüs, Intros und den ganzen Firlefanz brauchen wir noch nicht. Deshalb heißt unser erster Raum room_level01.

Größe: 1024×768

Hintergrundfarbe: #000088

Zu den Layern Background und Instances fügen wir noch Walls hinzu.

Objekte

Wir brauchen vier Objekte, die wir gleich anlegen und die Sprites zuweisen können. obj_wall01, obj_tank01, obj_bullet01 und obj_gamecontrolle. Das letzte Objekt bekommt natürlich keinen Sprite. Wir brauchen es, um später alle relevanten Dinge im Spiel zu steuern, die über den einzelnen Objekten stehen. Die Punktzahl, Bedingung für Spielende, ggf. Spielmodi uns andere Eigenschaften wären davon betroffen. In diesem Teil erfassen wir nur die reale FPS und beenden das Spiel mit Esc.

obj_wall01 stellen wir noch auf Solid. Code brauchen wir hier nicht.

Level erstellen

Im Leveleditor haben wir einen Raster von 32×32. Die oberen beiden Reihen lassen wir frei von Wänden. Dort erscheint in späteren Folgen unser GUI.

Im Layer Instances können wir oben links obj_gamecontrolle platzieren. Dann gehen wir in Layer Walls und platzieren die Wände. Da wir nur einfarbige Wände haben und auch keine Tiles nutzen, können wir die Wände skalieren. Den Rahmen für die Levelbegrenzung bauen wir somit aus vier Wänden. Anschließend kommen die Wände im Inneren. Die U-förmigen Wände helfen vor allem Steuerung und Kollision ordentlich zu prüfen.

Den Panzer platzieren wir im Layer Instances. Bei mir steht er auf 128, 416. Nun können wir mit dem Code beginnen.

obj_tank01

Bevor wir blind loslegen, wollen wir die Eigenschaften des Panzers definieren. Er soll vorwärts und rückwärts fahren können. Wenn man die Taste loslässt, soll der Panzer etwas weiter gleiten, bis er stillsteht. Er soll sich, auch im Stand, um 360° drehen können. Außerdem soll er schießen können.

Um möglichst flexibel zu sein, definieren wir im Create-Event viele Eigenschaften. Wie eingangs gesagt, sollen im Spiel mehrere Panzer möglich sein. Hier bietet sich das Prinzip der Vererbung an. Je mehr Eigenschaften wir im Create-Event definieren, umso flexibler ist der restliche Code.

Event Create

Die Variablen sind weitestgehend selbstredend. Die Höchstgeschwindigkeit des Panzers beträgt 6. Rückwärts fährt der Panzer mit einer Höchstgeschwindigkeit von -3. Die Beschleunigung beim Vorwärtsfahren beträgt 0.6, beim Rückwärtsfahren 0.3. brakeUp und brakeDown regeln das Abbremsverhalten.

Wir werden nicht pausenlos schießen können. Der Panzer hat einen Delay von 60. Wir wollen das dem Spieler in einer Leiste anzeigen, die unterhalb des Panzers gezeichnet wird. Dafür brauchen wir später die Variable shooteTimer. Im Spiel kann es möglich sein, dass zwei Panzer den selben Bullet nehmen. Deshalb werden wir später dem Bullet mitteilen, welcher Panzer ihn abgeschossen hat. Dafür ist bulletDontKill zuständig, die unsere ID weiter gibt.

Am Ende legen wir noch die Tasten für die Steuerung fest. Am Anfang gehen wir davon aus, dass zwei Leute an einer Tastatur spielen. Spieler 1 bekommt die Pfeiltasten und schießt mit der rechten STRG-Taste (vk_rcontrol). Gamepad und andere Spielereien kommen in einem anderen Teil. Eine Maussteuerung haben wir in diesem Spiel nicht.

Event Step

Das ist etwas viel Code, der aber sehr einfach zu verstehen ist. Die einzelnen Passagen sind kommentiert. Das Prinzip der Drehung erkläre ich weiter unten.

Als erstes halten wir den aktuellen Winkel des Panzers mit var oldAngle = image_angle; fest. Dann folgen zwei Abfragen für die Tasten links und rechts. Dabei geht es darum, den Panzer zu drehen. Zunächst kommt die Variable rotationSpeed zum Einsatz, die wir im Create-Event definiert haben. Wir drehen die Grafik in die entsprechende Richtung und definieren darauf folgend direction = image_angle;. So sind Richtung des Objekts und Richtung der Grafik immer gleich.

Es gibt noch die Situation, dass der Panzer an einer Wand hängt. In diesem Fall darf sich die Grafik natürlich nicht drehen. Das prüfen wir mit:

Ich weiß, dass man es auch einfacher schreiben kann. Etwa:

Die zusätzlichen Klammern nutze ich aber dennoch gerne, weil ich den Code besser lesen kann und weil er für den Fall, dass er erweitert werden soll, von der Struktur her bereit steht.

So viel zur Drehung. keyUp und keyDown bewegen den Panzer vor und zurück. Daraufhin werden die Höchstgeschwindigkeiten für beide Richtungen abgefragt und ggf. geregelt. Das Anschließende Abbremsen findet immer statt, außer die Geschwindigkeit ist bei 0. Das heißt, dass wir nicht fragen, ob irgendwelche Tasten losgelassen werden. Wir bremsen, während wir fahren. Das erzeugt ein ziemlich cooles Fahrgefühl und vereinfacht den Code.

Es gibt bei dem Verfahren eine kleine Unschärfe. Wenn man nur minimal anfährt, kann es, je nach Werten die wir im Create-Event definiert haben, dazu führen, dass der Panzer hin und her wippt, weil er nicht auf 0 kommt. Ich selbst finde das nicht so schlimm, wirkt vielleicht sogar realistischer, wenn der Panzer etwas wackelt, aber man kann das auch in den Griff bekommen. Entweder man wählt Zahlen, die sich durcheinander teilen lassen, oder man nimmt eine weitere Korrektur vor, welche einen Bereich um den Nullpunkt herum erfasst und in diesem Fall speed auf Null stellt.

Am Ende des Step-Events wird geschossen. Wir haben einen Delay definiert und benutzen die zusätzliche Variable canShoot. Nach dem Schuss schalten wir die Variable aus, setzen shooteTimer auf 0 und erzeugen das Bullet im Layer Instances.

Mit lengthdir_x und lengthdir_y erzeugen wir das Bullet knapp vor der Kanone. Anschließend fliegt das Bullet in die Richtung, in welche das Kanonenrohr zeigt. Wir geben dem Bullet noch die ID des Panzers auf den Weg, wünschen ihm eine gute Reise und starten Alarm[0].

Event Alarm[0]

Wenn der Delay fertig ist, wird die Variable canShoot eingeschaltet.

Event Draw

Wenn wir den Balken nicht hätten, bräuchten wir dieses Event nicht.

Mit draw_self(); zeichnen wir den Panzer. Wenn wir schießen können, ist der Balken grün, wenn nicht, ist er annähernd weiß. Die Farbpalette erlaubt nur 236 statt 255. Den Unterschied merkt wahrscheinlich niemand, aber so ist es ein Stück authentischer. Die Breite des Balkens berechnen wir in der Variable width und zeigen ihn mit draw_rectangle an.

Event Collision

Wir erzeugen einen Event Kollision mit der Wand. Einen Code brauchen wir, außer einem Kommentar, nicht. Das dient nur dazu, dass GMS die Kollision mit der Wand (Solid) akzeptiert. Ziemlich banal, aber es funktioniert.

obj_bullet01

Bevor wir mit dem Geschoss beginnen, legen wir auch hier die Eigenschaften fest. Wie man im Original von 1977 sehen kann, prallen die Geschosse von den Wänden ab. Ich möchte es aber nicht so übertreiben, da bei diesem Gameplay mehr der Zufall über Sieg und Niederlage entscheidet. In »GM Tank Combat« soll das Geschoss nur zweimal abprallen können. Berührt es zum dritten Mal eine Wand, wird es zerstört. Außerdem hat es eine Geschwindigkeit und eine Lebensdauer. Eine weitere Eigenschaft besteht daraus, dass sich das Geschoss zerstört, wenn es den Panzer berührt, der ihn abgeschossen hat. Das hat den Vorteil, dass ein Panzer nicht gegen eine Wand vor ihm schießen und damit den Gegner hinter sich treffen kann. Außerdem sieht es albern aus, wenn die Geschosse durch die Panzer fliegen.

Event Create

Damit ist alles definiert. Die Flugrichtung gibt der Panzer vor.

Event Step

Die Lebenszeit läuft ab. Wenn sie bei Null (oder darunter) liegt, zerstört sich das Geschoss.

Event Kollision mit obj_tank01

Wenn die ID nicht identisch ist mit dontKill, wird der andere Panzer zerstört. Momentan sinnfrei, weil wir nur einen Panzer haben, aber wir wollen ja mehr.

Egal ob eigener Panzer oder nicht: Das Geschoss selbst wird immer zerstört, wenn es einen Panzer berührt.

Event Kollision mit obj_wall01

Der Kommentar ist etwas zu konkret, da wir später auch Geschosse kreieren können, die mehrmals abprallen. In der Sache ist es aber richtig. Das Geschoss prallt ab, wenn der Max-Wert erreicht ist, zerstört es sich.

obj_gamecontrolle

Das letzte Objekt. Hier definieren wir vorwiegend den Debug-Modus.

Event Create

Über die Variable debug steuern wir diese Funktion. Wir können das später global tun. Meistens mache ich es so, dass ich bei Spielen einen init-Raum erzeuge. Da lade ich alle Einstellungen, INIs und was es sonst noch braucht. Das erste Objekt ist persistent. Darin definiere ich Tasten, die im ganzen Spiel gültig sind. F4 für den Wechsel zwischen Fenster- und Vollbildmodus ist so ein Fall. Da kann man auch eine Taste, zum Beispiel »D«, für den Debug-Modus definieren. Er schaltet dann nur eine globale Variable um, welche von allen Objekten abgefragt wird, in denen es entsprechende Funktionen gibt.

Event Alarm[0]

Wenn wir fps_real einfach so anzeigen, dann ist die Zahl sehr unruhig. So aber fragen wir sie alle 30 Steps ab und aktualisieren sie.

Event Draw GUI

Die FPS wird unten im Bild angezeigt.

Event Key Up Escape

Bei Escape wird das Spiel beendet.

Aussichten

Wir haben nun ein solides Grundgerüst. Mit dem Panzer können wir durch das Level fahren und schießen. Der Code ist relativ flexibel und wir können weitere Wände, Geschosse und Panzer als »Kinder« der Elternobjekte erstellen. In der zweiten Folge erstellen wir einen zweiten Panzer mit eigener Grafik und einem eigenen Geschoss.

Wer will, kann sich hier noch das Video zur aktuellen Version anschauen:

Download

 

Hat Dir dieser Artikel gefallen? Dann würden wir uns sehr über Unterstützung freuen.

Hinterlasse einen Kommentar

avatar
1024
  Abonnieren  
Benachrichtige mich bei