Spieleingabe vereinfacht

Stellen Sie sich eine Spielfigur namens "Bob the Butcher" vor, die alleine in einem dunklen Raum steht, während Horden von mutierten Wurstzombies durch Türen und zerbrochene Fenster strömen. An diesem Punkt wäre es eine gute Idee für Bob, die Wurst-Zombies in winzige Fleischstücke zu sprengen, aber wie wird Bob das in einem Cross-Plattform-Spiel tun? Muss der Spieler eine oder mehrere Tasten auf einer Tastatur drücken, mit der Maus klicken, auf den Bildschirm tippen oder eine Taste auf einem Gamepad drücken?

Wenn Sie ein plattformübergreifendes Spiel programmieren, ist dies die Art von Dingen, mit der Sie wahrscheinlich viel Zeit damit verbringen, zu kämpfen, wenn Sie nicht darauf vorbereitet sind. Wenn Sie nicht vorsichtig sind, kann es zu massiven Spaghetti-ähnlichen Ergebnissen kommen ob Aussagen oder Schalter Anweisungen für alle verschiedenen Eingabegeräte.

In diesem Lernprogramm werden wir die Dinge viel einfacher machen, indem Sie eine einzige Klasse erstellen, die mehrere Eingabegeräte vereinheitlicht. Jede Instanz der Klasse repräsentiert eine bestimmte Spielaktion oder ein bestimmtes Verhalten (z. B. "Schießen", "Laufen" oder "Springen") und kann angewiesen werden, verschiedene Tasten, Tasten und Zeiger auf mehreren Eingabegeräten zu hören.

Hinweis: Die in diesem Lernprogramm verwendete Programmiersprache ist JavaScript. Die Technik, mit der mehrere Eingabegeräte vereinheitlicht werden, kann problemlos auf jede andere plattformübergreifende Programmiersprache übertragen werden, die APIs für Eingabegeräte bereitstellt.

Würstchen schießen

Bevor wir mit dem Schreiben des Codes für die Klasse, die wir in diesem Lernprogramm erstellen werden, beginnen, werfen wir einen Blick darauf, wie die Klasse tatsächlich verwendet werden könnte.

// Erzeuge eine Eingabe für die Aktion "Schießen". schießen = neuer GameInput (); // Sag der Eingabe, worauf sie reagieren soll. shoot.add (GameInput.KEYBOARD_SPACE); shoot.add (GameInput.GAMEPAD_RT); // Überprüfen Sie bei jedem Spielupdate die Eingabe. function update () if (shoot.value> 0) // Bitten Sie Bob, die mutierten Wurstzombies zu erschießen!  else // Sage Bob, dass er mit dem Schießen aufhören soll. 

GameInput ist die Klasse, die wir erstellen werden, und Sie können sehen, wie viel einfacher die Dinge werden. Das schießen.wert Eigenschaft ist eine Zahl und ist ein positiver Wert, wenn die Leertaste auf einer Tastatur wird gedrückt oder die rechter Abzug auf einem Gamepad wird gedrückt. Wenn weder die Leertaste noch der rechte Auslöser gedrückt wird, ist der Wert Null.

Fertig machen

Das erste, was wir tun müssen, ist das Schließen einer Funktion für die GameInput Klasse. Der größte Teil des Codes, den wir schreiben werden, ist eigentlich nicht Teil der Klasse. Er muss jedoch von innerhalb der Klasse aus zugänglich sein, während er vor allem anderen verborgen bleibt. Eine Funktionsschließung erlaubt uns dies in JavaScript.

