Inhaltsverzeichnis:

Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger - Gunook
Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger - Gunook

Video: Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger - Gunook

Video: Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger - Gunook
Video: GIANT FREAKING LASER SHOOTING ROBOT | Albert and Otto Part 2 2024, November
Anonim
Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger
Arduino-gesteuertes Platformer-Spiel mit Joystick und IR-Empfänger

Heute verwenden wir einen Arduino-Mikrocontroller, um ein einfaches C#-basiertes Plattformspiel zu steuern. Ich verwende den Arduino, um Eingaben von einem Joystick-Modul zu empfangen und diese Eingabe an die C#-Anwendung zu senden, die die Eingabe über eine serielle Verbindung abhört und decodiert. Obwohl Sie keine Vorkenntnisse in der Entwicklung von Videospielen benötigen, um das Projekt abzuschließen, kann es einige Zeit dauern, einige der Dinge zu verarbeiten, die in der "Spielschleife" vor sich gehen, die wir später besprechen werden.

Um dieses Projekt abzuschließen, benötigen Sie:

  • Visual Studio-Community
  • Ein Arduino Uno (oder ähnlich)
  • Ein Joystick-Controller-Modul
  • Die Geduld

Wenn Sie bereit sind zu beginnen, machen Sie weiter!

Schritt 1: Schließen Sie den Joystick und die IR-LED an

Schließen Sie den Joystick und die IR-LED an
Schließen Sie den Joystick und die IR-LED an
Schließen Sie den Joystick und die IR-LED an
Schließen Sie den Joystick und die IR-LED an

Hier ist der Anschluss recht einfach. Ich habe Diagramme beigefügt, die nur den angeschlossenen Joystick sowie das von mir verwendete Setup zeigen, das den Joystick und eine Infrarot-LED zur Steuerung des Spiels mit einer Fernbedienung enthält, die mit vielen Arduino-Kits geliefert wird. Dies ist optional, aber es schien eine coole Idee zu sein, kabellos zu spielen.

Die im Setup verwendeten Pins sind:

  • A0 (analog) <- Horizontale oder X-Achse
  • A1 (analog) <- Vertikale oder Y-Achse
  • Pin 2 <- Joystick-Schaltereingang
  • Pin 2 <- Infrarot-LED-Eingang
  • VCC <- 5V
  • Boden
  • Masse #2

Schritt 2: Erstellen Sie eine neue Skizze

Erstellen Sie eine neue Skizze
Erstellen Sie eine neue Skizze

Wir beginnen mit der Erstellung unserer Arduino-Skizzendatei. Dies fragt den Joystick nach Änderungen ab und sendet diese Änderungen alle paar Millisekunden an das C#-Programm. In einem echten Videospiel würden wir den seriellen Port in einer Spielschleife auf Eingaben überprüfen, aber ich habe das Spiel als Experiment begonnen, also basiert die Framerate tatsächlich auf der Anzahl der Ereignisse auf dem seriellen Port. Ich hatte das Projekt eigentlich im Arduino-Schwesterprojekt Processing begonnen, aber es stellte sich heraus, dass es viel, viel langsamer war und die Anzahl der Boxen auf dem Bildschirm nicht bewältigen konnte.

Erstellen Sie also zuerst eine neue Skizze im Arduino-Code-Editor-Programm. Ich zeige meinen Code und erkläre dann, was er tut:

#include "IRremote.h"

