Ingresso di gioco semplificato

Immagina un personaggio del gioco chiamato "Bob the Butcher" in piedi da solo in una stanza buia, mentre orde di mutanti zombi salsiccia iniziano a riversarsi attraverso porte e finestre rotte. A questo punto, sarebbe una buona idea per Bob iniziare a far saltare gli zombi salsiccia in piccoli pezzi di carne, ma come lo farà Bob in un gioco multipiattaforma? Il giocatore deve premere uno o più tasti su una tastiera, fare clic con il mouse, toccare lo schermo o premere un pulsante su un gamepad?

Quando si programma un gioco multipiattaforma, questo è il tipo di cosa con cui si spende molto tempo a combattere se non si è pronti per questo. Se non stai attento, potresti ritrovarti con degli enormi spaghetti Se dichiarazioni o interruttore dichiarazioni per gestire tutti i diversi dispositivi di input.

In questo tutorial, renderemo le cose molto più semplici creando una singola classe che unificherà più dispositivi di input. Ogni istanza della classe rappresenterà un'azione o un comportamento specifico di gioco (come "sparare", "eseguire" o "saltare") e può essere detto di ascoltare vari tasti, pulsanti e puntatori su più dispositivi di input.

Nota: Il linguaggio di programmazione utilizzato in questo tutorial è JavaScript, ma la tecnica utilizzata per unificare più dispositivi di input può essere facilmente trasferita a qualsiasi altro linguaggio di programmazione multipiattaforma che fornisce API per i dispositivi di input.

Sparare alle salsicce

Prima di iniziare a scrivere il codice per la classe che creeremo in questo tutorial, diamo una rapida occhiata a come la classe potrebbe effettivamente essere utilizzata.

// Crea un input per l'azione "sparare". shoot = new GameInput (); // Indica all'input a cosa reagire. shoot.add (GameInput.KEYBOARD_SPACE); shoot.add (GameInput.GAMEPAD_RT); // Durante ogni aggiornamento del gioco, controlla l'input. function update () if (shoot.value> 0) // Dì a Bob di sparare agli zombi mutanti della salsiccia!  else // Spiega a Bob di smettere di sparare. 

GameInput è la classe che creeremo, e puoi vedere quanto più semplice farà le cose. Il shoot.value la proprietà è un numero e sarà un valore positivo se il barra spaziatrice su una tastiera viene premuto o il grilletto destro su un gamepad viene premuto. Se non viene premuto né la barra spaziatrice né il grilletto destro, il valore sarà zero.

Iniziare

La prima cosa che dobbiamo fare è creare una chiusura di funzione per il GameInput classe. La maggior parte del codice che scriveremo non fa parte della classe, ma deve essere accessibile all'interno della classe rimanendo nascosta a tutto il resto. Una chiusura di funzione ci consente di farlo in JavaScript.