(In einer Programmiersprache wie ActionScript oder C # können Sie einfach private Klassenmitglieder verwenden, aber das ist in JavaScript leider kein Luxus.)

(function () // Code geht hier hin) ();

Der Rest des Codes in diesem Lernprogramm ersetzt den Kommentar "Code goes here".

Die Variablen

Der Code benötigt nur eine Handvoll Variablen, die außerhalb von Funktionen definiert werden müssen. Diese Variablen lauten wie folgt.

var TASTATUR = 1; var POINTER = 2; var GAMEPAD = 3; var GERÄT = 16; var CODE = 8; var __pointer = currentX: 0, currentY: 0, previousX: 0, previousY: 0, distanceX: 0, distanceY: 0, Bezeichner: 0, verschoben: falsch, gedrückt: false; var __keyboard = ; var __inputs = []; var __channels = []; var __mouseDetected = false; var __touchDetected = false;

Die Konstante TASTATUR, ZEIGER, GAMEPAD, GERÄT und CODE Werte werden zur Definition verwendet Eingabegerät Kanäle, sowie GameInput.KEYBOARD_SPACE, und ihre Verwendung wird später im Tutorial klar.

Das __Zeiger Das Objekt enthält Eigenschaften, die für Eingabegeräte für Maus und Touchscreen relevant sind __Tastatur Objekt wird verwendet, um den Status der Tastaturtasten zu verfolgen. Das __inputs und __channels Arrays werden zum Speichern verwendet GameInput Instanzen und alle zu diesen Instanzen hinzugefügten Eingabegerätekanäle. Endlich, das __mouseDetected und __touchDetected Zeigt an, ob eine Maus oder ein Touchscreen erkannt wurde.

Hinweis: Den Variablen müssen nicht zwei Unterstriche vorangestellt werden. Das ist einfach die Codierungskonvention, die ich für den Code in diesem Lernprogramm verwendet habe. Es hilft, sie von in Funktionen definierten Variablen zu trennen.

Die Funktionen

Hier kommt der Großteil des Codes, daher möchten Sie vielleicht einen Kaffee oder etwas trinken, bevor Sie mit dem Lesen dieses Teils beginnen!

Diese Funktionen werden nach den Variablen im vorherigen Abschnitt dieses Lernprogramms definiert und in der Reihenfolge des Erscheinungsbilds definiert.

// Initialisiert das Eingabesystem. function main () // Den GameInput-Konstruktor verfügbar machen. window.GameInput = GameInput; // Füge die Event Listener hinzu. addMouseListeners (); addTouchListeners (); addKeyboardListeners (); // Einige UI-Aktionen, die wir in einem Spiel verhindern sollten. window.addEventListener ("contextmenu", killEvent, true); window.addEventListener ("selectstart", killEvent, true); // Starten Sie die Aktualisierungsschleife. window.requestAnimationFrame (Update); 

Das Main() Funktion wird am Ende des Codes aufgerufen, dh am Ende des Codes Funktionsabschluss haben wir zuvor erstellt. Es macht, was es auf der Dose sagt und bringt so alles zum Laufen GameInput Klasse kann verwendet werden.

Eine Sache, die ich auf Sie aufmerksam machen sollte, ist die Verwendung von requestAnimationFrame () Funktion, die Teil der W3C-Spezifikation für das Animations-Timing ist. Moderne Spiele und Anwendungen verwenden diese Funktion, um Update- oder Rendering-Schleifen auszuführen, da sie in den meisten Webbrowsern für diesen Zweck stark optimiert wurden.

// Aktualisiert das Eingabesystem. function update () window.requestAnimationFrame (Aktualisierung); // Aktualisiere zuerst die Zeigerwerte. updatePointer (); var i = __inputs.length; var input = null; var channels = null; während (i -> 0) input = __inputs [i]; channels = __channels [i]; if (input.enabled === true) updateInput (Eingang, Kanäle);  else input.value = 0; 

Das aktualisieren() Funktion rollt durch die Liste der aktiven GameInput Instanzen und Updates der aktivierten. Folgende updateInput () Funktion ist ziemlich lang, daher füge ich den Code hier nicht hinzu; Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.

// Aktualisiert eine GameInput-Instanz. Funktion updateInput (Eingabe, Kanäle) // Anmerkung: siehe Quelldateien

Das updateInput () Die Funktion betrachtet die Eingabegerätekanäle, die zu einem hinzugefügt wurden GameInput Beispiel und klappt was Wert Eigentum der GameInput Instanz sollte auf gesetzt sein. Wie im gesehen Würstchen schießen Beispielcode, der Wert Diese Eigenschaft gibt an, ob ein Eingabegerätekanal ausgelöst wird, und dies ermöglicht einem Spiel, entsprechend zu reagieren, indem es beispielsweise Bob anweist, die mutierten Wurst-Zombies abzuschießen.

// Aktualisiert den Wert einer GameInput-Instanz. Funktion updateValue (Eingabe, Wert, Schwelle) if (Schwelle! == undefined) if (Wert < threshold )  value = 0;   // The highest value has priority. if( input.value < value )  input.value = value;  

Das updateValue () Funktion bestimmt, ob die Wert Eigentum eines GameInput Instanz sollte aktualisiert werden. Das Schwelle wird hauptsächlich verwendet, um zu verhindern, dass analoge Geräteeingangskanäle, wie Gamepad-Tasten und -Sticks, die sich nicht richtig zurücksetzen, ständig ein Signal auslösen GameInput Beispiel. Dies passiert häufig bei fehlerhaften oder schmuddeligen Gamepads.

Wie updateInput () Funktion, die folgende updatePointer () Funktion ist ziemlich lang, ich werde den Code hier nicht hinzufügen. Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.

// Aktualisiert die Zeigerwerte. Funktion updatePointer () // Hinweis: siehe Quelldateien

Das updatePointer () Funktion aktualisiert die Eigenschaften in der __Zeiger Objekt. Kurz gesagt: Die Funktion klemmt die Position des Zeigers fest, um sicherzustellen, dass er das Ansichtsfenster des Webbrowsers nicht verlässt, und berechnet die Entfernung, die der Zeiger seit der letzten Aktualisierung verschoben wurde.

// Wird aufgerufen, wenn ein Mauseingabegerät erkannt wird. function mouseDetected () if (__mouseDetected === false) __mouseDetected = true; // Berührungsereignisse ignorieren, wenn eine Maus verwendet wird. removeTouchListeners ();  // Wird aufgerufen, wenn ein Touchscreen-Eingabegerät erkannt wird. Funktion touchDetected () if (__touchDetected === false) __touchDetected = true; // Mausereignisse ignorieren, wenn ein Touchscreen verwendet wird. removeMouseListeners (); 

Das mouseDetected () und touchDetected () Funktionen weisen den Code an, das eine oder andere Eingabegerät zu ignorieren. Wenn eine Maus vor einem Touchscreen erkannt wird, wird der Touchscreen ignoriert. Wenn ein Touchscreen vor einer Maus erkannt wird, wird die Maus ignoriert.

// Wird aufgerufen, wenn ein zeigerähnliches Eingabegerät gedrückt wird. FunktionszeigerPressed (x, y, Bezeichner) __pointer.identifier = Bezeichner; __pointer.pressed = true; pointerMoved (x, y);  // Wird aufgerufen, wenn ein zeigerähnliches Eingabegerät freigegeben wird. function pointerReleased () __pointer.identifier = 0; __pointer.pressed = false;  // Wird aufgerufen, wenn ein zeigerähnliches Eingabegerät verschoben wird. FunktionszeigerMoved (x, y) __pointer.currentX = x >>> 0; __pointer.currentY = y >>> 0; if (__pointer.moved === false) __pointer.moved = true; __pointer.previousX = __pointer.currentX; __pointer.previousY = __pointer.currentY; 

Das pointerPressed (), pointerReleased () und pointerMoved () Funktionen übernehmen die Eingabe über eine Maus oder einen Touchscreen. Alle drei Funktionen aktualisieren einfach die Eigenschaften im __Zeiger Objekt.

Nach diesen drei Funktionen haben wir eine Handvoll Standard-JavaScript-Ereignisbehandlungsfunktionen. Die Funktionen sind selbsterklärend, daher füge ich den Code hier nicht hinzu. Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.

// Fügt einer GameInput-Instanz einen Eingabegerätekanal hinzu. Funktion inputAdd (Eingang, Kanal) var i = __inputs.indexOf (Eingang); if (i === -1) __inputs.push (Eingabe); __channels.push ([channel]); Rückkehr;  var ca = __channels [i]; var ci = ca.indexOf (Kanal); if (ci === -1) ca.push (channel);  // Entfernt einen Eingabegerätkanal in eine GameInput-Instanz. Funktion inputRemove (Eingang, Kanal) var i = __inputs.indexOf (Eingang); if (i === -1) return;  var ca = __channels [i]; var ci = ca.indexOf (Kanal); if (ci! == -1) ca.splice (ci, 1); if (ca.length === 0) __inputs.splice (i, 1); __channels.splice (i, 1);  // Setzt eine GameInput-Instanz zurück. function inputReset (Eingabe) var i = __inputs.indexOf (Eingabe); if (i! == -1) __inputs.splice (i, 1); __channels.splice (i, 1);  input.value = 0; input.enabled = true; 

Das inputAdd (), inputRemove () und inputReset () Funktionen werden von a aufgerufen GameInput Instanz (siehe unten). Die Funktionen ändern die __inputs und __channels Arrays abhängig davon, was zu tun ist.

EIN GameInput Die Instanz wird als aktiv betrachtet und der hinzugefügt __inputs Array, wenn ein Eingabegerätkanal dem hinzugefügt wurde GameInput Beispiel. Wenn ein aktiver GameInput Instanz hat alle Eingabegerätkanäle entfernt, die GameInput Instanz als inaktiv betrachtet und aus dem entfernt __inputs Array.

Nun kommen wir am an GameInput Klasse.

// GameInput-Konstruktor. function GameInput ()  GameInput.prototype = value: 0, aktiviert: true, // Fügt einen Eingabegerätekanal hinzu. add: function (channel) inputAdd (this, channel); , // Entfernt einen Eingabegerätekanal. remove: function (channel) inputRemove (this, channel); , // Entfernt alle Eingabegerätekanäle. reset: function () inputReset (this); ;

Ja, das ist alles, was es gibt - es ist eine superleichte Klasse, die im Wesentlichen als Schnittstelle zum Hauptcode dient. Das Wert Eigenschaft ist eine Zahl, die von reicht 0 (null) bis zu 1 (ein). Wenn der Wert ist 0, es bedeutet, dass die GameInput Die Instanz empfängt nichts von den hinzugefügten Kanälen der Eingabegeräte.

Das GameInput Klasse hat einige statische Eigenschaften, daher werden wir diese jetzt hinzufügen.

// Die X-Position des Zeigers im Fenster-Ansichtsfenster. GameInput.pointerX = 0; // Die Y-Position des Zeigers im Fenster-Ansichtsfenster. GameInput.pointerY = 0; // Die Entfernung, um die sich der Zeiger in Pixeln pro Bild bewegen muss, damit // der Wert einer GameInput-Instanz gleich 1,0 ist. GameInput.pointerSpeed ​​= 10;

Tastaturgerätekanäle:

GameInput.KEYBOARD_A = TASTATUR << DEVICE | 65 << CODE; GameInput.KEYBOARD_B = KEYBOARD << DEVICE | 66 << CODE; GameInput.KEYBOARD_C = KEYBOARD << DEVICE | 67 << CODE; GameInput.KEYBOARD_D = KEYBOARD << DEVICE | 68 << CODE; GameInput.KEYBOARD_E = KEYBOARD << DEVICE | 69 << CODE; GameInput.KEYBOARD_F = KEYBOARD << DEVICE | 70 << CODE; GameInput.KEYBOARD_G = KEYBOARD << DEVICE | 71 << CODE; GameInput.KEYBOARD_H = KEYBOARD << DEVICE | 72 << CODE; GameInput.KEYBOARD_I = KEYBOARD << DEVICE | 73 << CODE; GameInput.KEYBOARD_J = KEYBOARD << DEVICE | 74 << CODE; GameInput.KEYBOARD_K = KEYBOARD << DEVICE | 75 << CODE; GameInput.KEYBOARD_L = KEYBOARD << DEVICE | 76 << CODE; GameInput.KEYBOARD_M = KEYBOARD << DEVICE | 77 << CODE; GameInput.KEYBOARD_N = KEYBOARD << DEVICE | 78 << CODE; GameInput.KEYBOARD_O = KEYBOARD << DEVICE | 79 << CODE; GameInput.KEYBOARD_P = KEYBOARD << DEVICE | 80 << CODE; GameInput.KEYBOARD_Q = KEYBOARD << DEVICE | 81 << CODE; GameInput.KEYBOARD_R = KEYBOARD << DEVICE | 82 << CODE; GameInput.KEYBOARD_S = KEYBOARD << DEVICE | 83 << CODE; GameInput.KEYBOARD_T = KEYBOARD << DEVICE | 84 << CODE; GameInput.KEYBOARD_U = KEYBOARD << DEVICE | 85 << CODE; GameInput.KEYBOARD_V = KEYBOARD << DEVICE | 86 << CODE; GameInput.KEYBOARD_W = KEYBOARD << DEVICE | 87 << CODE; GameInput.KEYBOARD_X = KEYBOARD << DEVICE | 88 << CODE; GameInput.KEYBOARD_Y = KEYBOARD << DEVICE | 89 << CODE; GameInput.KEYBOARD_Z = KEYBOARD << DEVICE | 90 << CODE; GameInput.KEYBOARD_0 = KEYBOARD << DEVICE | 48 << CODE; GameInput.KEYBOARD_1 = KEYBOARD << DEVICE | 49 << CODE; GameInput.KEYBOARD_2 = KEYBOARD << DEVICE | 50 << CODE; GameInput.KEYBOARD_3 = KEYBOARD << DEVICE | 51 << CODE; GameInput.KEYBOARD_4 = KEYBOARD << DEVICE | 52 << CODE; GameInput.KEYBOARD_5 = KEYBOARD << DEVICE | 53 << CODE; GameInput.KEYBOARD_6 = KEYBOARD << DEVICE | 54 << CODE; GameInput.KEYBOARD_7 = KEYBOARD << DEVICE | 55 << CODE; GameInput.KEYBOARD_8 = KEYBOARD << DEVICE | 56 << CODE; GameInput.KEYBOARD_9 = KEYBOARD << DEVICE | 57 << CODE; GameInput.KEYBOARD_UP = KEYBOARD << DEVICE | 38 << CODE; GameInput.KEYBOARD_DOWN = KEYBOARD << DEVICE | 40 << CODE; GameInput.KEYBOARD_LEFT = KEYBOARD << DEVICE | 37 << CODE; GameInput.KEYBOARD_RIGHT = KEYBOARD << DEVICE | 39 << CODE; GameInput.KEYBOARD_SPACE = KEYBOARD << DEVICE | 32 << CODE; GameInput.KEYBOARD_SHIFT = KEYBOARD << DEVICE | 16 << CODE;

Zeigergerätekanäle:

GameInput.POINTER_UP = POINTER << DEVICE | 0 << CODE; GameInput.POINTER_DOWN = POINTER << DEVICE | 1 << CODE; GameInput.POINTER_LEFT = POINTER << DEVICE | 2 << CODE; GameInput.POINTER_RIGHT = POINTER << DEVICE | 3 << CODE; GameInput.POINTER_PRESS = POINTER << DEVICE | 4 << CODE;

Gamepad-Gerätekanäle:

GameInput.GAMEPAD_A = GAMEPAD << DEVICE | 0 << CODE; GameInput.GAMEPAD_B = GAMEPAD << DEVICE | 1 << CODE; GameInput.GAMEPAD_X = GAMEPAD << DEVICE | 2 << CODE; GameInput.GAMEPAD_Y = GAMEPAD << DEVICE | 3 << CODE; GameInput.GAMEPAD_LB = GAMEPAD << DEVICE | 4 << CODE; GameInput.GAMEPAD_RB = GAMEPAD << DEVICE | 5 << CODE; GameInput.GAMEPAD_LT = GAMEPAD << DEVICE | 6 << CODE; GameInput.GAMEPAD_RT = GAMEPAD << DEVICE | 7 << CODE; GameInput.GAMEPAD_START = GAMEPAD << DEVICE | 8 << CODE; GameInput.GAMEPAD_SELECT = GAMEPAD << DEVICE | 9 << CODE; GameInput.GAMEPAD_L = GAMEPAD << DEVICE | 10 << CODE; GameInput.GAMEPAD_R = GAMEPAD << DEVICE | 11 << CODE; GameInput.GAMEPAD_UP = GAMEPAD << DEVICE | 12 << CODE; GameInput.GAMEPAD_DOWN = GAMEPAD << DEVICE | 13 << CODE; GameInput.GAMEPAD_LEFT = GAMEPAD << DEVICE | 14 << CODE; GameInput.GAMEPAD_RIGHT = GAMEPAD << DEVICE | 15 << CODE; GameInput.GAMEPAD_L_UP = GAMEPAD << DEVICE | 16 << CODE; GameInput.GAMEPAD_L_DOWN = GAMEPAD << DEVICE | 17 << CODE; GameInput.GAMEPAD_L_LEFT = GAMEPAD << DEVICE | 18 << CODE; GameInput.GAMEPAD_L_RIGHT = GAMEPAD << DEVICE | 19 << CODE; GameInput.GAMEPAD_R_UP = GAMEPAD << DEVICE | 20 << CODE; GameInput.GAMEPAD_R_DOWN = GAMEPAD << DEVICE | 21 << CODE; GameInput.GAMEPAD_R_LEFT = GAMEPAD << DEVICE | 22 << CODE; GameInput.GAMEPAD_R_RIGHT = GAMEPAD << DEVICE | 23 << CODE;

Um den Code zu finalisieren, müssen wir einfach die Main() Funktion.

// Das Eingabesystem initialisieren. Main();

Und das ist alles Code. Wieder ist alles in den Quelldateien vorhanden.

Weglaufen!

Bevor wir das Tutorial mit einer Schlussfolgerung abschließen, werfen wir einen Blick auf ein weiteres Beispiel GameInput Klasse kann verwendet werden. Diesmal werden wir Bob die Fähigkeit geben, sich zu bewegen und zu springen, da die Horden mutanter Wurstzombies für ihn zu viel werden könnten, als dass er alleine damit umgehen könnte.

// Erstellen Sie die Eingaben. var jump = new GameInput (); var moveLeft = new GameInput (); var moveRight = new GameInput (); // Sagen Sie den Eingängen, worauf Sie reagieren sollen. jump.add (GameInput.KEYBOARD_UP); jump.add (GameInput.KEYBOARD_W); jump.add (GameInput.GAMEPAD_A); moveLeft.add (GameInput.KEYBOARD_LEFT); moveLeft.add (GameInput.KEYBOARD_A); moveLeft.add (GameInput.GAMEPAD_LEFT); moveRight.add (GameInput.KEYBOARD_RIGHT); moveRight.add (GameInput.KEYBOARD_D); moveRight.add (GameInput.GAMEPAD_RIGHT); // Überprüfen Sie bei jedem Spiel-Update die Eingaben. function update () if (jump.value> 0) // Bitten Sie Bob, zu springen.  else // Sag Bob, dass er aufhören soll zu springen.  if (moveLeft.value> 0) // Bitten Sie Bob, sich nach links zu bewegen.  else // Sag Bob, dass er aufhören soll, sich nach links zu bewegen.  if (moveRight.value> 0) // Bitten Sie Bob, sich nach rechts zu bewegen.  else // Bitten Sie Bob, sich nicht mehr nach rechts zu bewegen. 

Schön und einfach. Denken Sie daran, dass die Wert Eigentum von GameInput Instanzen reichen von 0 durch zu 1, Wir könnten also so etwas wie die Bewegungsgeschwindigkeit von Bob mit diesem Wert ändern, wenn einer der aktiven Eingangskanäle analog ist.

if (moveLeft.value> 0) bob.x - = bob.maxDistancePerFrame * moveLeft.value; 

Habe Spaß!

Fazit

Plattformübergreifende Spiele haben eines gemeinsam: Sie müssen alle mit einer Vielzahl von Spiel-Eingabegeräten (Controllern) umgehen, und der Umgang mit diesen Eingabegeräten kann zu einer entmutigenden Aufgabe werden. In diesem Lernprogramm wurde eine Möglichkeit zum Umgang mit mehreren Eingabegeräten mithilfe einer einfachen, einheitlichen API gezeigt.