// IR-Variablen int Receiver = 3; // Signal-Pin des IR-Empfängers IRrecv irrecv (Empfänger); // Instanz von 'irrecv' erstellen decode_results Ergebnisse; // Instanz von 'decode_results' erstellen // Joystick-/Spielvariablen int xPos = 507; int yPos = 507; Byte joyXPin = A0; Byte joyYPin = A1; Byte joySwitch = 2; flüchtiger Byte-Klickzähler = -1; int minMoveHigh = 530; int minMoveLow = 490; int aktuelle Geschwindigkeit = 550; // Standard = eine Durchschnittsgeschwindigkeit int speedIncrement = 25; // Betrag zum Erhöhen / Verringern der Geschwindigkeit mit Y-Eingang unsigned long current = 0; // Hält den aktuellen Zeitstempel int wait = 40; // ms, um zwischen Nachrichten zu warten [Hinweis: niedrigere Wartezeit = schnellere Framerate] volatile bool buttonPressed = false; // Messen, wenn die Taste gedrückt wird void setup () { Serial.begin (9600); pinMode (joySwitch, INPUT_PULLUP); attachInterrupt(0, Sprung, FALLING); Strom = Millis(); // Aktuelle Uhrzeit einstellen // Infrarot-Empfänger einrichten: irrecv.enableIRIn(); // Starten Sie den Empfänger} // setup void loop () { Int xMovement = analogRead (joyXPin); int yPos = analogRead(joyYPin); // Behandeln Sie die Joystick X-Bewegung unabhängig vom Timing: if (xMovement > minMoveHigh || xMovement current + wait) { currentSpeed = yPos > minMoveLow && yPos < minMoveHigh // Wenn nur ein wenig bewegt… ? currentSpeed // …nur die aktuelle Geschwindigkeit zurückgeben: getSpeed(yPos); // yPos nur ändern, wenn sich der Joystick deutlich bewegt //int distance =; Serial.print ((String) xPos + ", " + (String) yPos + ', ' + (String) currentSpeed + '\n'); Strom = Millis(); aufrechtzuerhalten unter 0 gehen return currentSpeed - speedIncrement < 0 ? 0: currentSpeed - speedIncrement; } } // getSpeed void jump() { buttonPressed = true; // Zeigt an, dass die Taste gedrückt wurde. } // jump // Wenn eine Taste auf dem. gedrückt wird remote, handhabe die richtige Antwort void translateIR (decode_results results) // ergreift Maßnahmen basierend auf dem empfangenen IR-Code { switch (results.value) { case 0xFF18E7: // Serial.println ("2"); currentSpeed += speedIncrement * 2; break; case 0xFF10EF: //Serial.println("4"); xPos = -900; break; case 0xFF38C7: //Serial.println("5"); jump(); break; case 0xFF5AA5: //Serial. println("6"); xPos = 900; break; case 0xFF4AB5: //Serial.println("8"); currentSpeed -= speedIncrement * 2; break; default: //Serial.println(" other button"); break; } // Endschalter } //END translateIR

Ich habe versucht, den Code weitgehend selbsterklärend zu erstellen, aber es gibt ein paar Dinge, die es wert sind, erwähnt zu werden. Eine Sache, die ich zu erklären versuchte, war in den folgenden Zeilen:

int minYMoveUp = 520;

int minYMoveDown = 500;

Wenn das Programm läuft, neigt der analoge Eingang vom Joystick dazu, herumzuspringen und bleibt normalerweise bei etwa 507. Um dies zu korrigieren, ändert sich der Eingang nicht, es sei denn, er ist größer als minYMoveUp oder kleiner als minYMoveDown.

pinMode (joySwitch, INPUT_PULLUP);

attachInterrupt(0, Sprung, FALLING);

Die Methode attachInterrupt() ermöglicht es uns, die normale Schleife jederzeit zu unterbrechen, um Eingaben wie das Drücken der Taste beim Klicken auf den Joystick entgegenzunehmen. Hier haben wir den Interrupt in der Zeile davor mit der Methode pinMode() angehängt. Ein wichtiger Hinweis hier ist, dass Sie zum Anschließen eines Interrupts am Arduino Uno entweder Pin 2 oder 3 verwenden müssen. Andere Modelle verwenden andere Interrupt-Pins, daher müssen Sie möglicherweise auf der Arduino-Website überprüfen, welche Pins Ihr Modell verwendet. Der zweite Parameter ist für die Callback-Methode, hier ISR oder "Interrupt Service Routine" genannt. Es sollte keine Parameter annehmen oder irgendetwas zurückgeben.

Seriendruck(…)

Dies ist die Zeile, die unsere Daten an das C#-Spiel sendet. Hier senden wir den X-Achsen-Messwert, den Y-Achsen-Messwert und eine Geschwindigkeitsvariable an das Spiel. Diese Messwerte können um andere Eingaben und Messwerte erweitert werden, um das Spiel interessanter zu machen, aber hier werden wir nur einige verwenden.

Wenn Sie bereit sind, Ihren Code zu testen, laden Sie ihn auf den Arduino hoch und drücken Sie [Shift] + [Strg] + [M], um den seriellen Monitor zu öffnen und zu sehen, ob Sie eine Ausgabe erhalten. Wenn Sie Daten vom Arduino erhalten, können wir zum C#-Teil des Codes übergehen…

Schritt 3: Erstellen Sie das C#-Projekt

Um unsere Grafiken anzuzeigen, habe ich zunächst ein Projekt in Processing gestartet, aber später entschieden, dass es zu langsam wäre, alle Objekte anzuzeigen, die wir anzeigen müssen. Also entschied ich mich für C#, was sich bei der Verarbeitung unserer Eingaben als viel reibungsloser und reaktionsschneller herausstellte.

Für den C#-Teil des Projekts ist es am besten, einfach die ZIP-Datei herunterzuladen, in einen eigenen Ordner zu extrahieren und dann zu ändern. Es gibt zwei Ordner in der Zip-Datei. Um das Projekt in Visual Studio zu öffnen, geben Sie den RunnerGame_CSharp-Ordner in Windows Explorer ein. Doppelklicken Sie hier auf die.sln-Datei (Lösung), und VS lädt das Projekt.

Es gibt ein paar verschiedene Klassen, die ich für das Spiel erstellt habe. Ich werde nicht auf alle Details zu jeder Klasse eingehen, aber ich werde einen Überblick darüber geben, wofür die Hauptklassen gedacht sind.

Die Box-Klasse

Ich habe die Box-Klasse erstellt, damit Sie einfache Rechteckobjekte erstellen können, die auf dem Bildschirm in einem Windows-Formular gezeichnet werden können. Die Idee ist, eine Klasse zu erstellen, die mit anderen Klassen erweitert werden kann, die möglicherweise eine Art von Grafiken zeichnen möchten. Das Schlüsselwort "virtual" wird verwendet, damit andere Klassen sie überschreiben können (unter Verwendung des Schlüsselworts "override"). Auf diese Weise können wir bei Bedarf das gleiche Verhalten für die Player-Klasse und die Platform-Klasse erzielen und die Objekte auch nach Bedarf ändern.

Machen Sie sich nicht zu viele Gedanken über alle Eigenschaften und zeichnen Sie Calls. Ich habe diese Klasse geschrieben, damit ich sie für jedes Spiel oder Grafikprogramm erweitern kann, das ich in Zukunft machen möchte. Wenn Sie einfach im Handumdrehen ein Rechteck zeichnen müssen, müssen Sie keine große Klasse wie diese schreiben. Die C#-Dokumentation enthält gute Beispiele dafür.

Ich werde jedoch einige der Logik meiner "Box" -Klasse darlegen:

public virtual bool IsCollidedX(Box otherObject) { … }

Hier prüfen wir auf Kollisionen mit Objekten in X-Richtung, da der Spieler nur dann auf Kollisionen in Y-Richtung (oben und unten) prüfen muss, wenn er damit auf dem Bildschirm ausgerichtet ist.

public virtual bool IsCollidedY(Box otherObject) { … }

Wenn wir uns über oder unter einem anderen Spielobjekt befinden, prüfen wir auf Y-Kollisionen.

public virtual bool IsCollided(Box otherObject) { … }

Dies kombiniert X- und Y-Kollisionen und gibt zurück, ob ein Objekt mit diesem kollidiert ist.

public virtual void OnPaint(Grafikgrafik) { … }

Mit der obigen Methode übergeben wir ein beliebiges Grafikobjekt und verwenden es, während das Programm läuft. Wir erstellen alle Rechtecke, die möglicherweise gezeichnet werden müssen. Dies könnte jedoch für eine Vielzahl von Animationen verwendet werden. Für unsere Zwecke reichen Rechtecke sowohl für die Plattformen als auch für den Spieler aus.