(In un linguaggio di programmazione come ActionScript o C #, potresti semplicemente usare membri di classi private, ma sfortunatamente non è un lusso che abbiamo in JavaScript.)

(function () // code goes here) ();

Il resto del codice in questo tutorial sostituirà il commento "il codice va qui".

Le variabili

Il codice richiede solo una manciata di variabili da definire al di fuori delle funzioni e tali variabili sono le seguenti.

var KEYBOARD = 1; var POINTER = 2; var GAMEPAD = 3; var DEVICE = 16; var CODICE = 8; var __pointer = currentX: 0, currentY: 0, previousX: 0, previousY: 0, distanceX: 0, distanceY: 0, identificatore: 0, spostato: false, premuto: false; var __keyboard = ; var __inputs = []; var __channels = []; var __mouseDetected = false; var __touchDetected = false;

Il costante TASTIERA, POINTER, GAMEPAD, DISPOSITIVO e CODICE i valori sono usati per definire input dei canali del dispositivo, ad esempio GameInput.KEYBOARD_SPACE, e il loro uso diventerà chiaro più avanti nel tutorial.

Il __pointer oggetto contiene proprietà che sono rilevanti per il mouse e dispositivi di input touch-screen, e il __tastiera oggetto è usato per tenere traccia degli stati chiave della tastiera. Il __inputs e __channels gli array vengono utilizzati per memorizzare GameInput istanze e tutti i canali di dispositivi di input aggiunti a tali istanze. Finalmente, il __mouseDetected e __touchDetected indicare se è stato rilevato un mouse o un touch screen.

Nota: Le variabili non devono essere precedute da due trattini bassi; questa è semplicemente la convenzione di codifica che ho scelto di usare per il codice in questo tutorial. Aiuta a separarli dalle variabili definite nelle funzioni.

Le funzioni

Ecco la maggior parte del codice, quindi potresti prendere un caffè o qualcosa prima di iniziare a leggere questa parte!

Queste funzioni sono definite dopo le variabili nella sezione precedente di questo tutorial e sono definite in ordine di apparizione.

// Inizializza il sistema di input. function main () // Esporre il costruttore GameInput. window.GameInput = GameInput; // Aggiungi i listener di eventi. addMouseListeners (); addTouchListeners (); addKeyboardListeners (); // Alcune azioni dell'interfaccia utente che dovremmo evitare in un gioco. window.addEventListener ("contextmenu", killEvent, true); window.addEventListener ("selectstart", killEvent, true); // Avvia il ciclo di aggiornamento. window.requestAnimationFrame (aggiornamento); 

Il principale() la funzione è chiamata alla fine del codice, cioè alla fine del chiusura della funzione che abbiamo creato in precedenza. Fa quello che dice sulla lattina e fa funzionare tutto così GameInput la classe può essere usata.

Una cosa che dovrei portare alla tua attenzione è l'uso del requestAnimationFrame () funzione, che fa parte delle specifiche del Timing di animazione del W3C. I giochi e le applicazioni moderni utilizzano questa funzione per eseguire i loro cicli di aggiornamento o rendering perché sono stati altamente ottimizzati per questo scopo nella maggior parte dei browser web.

// Aggiorna il sistema di input. function update () window.requestAnimationFrame (update); // Aggiorna prima i valori del puntatore. 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; 

Il aggiornare() la funzione scorre attraverso la lista di attivi GameInput istanze e aggiorna quelli che sono abilitati. Il seguente updateInput () la funzione è abbastanza lunga, quindi non aggiungerò il codice qui; puoi vedere il codice per intero scaricando i file sorgente.

// Aggiorna un'istanza di GameInput. function updateInput (input, channels) // note: guarda i file sorgente

Il updateInput () la funzione controlla i canali del dispositivo di input che sono stati aggiunti a GameInput istanza e risolve il problema valore proprietà del GameInput l'istanza dovrebbe essere impostata su Come visto in Sparare alle salsicce codice di esempio, il valore la proprietà indica se un canale del dispositivo di input viene attivato e ciò consente a un gioco di reagire di conseguenza, forse dicendo a Bob di sparare agli zombi mutanti della salsiccia.

// Aggiorna il valore di un'istanza di GameInput. function updateValue (input, value, threshold) if (threshold! == undefined) if (valore < threshold )  value = 0;   // The highest value has priority. if( input.value < value )  input.value = value;  

Il UpdateValue () la funzione determina se il valore proprietà di un GameInput l'istanza dovrebbe essere aggiornata. Il soglia viene utilizzato principalmente per impedire che i canali di input del dispositivo analogico, come i pulsanti del gamepad e le levette, non si reimpostino correttamente da un avvio costante a GameInput esempio. Questo accade abbastanza spesso con gamepad difettosi o sporchi.

Come il updateInput () funzione, il seguente updatePointer () la funzione è piuttosto lunga, quindi non aggiungerò il codice qui. Puoi vedere il codice per intero scaricando i file sorgente.

// Aggiorna i valori del puntatore. function updatePointer () // note: guarda i file sorgente

Il updatePointer () la funzione aggiorna le proprietà nel __pointer oggetto. In poche parole, la funzione blocca la posizione del puntatore per assicurarsi che non lasci il viewport della finestra del browser Web e calcola la distanza del puntatore dal suo ultimo aggiornamento.

// Chiamato quando viene rilevato un dispositivo di input del mouse. function mouseDetected () if (__mouseDetected === false) __mouseDetected = true; // Ignora gli eventi di tocco se viene utilizzato un mouse. removeTouchListeners ();  // Chiamato quando viene rilevato un dispositivo di input touch-screen. function touchDetected () if (__touchDetected === false) __touchDetected = true; // Ignora gli eventi del mouse se viene utilizzato un touch-screen. removeMouseListeners (); 

Il mouseDetected () e touchDetected () le funzioni dicono al codice di ignorare un dispositivo di input o l'altro. Se viene rilevato un mouse prima di un touch screen, il touch screen verrà ignorato. Se viene rilevato un touch screen prima di un mouse, il mouse verrà ignorato.

// Chiamato quando viene premuto un dispositivo di input a puntatore. function pointerPressed (x, y, identificatore) __pointer.identifier = identificatore; __pointer.pressed = true; pointerMoved (x, y);  // Chiamato quando viene rilasciato un dispositivo di input simile a un puntatore. function pointerReleased () __pointer.identifier = 0; __pointer.pressed = false;  // Chiamato quando viene spostato un dispositivo di input simile a un puntatore. 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; 

Il pointerPressed (), pointerReleased () e pointerMoved () le funzioni gestiscono l'input da un mouse o da un touch screen. Tutte e tre le funzioni aggiornano semplicemente le proprietà nel __pointer oggetto.

Dopo queste tre funzioni, abbiamo una manciata di funzioni di gestione degli eventi JavaScript standard. Le funzioni sono autoesplicative quindi non aggiungerò il codice qui; puoi vedere il codice per intero scaricando i file sorgente.

// Aggiunge un canale di dispositivo di input a un'istanza di GameInput. function inputAdd (input, channel) var i = __inputs.indexOf (input); if (i === -1) __inputs.push (input); __channels.push ([canale]); ritorno;  var ca = __channels [i]; var ci = ca.indexOf (canale); if (ci === -1) ca.push (canale);  // Rimuove un canale del dispositivo di input in un'istanza di GameInput. function inputRemove (input, channel) var i = __inputs.indexOf (input); if (i === -1) return;  var ca = __channels [i]; var ci = ca.indexOf (canale); if (ci! == -1) ca.splice (ci, 1); if (ca.length === 0) __inputs.splice (i, 1); __channels.splice (i, 1);  // Reimposta un'istanza GameInput. 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; 

Il inputAdd (), inputRemove () e inputReset () le funzioni sono chiamate da a GameInput istanza (vedi sotto). Le funzioni modificano il __inputs e __channels array in base a ciò che deve essere fatto.

UN GameInput l'istanza è considerata attiva e aggiunta al __inputs array, quando un canale di dispositivo di input è stato aggiunto al GameInput esempio. Se attivo GameInput l'istanza ha tutti i canali del dispositivo di input rimossi, il GameInput istanza considerata inattiva e rimossa dal __inputs schieramento.

Ora arriviamo al GameInput classe.

// Costruttore GameInput. function GameInput ()  GameInput.prototype = valore: 0, abilitato: true, // Aggiunge un canale di dispositivo di input. aggiungi: funzione (canale) inputAdd (questo, canale); , // Rimuove un canale del dispositivo di input. remove: function (channel) inputRemove (this, channel); , // Rimuove tutti i canali del dispositivo di input. reset: function () inputReset (this); ;

Sì, questo è tutto quello che c'è: è una classe super leggera che agisce essenzialmente come interfaccia per il codice principale. Il valore la proprietà è un numero che varia da 0 (zero) fino a 1 (uno). Se il valore è 0, significa che il GameInput l'istanza non riceve nulla da alcun canale di dispositivo di input che è stato aggiunto ad esso.

Il GameInput la classe ha alcune proprietà statiche, quindi le aggiungeremo ora.

// La posizione X del puntatore all'interno della finestra di visualizzazione. GameInput.pointerX = 0; // La posizione Y del puntatore all'interno della finestra di visualizzazione. GameInput.pointerY = 0; // La distanza che il puntatore deve spostare, in pixel per fotogramma, per // causa che il valore di un'istanza di GameInput sia uguale a 1.0. GameInput.pointerSpeed ​​= 10;

Canali del dispositivo della tastiera:

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;

Canali del dispositivo puntatore:

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;

Canali del dispositivo Gamepad:

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;

Per finalizzare il codice, abbiamo semplicemente bisogno di chiamare il principale() funzione.

// Inizializza il sistema di input. principale();

E questo è tutto il codice. Di nuovo, è tutto disponibile nei file sorgente.

Scappa!

Prima di concludere il tutorial con una conclusione, diamo un'occhiata a un altro esempio di come GameInput la classe può essere usata. Questa volta, daremo a Bob l'abilità di muoversi e saltare perché le orde di zombie mutanti potrebbero diventare troppo per lui da gestire da solo.

// Crea gli input. var jump = new GameInput (); var moveLeft = new GameInput (); var moveRight = new GameInput (); // Dì agli input a cosa reagire. 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); // Durante ogni aggiornamento del gioco, controlla gli input. function update () if (jump.value> 0) // Spiega a Bob di saltare.  else // Dì a Bob di smettere di saltare.  if (moveLeft.value> 0) // Spiega a Bob di spostarsi / correre a sinistra.  else // Dì a Bob di smettere di spostarsi a sinistra.  if (moveRight.value> 0) // Spiega a Bob di spostarsi / correre a destra.  else // Dì a Bob di smettere di muoversi a destra. 

Bello e facile. Tieni presente che il valore proprietà di GameInput le istanze variano da 0 attraverso 1, quindi potremmo fare qualcosa come cambiare la velocità di movimento di Bob usando quel valore se uno dei canali del dispositivo di input attivo è analogico.

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

Divertiti!

Conclusione

I giochi multipiattaforma hanno tutti una cosa in comune: tutti devono gestire una moltitudine di dispositivi di input di gioco (controller) e occuparsi di questi dispositivi di input può diventare un compito scoraggiante. Questo tutorial ha dimostrato un modo per gestire più dispositivi di input con l'uso di un'API semplice e unificata.