Inhoudsopgave:

Arduino-gestuurd platformspel met joystick en IR-ontvanger - Ajarnpa
Arduino-gestuurd platformspel met joystick en IR-ontvanger - Ajarnpa

Video: Arduino-gestuurd platformspel met joystick en IR-ontvanger - Ajarnpa

Video: Arduino-gestuurd platformspel met joystick en IR-ontvanger - Ajarnpa
Video: Arduino Tutorial 32: Understanding and Using Joysticks in a Project 2024, Juli-
Anonim
Arduino-gestuurd platformspel met joystick en IR-ontvanger
Arduino-gestuurd platformspel met joystick en IR-ontvanger

Vandaag gaan we een Arduino-microcontroller gebruiken om een eenvoudig op C# gebaseerd platformspel te besturen. Ik gebruik de Arduino om invoer van een joystickmodule te krijgen en die invoer naar de C # -toepassing te sturen die de invoer via een seriële verbinding luistert en decodeert. Hoewel je geen eerdere ervaring met het bouwen van videogames nodig hebt om het project te voltooien, kan het enige tijd duren voordat je een aantal van de dingen die in de "game-loop" gebeuren, die we later zullen bespreken, op je in laten werken.

Om dit project te voltooien, hebt u nodig:

  • Visual Studio-gemeenschap
  • Een Arduino Uno (of vergelijkbaar)
  • Een joystick-controllermodule
  • Geduld

Als je klaar bent om te beginnen, ga dan door!

Stap 1: Sluit de joystick en IR-LED aan

Sluit de joystick en IR-led aan
Sluit de joystick en IR-led aan
Sluit de joystick en IR-led aan
Sluit de joystick en IR-led aan

Hier is de aansluiting vrij eenvoudig. Ik heb diagrammen toegevoegd die alleen de aangesloten joystick tonen, evenals de setup die ik gebruik, waaronder de joystick plus een infrarood-LED voor het besturen van het spel met een afstandsbediening, die bij veel Arduino-kits wordt geleverd. Dit is optioneel, maar het leek me een gaaf idee om draadloos te kunnen gamen.

De pinnen die in de opstelling worden gebruikt zijn:

  • A0 (analoog) <- Horizontaal of X-as
  • A1 (analoog) <- Verticaal of Y-as
  • Pin 2 <- Joystick Switch-ingang
  • Pin 2 <- Infrarood LED-ingang
  • VCC <- 5V
  • Grond
  • Grond #2

Stap 2: Maak een nieuwe schets

Een nieuwe schets maken
Een nieuwe schets maken

We beginnen met het maken van ons Arduino-schetsbestand. Dit peilt de joystick naar wijzigingen en stuurt die wijzigingen om de paar milliseconden naar het C#-programma. In een echte videogame controleerden we de seriële poort in een gameloop op invoer, maar ik begon de game als een experiment, dus de framerate is eigenlijk gebaseerd op het aantal gebeurtenissen op de seriële poort. Ik was eigenlijk aan het project begonnen in het Arduino-zusterproject Processing, maar het bleek veel, veel langzamer te zijn en het aantal vakjes op het scherm niet aan te kunnen.

Maak dus eerst een nieuwe Sketch in het Arduino-code-editorprogramma. Ik zal mijn code laten zien en dan uitleggen wat het doet:

#include "IRremote.h"

// IR-variabelen int ontvanger = 3; // Signaalpen van IR-ontvanger IRrecv irrecv (ontvanger); // maak instantie van 'irrecv' decode_results resultaten; // maak een instantie van 'decode_results' // Joystick/game-variabelen int xPos = 507; int yPos = 507; byte joyXPin = A0; byte joyYPin = A1; byte joySwitch = 2; vluchtige byte clickCounter = -1; int minMoveHigh = 530; int minMoveLow = 490; int stroomsnelheid = 550; // Standaard = een gemiddelde snelheid int speedIncrement = 25; // Bedrag om de snelheid te verhogen/verlagen met Y-invoer zonder teken, lange stroom = 0; // Houdt huidige tijdstempel int wait = 40; // ms om te wachten tussen berichten [Opmerking: lagere wachttijd = snellere framerate] vluchtige bool buttonPressed = false; // Meet of de knop wordt ingedrukt void setup () { Serial.begin (9600); pinMode (joySwitch, INPUT_PULLUP); attachInterrupt(0, springen, VALLEN); stroom = millis(); // Stel de huidige tijd in // Stel infraroodontvanger in: irrecv.enableIRIn(); // Start de ontvanger} // setup void loop () {int xMovement = analogRead (joyXPin); int yPos = analogRead(joyYPin); // Hanteer de Joystick X-beweging ongeacht de timing: if (xMovement > minMoveHigh || xMovement current + wait) { currentSpeed = yPos > minMoveLow && yPos < minMoveHigh // Als het maar een klein beetje bewogen wordt… ? currentSpeed // …geef gewoon de huidige snelheid terug: getSpeed(yPos); // Wijzig yPos alleen als de joystick aanzienlijk is bewogen //int distance =; Serial.print((String) xPos + ", " + (String) yPos + ', ' + (String) currentSpeed + '\n'); stroom = millis(); } } // loop int getSpeed (int yPos) { // Negatieve waarden geven aan dat Joystick omhoog is bewogen if (yPos 1023 ? 1023: currentSpeed + speedIncrement; } else if (yPos > minMoveHigh) // Interpreted "Down" { // Protect from onder 0 terugkeren currentSpeed - speedIncrement < 0 ? 0: currentSpeed - speedIncrement; } } // getSpeed void jump() { buttonPressed = true; // Geeft aan dat de knop is ingedrukt. } // jump // Wanneer een knop wordt ingedrukt op de remote, handel de juiste reactie af void translateIR (decode_results results) // onderneemt actie op basis van ontvangen IR-code {switch(results.valu) { 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(" andere knop "); pauze; }// Eindschakelaar } //END translateIR

Ik heb geprobeerd de code zo duidelijk mogelijk te maken, maar er zijn een paar dingen die het vermelden waard zijn. Een ding dat ik probeerde te verklaren was in de volgende regels:

int minYMoveUp = 520;

int minYMoveDown = 500;

Wanneer het programma draait, heeft de analoge invoer van de joystick de neiging om rond te springen, meestal rond de 507. Om dit te corrigeren, verandert de invoer niet tenzij deze groter is dan minYMoveUp, of kleiner dan minYMoveDown.

pinMode (joySwitch, INPUT_PULLUP);

attachInterrupt(0, spring, FALLING);

Met de methode attachInterrupt() kunnen we de normale lus op elk moment onderbreken, zodat we invoer kunnen nemen, zoals de knop die wordt ingedrukt wanneer op de joystickknop wordt geklikt. Hier hebben we de interrupt in de regel ervoor toegevoegd, met behulp van de methode pinMode(). Een belangrijke opmerking hierbij is dat om een interrupt op de Arduino Uno te bevestigen, je ofwel pin 2 of 3 moet gebruiken. Andere modellen gebruiken andere interrupt-pinnen, dus het kan zijn dat je op de Arduino-website moet controleren welke pinnen je model gebruikt. De tweede parameter is voor de callback-methode, hier een ISR of een "Interrupt Service Routine" genoemd. Het mag geen parameters gebruiken of iets retourneren.

Serie.afdruk(…)

Dit is de regel die onze gegevens naar de C#-game stuurt. Hier sturen we de X-as-aflezing, de Y-as-aflezing en een snelheidsvariabele naar het spel. Deze metingen kunnen worden uitgebreid met andere invoer en metingen om het spel interessanter te maken, maar hier zullen we er slechts een paar gebruiken.

Als je klaar bent om je code te testen, upload deze dan naar de Arduino en druk op [Shift] + [Ctrl] + [M] om de seriële monitor te openen en te kijken of je output krijgt. Als u gegevens van de Arduino ontvangt, zijn we klaar om verder te gaan naar het C # -gedeelte van de code …

Stap 3: Maak het C#-project

Om onze afbeeldingen weer te geven, begon ik aanvankelijk een project in Processing, maar later besloot ik dat het te traag zou zijn om alle objecten weer te geven die we moeten weergeven. Dus koos ik ervoor om C# te gebruiken, dat veel soepeler en responsiever bleek te zijn bij het verwerken van onze invoer.

Voor het C#-gedeelte van het project is het het beste om gewoon het.zip-bestand te downloaden en het uit te pakken in zijn eigen map, en het vervolgens aan te passen. Er zijn twee mappen in het zip-bestand. Om het project in Visual Studio te openen, voert u de map RunnerGame_CSharp in Windows Verkenner in. Dubbelklik hier op het.sln-bestand (oplossing) en VS zal het project laden.

Er zijn een paar verschillende klassen die ik voor het spel heb gemaakt. Ik zal niet ingaan op alle details over elke klasse, maar ik zal een overzicht geven van waar de hoofdklassen voor zijn.

The Box Class

Ik heb de box-klasse gemaakt waarmee je eenvoudige rechthoekige objecten kunt maken die op het scherm in een venstervorm kunnen worden getekend. Het idee is om een klasse te maken die kan worden uitgebreid met andere klassen die misschien een soort van afbeeldingen willen tekenen. Het "virtuele" sleutelwoord wordt gebruikt zodat andere klassen deze kunnen overschrijven (met behulp van het "override" sleutelwoord). Op die manier kunnen we hetzelfde gedrag krijgen voor de klasse Player en de klasse Platform wanneer dat nodig is, en ook de objecten naar wens aanpassen.