Die Charakterklasse

Die Character-Klasse erweitert meine Box-Klasse, so dass wir eine gewisse Physik out of the box haben. Ich habe die Methode "CheckForCollisions" erstellt, um alle von uns erstellten Plattformen schnell auf Kollisionen zu überprüfen. Die Methode "Jump" setzt die Aufwärtsgeschwindigkeit des Players auf die Variable JumpSpeed, die dann Frame für Frame in der MainWindow-Klasse modifiziert wird.

Kollisionen werden hier etwas anders behandelt als in der Box-Klasse. Ich habe in diesem Spiel entschieden, dass wir beim Aufwärtsspringen durch eine Plattform springen können, aber sie wird unseren Spieler auf dem Weg nach unten erwischen, wenn sie damit kollidiert.

Die Plattformklasse

In diesem Spiel verwende ich nur den Konstruktor dieser Klasse, der eine X-Koordinate als Eingabe verwendet und alle X-Positionen der Plattformen in der MainWindow-Klasse berechnet. Jede Plattform wird an einer zufälligen Y-Koordinate von 1/2 des Bildschirms bis 3/4 der Bildschirmhöhe aufgestellt. Höhe, Breite und Farbe werden ebenfalls zufällig generiert.

Die MainWindow-Klasse

Hier legen wir die gesamte Logik ab, die während des Spiels verwendet werden soll. Zuerst geben wir im Konstruktor alle COM-Ports aus, die dem Programm zur Verfügung stehen.

foreach(String-Port in SerialPort. GetPortNames())

Console. WriteLine("VERFÜGBARE PORTS: " + port);

Wir wählen aus, auf welchem Port wir die Kommunikation akzeptieren, je nachdem, welchen Port Ihr Arduino bereits verwendet:

SerialPort = neuer SerialPort (SerialPort. GetPortNames()[2], 9600, Parity. None, 8, StopBits. One);

Achten Sie genau auf den Befehl: SerialPort. GetPortNames()[2]. [2] gibt an, welcher serielle Port verwendet werden soll. Wenn das Programm beispielsweise "COM1, COM2, COM3" ausgibt, hören wir auf COM3, da die Nummerierung im Array bei 0 beginnt.

Auch im Konstruktor erstellen wir alle Plattformen mit halbzufälligem Abstand und Platzierung in Y-Richtung auf dem Bildschirm. Alle Plattformen werden einem List-Objekt hinzugefügt, was in C# einfach eine sehr benutzerfreundliche und effiziente Möglichkeit ist, eine Array-ähnliche Datenstruktur zu verwalten. Wir erstellen dann den Player, das unser Character-Objekt ist, setzen die Punktzahl auf 0 und setzen GameOver auf false.

privat statisch void DataReceived(Objektsender, SerialDataReceivedEventArgs e)

Dies ist die Methode, die aufgerufen wird, wenn Daten am seriellen Port empfangen werden. Hier wenden wir unsere gesamte Physik an, entscheiden, ob das Spiel angezeigt wird, die Plattformen verschoben werden usw. Wenn Sie jemals ein Spiel gebaut haben, haben Sie im Allgemeinen eine sogenannte "Spielschleife", die jedes Mal aufgerufen wird, wenn der Frame erfrischt. In diesem Spiel fungiert die DataReceived-Methode als Spielschleife und manipuliert nur die Physik, wenn Daten vom Controller empfangen werden. Es hätte vielleicht besser funktioniert, einen Timer im Hauptfenster einzurichten und die Objekte basierend auf den empfangenen Daten zu aktualisieren, aber da es sich um ein Arduino-Projekt handelt, wollte ich ein Spiel erstellen, das tatsächlich basierend auf den eingehenden Daten lief.

Zusammenfassend ist dieses Setup eine gute Grundlage, um das Spiel zu etwas Brauchbarem zu erweitern. Obwohl die Physik nicht ganz perfekt ist, funktioniert es für unsere Zwecke gut genug, nämlich den Arduino für etwas zu verwenden, das jeder mag: Spiele spielen!

Empfohlen: