German (Deutsch) translation by Federicco Ancie (you can also view the original English article)
Stellen Sie sich eine Spielfigur namens "Bob the Butcher" vor, die alleine in einem abgedunkelten Raum steht, während Horden mutierter Wurstzombies durch Türen und zerbrochene Fenster hereinströmen. An diesem Punkt wäre es eine gute Idee für Bob, die Wurstzombies in winzige Fleischstücke zu sprengen, aber wie wird Bob das in einem plattformübergreifenden 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 denen Sie wahrscheinlich viel Zeit verbringen werden, wenn Sie nicht darauf vorbereitet sind. Wenn Sie nicht aufpassen, können Sie massive, spaghettiartige if
-Anweisungen oder switch
-Anweisungen erhalten, um mit all den verschiedenen Eingabegeräten umzugehen.
In diesem Tutorial werden wir die Dinge viel einfacher machen, indem wir eine einzelne Klasse erstellen, die mehrere Eingabegeräte vereint. Jede Instanz der Klasse repräsentiert eine bestimmte Spielaktion oder ein bestimmtes Spielverhalten (z. B. "Schießen", "Ausführen" oder "Springen") und kann angewiesen werden, verschiedene Tasten, Schaltflächen und Zeiger auf mehreren Eingabegeräten anzuhören.
Hinweis: In diesem Lernprogramm verwendeten wir Programmiersprache JavaScript. Die Technik zur Vereinheitlichung mehrerer Eingabegeräte kann jedoch 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 beginnen, die wir in diesem Lernprogramm erstellen, werfen wir einen kurzen Blick darauf, wie die Klasse tatsächlich verwendet werden kann.
// Create an input for the "shoot" action. shoot = new GameInput(); // Tell the input what to react to. shoot.add( GameInput.KEYBOARD_SPACE ); shoot.add( GameInput.GAMEPAD_RT ); // During each game update, check the input. function update() { if( shoot.value > 0 ) { // Tell Bob to shoot the mutant sausage zombies! } else { // Tell Bob to stop shooting. } }
GameInput
ist die Klasse, die wir erstellen werden, und Sie können sehen, wie viel einfacher es die Dinge machen wird. Die Eigenschaft shot.value
ist eine Zahl und ein positiver Wert, wenn die Leertaste auf einer Tastatur oder der rechte Auslöser auf einem Gamepad gedrückt wird. Wenn weder die Leertaste noch der rechte Auslöser gedrückt werden, ist der Wert Null.
Anfangen
Als erstes müssen wir einen Funktionsabschluss für die GameInput
-Klasse erstellen. Der größte Teil des Codes, den wir schreiben, ist nicht Teil der Klasse, aber er muss innerhalb der Klasse zugänglich sein und vor allem anderen verborgen bleiben. Ein Funktionsabschluss ermöglicht es uns, dies in JavaScript zu tun.
(In einer Programmiersprache wie ActionScript oder C# können Sie einfach private Klassenmitglieder verwenden, aber das ist leider kein Luxus, den wir in JavaScript haben.)
(function(){ // code goes here })();
Der Rest des Codes in diesem Tutorial ersetzt den Kommentar "Code geht hierher".
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 KEYBOARD = 1; var POINTER = 2; var GAMEPAD = 3; var DEVICE = 16; var CODE = 8; var __pointer = { currentX : 0, currentY : 0, previousX : 0, previousY : 0, distanceX : 0, distanceY : 0, identifier : 0, moved : false, pressed : false }; var __keyboard = {}; var __inputs = []; var __channels = []; var __mouseDetected = false; var __touchDetected = false;
Die konstanten Werte KEYBOARD
, POINTER
, GAMEPAD
, DEVICE
und CODE
werden zum Definieren von Eingabegerätekanälen wie GameInput.KEYBOARD_SPACE
verwendet. Ihre Verwendung wird später im Lernprogramm erläutert.
Das __pointer
-Objekt enthält Eigenschaften, die für Maus- und Touchscreen-Eingabegeräte relevant sind, und das __keyboard
-Objekt wird verwendet, um die Tastaturtastenzustände zu verfolgen. Die Arrays __inputs
und __channels
werden zum Speichern von GameInput
-Instanzen und allen diesen Instanzen hinzugefügten Eingabekanälen verwendet. Schließlich zeigen __mouseDetected
und __touchDetected
an, ob eine Maus oder ein Touchscreen erkannt wurde.
Hinweis: Den Variablen müssen keine zwei Unterstriche vorangestellt werden. Das ist einfach die Codierungskonvention, die ich für den Code in diesem Tutorial gewählt habe. Es hilft, sie von Variablen zu trennen, die in Funktionen definiert sind.
Die Funktionen
Hier kommt der Großteil des Codes, also möchten Sie vielleicht einen Kaffee oder etwas anderes 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 ihres Erscheinungsbilds definiert.
// Initializes the input system. function main() { // Expose the GameInput constructor. window.GameInput = GameInput; // Add the event listeners. addMouseListeners(); addTouchListeners(); addKeyboardListeners(); // Some UI actions we should prevent in a game. window.addEventListener( "contextmenu", killEvent, true ); window.addEventListener( "selectstart", killEvent, true ); // Start the update loop. window.requestAnimationFrame( update ); }
Die main()
- Funktion wird am Ende des Codes aufgerufen, dh am Ende des zuvor erstellten Funktionsabschlusses. Es macht das, was es verspricht, und bringt alles zum Laufen, damit die GameInput
-Klasse verwendet werden kann.
Eine Sache, auf die ich Sie aufmerksam machen sollte, ist die Verwendung der Funktion requestAnimationFrame()
, die Teil der W3C Animation Timing-Spezifikation ist. Moderne Spiele und Anwendungen verwenden diese Funktion, um ihre Aktualisierungs- oder Rendering-Schleifen auszuführen, da sie in den meisten Webbrowsern für diesen Zweck stark optimiert wurden.
// Updates the input system. function update() { window.requestAnimationFrame( update ); // Update the pointer values first. updatePointer(); var i = __inputs.length; var input = null; var channels = null; while( i -- > 0 ) { input = __inputs[ i ]; channels = __channels[ i ]; if( input.enabled === true ) { updateInput( input, channels ); } else { input.value = 0; } } }
Die Funktion update()
durchläuft die Liste der aktiven GameInput
-Instanzen und aktualisiert die aktivierten. Die folgende updateInput()
-Funktion ist ziemlich lang, daher werde ich den Code hier nicht hinzufügen. Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.
// Updates a GameInput instance. function updateInput( input, channels ) { // note: see the source files }
Die Funktion updateInput()
überprüft die Eingabegerätekanäle, die einer GameInput
-Instanz hinzugefügt wurden, und ermittelt, auf welche value
-Eigenschaft der GameInput
-Instanz festgelegt werden soll. Wie im Beispielcode Shooting the Sausages zu sehen ist, gibt die value
-Eigenschaft an, ob ein Eingabegerätekanal ausgelöst wird, und ermöglicht es einem Spiel, entsprechend zu reagieren, indem Bob möglicherweise angewiesen wird, die mutierten Wurstzombies zu schießen.
// Updates the value of a GameInput instance. function updateValue( input, value, threshold ) { if( threshold !== undefined ) { if( value < threshold ) { value = 0; } } // The highest value has priority. if( input.value < value ) { input.value = value; } }
Die Funktion updateValue()
bestimmt, ob die value
-Eigenschaft einer GameInput
-Instanz aktualisiert werden soll. Der threshold
wird hauptsächlich verwendet, um zu verhindern, dass analoge Geräteeingangskanäle wie Gamepad-Tasten und -Sticks, die sich nicht ordnungsgemäß zurücksetzen, ständig eine GameInput
-Instanz auslösen. Dies passiert ziemlich oft bei fehlerhaften oder schmuddeligen Gamepads.
Wie die Funktion updateInput()
ist die folgende Funktion updatePointer()
ziemlich lang, daher werde ich den Code hier nicht hinzufügen. Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.
// Updates the pointer values. function updatePointer() { // note: see the source files }
Die Funktion updatePointer()
aktualisiert die Eigenschaften im __pointer
-Objekt. Kurz gesagt, die Funktion klemmt die Position des Zeigers, um sicherzustellen, dass das Fensterfenster des Webbrowsers nicht verlassen wird, und berechnet die Entfernung, um die sich der Zeiger seit der letzten Aktualisierung bewegt hat.
// Called when a mouse input device is detected. function mouseDetected() { if( __mouseDetected === false ) { __mouseDetected = true; // Ignore touch events if a mouse is being used. removeTouchListeners(); } } // Called when a touch-screen input device is detected. function touchDetected() { if( __touchDetected === false ) { __touchDetected = true; // Ignore mouse events if a touch-screen is being used. removeMouseListeners(); } }
Die Funktionen mouseDetected()
und touchDetected()
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 vor einer Maus ein Touchscreen erkannt wird, wird die Maus ignoriert.
// Called when a pointer-like input device is pressed. function pointerPressed( x, y, identifier ) { __pointer.identifier = identifier; __pointer.pressed = true; pointerMoved( x, y ); } // Called when a pointer-like input device is released. function pointerReleased() { __pointer.identifier = 0; __pointer.pressed = false; } // Called when a pointer-like input device is moved. function pointerMoved( 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; } }
Die Funktionen pointerPressed()
, pointerReleased()
und pointerMoved()
verarbeiten Eingaben von einer Maus oder einem Touchscreen. Alle drei Funktionen aktualisieren einfach die Eigenschaften im __pointer
-Objekt.
Nach diesen drei Funktionen haben wir eine Handvoll Standardfunktionen zur Behandlung von JavaScript-Ereignissen. Die Funktionen sind selbsterklärend, daher werde ich den Code hier nicht hinzufügen. Sie können den Code vollständig anzeigen, indem Sie die Quelldateien herunterladen.
// Adds an input device channel to a GameInput instance. function inputAdd( input, channel ) { var i = __inputs.indexOf( input ); if( i === -1 ) { __inputs.push( input ); __channels.push( [ channel ] ); return; } var ca = __channels[ i ]; var ci = ca.indexOf( channel ); if( ci === -1 ) { ca.push( channel ); } } // Removes an input device channel to a GameInput instance. function inputRemove( input, channel ) { var i = __inputs.indexOf( input ); if( i === -1 ) { return; } var ca = __channels[ i ]; var ci = ca.indexOf( channel ); if( ci !== -1 ) { ca.splice( ci, 1 ); if( ca.length === 0 ) { __inputs.splice( i, 1 ); __channels.splice( i, 1 ); } } } // Resets a GameInput instance. function inputReset( input ) { var i = __inputs.indexOf( input ); if( i !== -1 ) { __inputs.splice( i, 1 ); __channels.splice( i, 1 ); } input.value = 0; input.enabled = true; }
Die Funktionen inputAdd()
, inputRemove()
und inputReset()
werden von einer GameInput
-Instanz aufgerufen (siehe unten). Die Funktionen ändern die Arrays __inputs
und __channels
je nachdem, was zu tun ist.
Eine GameInput
-Instanz wird als aktiv betrachtet und dem __inputs
-Array hinzugefügt, wenn der GameInput
-Instanz ein Eingabegerätekanal hinzugefügt wurde. Wenn bei einer aktiven GameInput
-Instanz alle Kanäle des Eingabegeräts entfernt wurden, wird die GameInput
-Instanz als inaktiv betrachtet und aus dem Array __inputs
entfernt.
Jetzt kommen wir zur GameInput
-Klasse.
// GameInput constructor. function GameInput() {} GameInput.prototype = { value : 0, enabled : true, // Adds an input device channel. add : function( channel ) { inputAdd( this, channel ); }, // Removes an input device channel. remove : function( channel ) { inputRemove( this, channel ); }, // Removes all input device channels. reset : function() { inputReset( this ); } };
Ja, das ist alles, was es gibt - es ist eine superleichte Klasse, die im Wesentlichen als Schnittstelle zum Hauptcode fungiert. Die value
-Eigenschaft ist eine Zahl, die von 0
(Null) bis 1
(Eins) reicht. Wenn der Wert 0
ist, bedeutet dies, dass die GameInput
-Instanz nichts von den hinzugefügten Eingabegerätekanälen empfängt.
Die GameInput
-Klasse hat einige statische Eigenschaften, daher werden wir diese jetzt hinzufügen.
// The X position of the pointer within the window viewport. GameInput.pointerX = 0; // The Y position of the pointer within the window viewport. GameInput.pointerY = 0; // The distance the pointer has to move, in pixels per frame, to // cause the value of a GameInput instance to equal 1.0. GameInput.pointerSpeed = 10;
Tastaturgerätekanäle:
GameInput.KEYBOARD_A = KEYBOARD << 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 fertigzustellen, müssen wir einfach die Funktion main()
aufrufen.
// Initialize the input system. main();
Und das ist der ganze Code. Auch hier ist alles in den Quelldateien verfügbar.
Renn weg!
Bevor wir das Tutorial mit einer Schlussfolgerung abschließen, werfen wir einen Blick auf ein weiteres Beispiel, wie die GameInput
-Klasse verwendet werden kann. Dieses Mal geben wir Bob die Möglichkeit, sich zu bewegen und zu springen, da die Horden mutierter Wurstzombies möglicherweise zu viel werden, als dass er sie alleine handhaben könnte.
// Create the inputs. var jump = new GameInput(); var moveLeft = new GameInput(); var moveRight = new GameInput(); // Tell the inputs what to react to. 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 ); // During each game update, check the inputs. function update() { if( jump.value > 0 ) { // Tell Bob to jump. } else { // Tell Bob to stop jumping. } if( moveLeft.value > 0 ) { // Tell Bob to move/run left. } else { // Tell Bob to stop moving left. } if( moveRight.value > 0 ) { // Tell Bob to move/run right. } else { // Tell Bob to stop moving right. } }
Schön und leicht. Beachten Sie, dass die value
-Eigenschaft von GameInput
-Instanzen zwischen 0
und 1
liegt. Sie können also zum Beispiel die Bewegungsgeschwindigkeit von Bob mit dieses Werts ändern, wenn einer der aktiven Eingabegerätekanäle analog ist.
if( moveLeft.value > 0 ) { bob.x -= bob.maxDistancePerFrame * moveLeft.value; }
Haben Sie viel Spaß!
Abschluss
Plattformübergreifende Spiele haben alle eines gemeinsam: Sie müssen sich alle mit einer Vielzahl von Spieleingabegeräten (Controllern) befassen, und der Umgang mit diesen Eingabegeräten kann zu einer entmutigenden Aufgabe werden. In diesem Lernprogramm wurde eine Möglichkeit aufgezeigt, mit mehreren Eingabegeräten mithilfe einer einfachen, einheitlichen API umzugehen.
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post