Maak je niet al te veel zorgen over alle eigendommen en trek telefoontjes aan. Ik heb deze les geschreven zodat ik deze kon uitbreiden voor elk spel of grafisch programma dat ik in de toekomst zou willen maken. Als je eenvoudig een rechthoek moet tekenen, hoef je zo'n grote klasse niet uit te schrijven. De C#-documentatie heeft goede voorbeelden van hoe u dit kunt doen.

Ik zal echter een deel van de logica van mijn "Box" -klasse uiteenzetten:

openbare virtuele bool IsCollidedX(Box otherObject) { … }

Hier controleren we op botsingen met objecten in de X-richting, omdat de speler alleen hoeft te controleren op botsingen in de Y-richting (omhoog en omlaag) als hij ermee in lijn staat op het scherm.

openbare virtuele bool IsCollidedY(Box otherObject) { … }

Wanneer we ons boven of onder een ander spelobject bevinden, controleren we op Y-botsingen.

openbare virtuele bool IsCollided(Box otherObject) { … }

Dit combineert X- en Y-botsingen en geeft terug of een object hiermee is gebotst.

openbare virtuele leegte OnPaint (grafische afbeeldingen) { … }

Met behulp van de bovenstaande methode geven we elk grafisch object door en gebruiken het terwijl het programma draait. We maken alle rechthoeken die mogelijk moeten worden getekend. Dit kan echter voor verschillende animaties worden gebruikt. Voor onze doeleinden zullen rechthoeken het goed doen voor zowel de platforms als de speler.

De karakterklasse

De Character-klasse is een uitbreiding van mijn Box-klasse, dus we hebben bepaalde fysica uit de doos. Ik heb de "CheckForCollisions"-methode gemaakt om snel alle platforms die we hebben gemaakt te controleren op een botsing. De "Jump"-methode stelt de opwaartse snelheid van de speler in op de JumpSpeed-variabele, die vervolgens frame voor frame wordt gewijzigd in de klasse MainWindow.

Aanrijdingen worden hier iets anders afgehandeld dan in de Box-klasse. Ik heb in dit spel besloten dat als we omhoog springen, we door een platform kunnen springen, maar het zal onze speler op de weg naar beneden vangen als hij ermee in botsing komt.

De platformklas

In dit spel gebruik ik alleen de constructor van deze klasse die een X-coördinaat als invoer neemt en alle X-locaties van het platform in de MainWindow-klasse berekent. Elk platform is opgesteld op een willekeurige Y-coördinaat van 1/2 het scherm tot 3/4 van de hoogte van het scherm. De hoogte, breedte en kleur worden ook willekeurig gegenereerd.

De MainWindow-klasse

Hier plaatsen we alle logica die moet worden gebruikt terwijl het spel draait. Eerst drukken we in de constructor alle COM-poorten af die beschikbaar zijn voor het programma.

foreach (stringpoort in SerialPort. GetPortNames())

Console. WriteLine("BESCHIKBARE POORTEN: " + poort);

We kiezen op welke we communicatie accepteren, afhankelijk van welke poort je Arduino al gebruikt:

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

Let goed op het commando: SerialPort. GetPortNames()[2]. De [2] geeft aan welke seriële poort moet worden gebruikt. Als het programma bijvoorbeeld "COM1, COM2, COM3" zou afdrukken, zouden we naar COM3 luisteren omdat de nummering begint bij 0 in de array.

Ook in de constructor maken we alle platforms met semi-willekeurige tussenruimte en plaatsing in de Y-richting op het scherm. Alle platforms worden toegevoegd aan een List-object, wat in C# gewoon een zeer gebruiksvriendelijke en efficiënte manier is om een array-achtige datastructuur te beheren. Vervolgens maken we de speler, ons Character-object, stellen de score in op 0 en stellen GameOver in op false.

private statische leegte DataReceived (objectafzender, SerialDataReceivedEventArgs e)

Dit is de methode die wordt aangeroepen wanneer gegevens worden ontvangen op de seriële poort. Dit is waar we al onze fysica toepassen, beslissen of we game over willen weergeven, de platforms willen verplaatsen, enz. Als je ooit een game hebt gebouwd, heb je over het algemeen een zogenaamde "gameloop", die elke keer dat het frame wordt genoemd verfrist. In dit spel fungeert de DataReceived-methode als de spellus, waarbij alleen de fysica wordt gemanipuleerd wanneer gegevens van de controller worden ontvangen. Het had misschien beter gewerkt om een timer in het hoofdvenster in te stellen en de objecten te vernieuwen op basis van de ontvangen gegevens, maar aangezien dit een Arduino-project is, wilde ik een spel maken dat daadwerkelijk draaide op basis van de gegevens die eruit kwamen.

Kortom, deze opzet geeft een goede basis om het spel uit te breiden tot iets bruikbaars. Hoewel de fysica niet helemaal perfect is, werkt het goed genoeg voor onze doeleinden, namelijk om de Arduino te gebruiken voor iets dat iedereen leuk vindt: spelletjes spelen!

Aanbevolen: