Entrée de jeu simplifiée

Imaginez un personnage du jeu nommé "Bob the Butcher" debout seul dans une pièce sombre tandis que des hordes de zombies mutants de saucisses commencent à affluer à travers les portes et les fenêtres cassées. À ce stade, ce serait une bonne idée pour Bob de commencer à transformer les zombies en saucisses en petits morceaux de viande, mais comment Bob fera-t-il cela dans un jeu multi-plateformes? Le joueur devra-t-il appuyer sur une ou plusieurs touches du clavier, cliquer sur la souris, appuyer sur l'écran ou appuyer sur un bouton de la manette de jeu??

Lors de la programmation d'un jeu multiplate-forme, c'est le genre de chose avec laquelle vous passerez probablement beaucoup de temps à vous battre si vous n'y êtes pas préparé. Si vous ne faites pas attention, vous pourriez vous retrouver avec un spaghetti massif si déclarations ou commutateur déclarations pour traiter avec tous les différents périphériques d'entrée.

Dans ce tutoriel, nous allons rendre les choses beaucoup plus simples en créant une seule classe qui unifiera plusieurs périphériques d’entrée. Chaque instance de la classe représente une action ou un comportement de jeu spécifique (tel que "tirer", "courir" ou "sauter") et peut être invitée à écouter différentes touches, boutons et pointeurs sur plusieurs périphériques d'entrée..

Remarque: Le langage de programmation utilisé dans ce tutoriel est JavaScript, mais la technique utilisée pour unifier plusieurs périphériques d'entrée peut facilement être transférée vers tout autre langage de programmation multiplate-forme fournissant des API pour les périphériques d'entrée..

Tirer sur les saucisses

Avant de commencer à écrire le code de la classe que nous allons créer dans ce tutoriel, examinons rapidement comment cette classe pourrait être utilisée..

// Crée une entrée pour l'action "tirer". shoot = new GameInput (); // Dire à l'entrée à quoi réagir. shoot.add (GameInput.KEYBOARD_SPACE); shoot.add (GameInput.GAMEPAD_RT); // Lors de chaque mise à jour du jeu, vérifiez l'entrée. function update () if (shoot.value> 0) // Dites à Bob de tirer sur les zombies de saucisse mutants!  else // Dites à Bob d'arrêter de tirer. 

GameInput est la classe que nous allons créer, et vous pouvez voir à quel point cela simplifiera les choses. le shoot.value propriété est un nombre et sera une valeur positive si le barre d'espace sur un clavier est appuyé ou le déclencheur droit sur un gamepad est pressé. Si vous n'appuyez sur ni la barre d'espace ni le déclencheur droit, la valeur sera zéro.

Commencer

La première chose à faire est de créer une fermeture de fonction pour le GameInput classe. La plupart du code que nous allons écrire ne fait pas réellement partie de la classe, mais il doit être accessible depuis la classe tout en restant caché de tout le reste. Une fermeture de fonction nous permet de le faire en JavaScript.

(Dans un langage de programmation tel qu'ActionScript ou C #, vous pouvez simplement utiliser des membres de classe privés, mais ce n'est pas un luxe que nous avons en JavaScript, malheureusement.)

(function () // le code va ici) ();

Le reste du code de ce didacticiel remplacera le commentaire "le code va ici".

Les variables

Le code nécessite seulement une poignée de variables à définir en dehors des fonctions, et ces variables sont les suivantes.

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, identificateur: 0, déplacé: false, enfoncé: false; var __keyboard = ; var __inputs = []; var __channels = []; var __mouseDetected = false; var __touchDetected = false;

Le constant-like CLAVIER, AIGUILLE, GAMEPAD, DISPOSITIF et CODE les valeurs sont utilisées pour définir canaux de périphérique d'entrée, tel que GameInput.KEYBOARD_SPACE, et leur utilisation deviendra claire plus tard dans le tutoriel.

le __aiguille L’objet contient des propriétés relatives aux périphériques de saisie de la souris et à l’écran tactile, et __clavier objet est utilisé pour suivre les états des touches du clavier. le __contributions et __channels les tableaux sont utilisés pour stocker GameInput instances et tous les canaux de périphérique d'entrée ajoutés à ces instances. Finalement, le __mouseDetected et __touchDetected indiquer si une souris ou un écran tactile a été détecté.

Remarque: Les variables n'ont pas besoin d'être préfixées par deux traits de soulignement; c'est simplement la convention de codage que j'ai choisi d'utiliser pour le code de ce tutoriel. Cela aide à les séparer des variables définies dans les fonctions.

Les fonctions

Voici le gros du code, vous voudrez peut-être prendre un café ou autre chose avant de commencer à lire cette partie!

Ces fonctions sont définies après les variables de la section précédente de ce didacticiel, et par ordre d'apparition..

// Initialise le système d'entrée. function main () // Exposer le constructeur GameInput. window.GameInput = GameInput; // Ajoute les écouteurs d'événement. addMouseListeners (); addTouchListeners (); addKeyboardListeners (); // Certaines actions d'interface utilisateur devraient être évitées lors d'une partie. window.addEventListener ("contextmenu", killEvent, true); window.addEventListener ("selectstart", killEvent, true); // Démarrer la boucle de mise à jour. window.requestAnimationFrame (mise à jour); 

le principale() la fonction est appelée à la fin du code, c’est-à-dire à la fin du fermeture de la fonction, nous avons créé plus tôt. Il fait ce qu’il dit sur l’étain et fait tout courir pour que le GameInput la classe peut être utilisée.

Une chose que je devrais porter à votre attention est l’utilisation du requestAnimationFrame () fonction, qui fait partie de la spécification W3C Animation Timing. Les jeux et applications modernes utilisent cette fonction pour exécuter leurs boucles de mise à jour ou de rendu, car elle a été optimisée à cette fin dans la plupart des navigateurs Web..

// Met à jour le système d'entrée. function update () window.requestAnimationFrame (update); // Met à jour les valeurs du pointeur en premier. updatePointer (); var i = __inputs.length; var input = null; var channels = null; while (i -> 0) entrée = __inputs [i]; canaux = __channels [i]; if (input.enabled === true) updateInput (entrée, canaux);  else input.value = 0; 

le mettre à jour() la fonction défile dans la liste des actifs GameInput instances et met à jour celles qui sont activées. Le suivant updateInput () la fonction est assez longue, donc je n’ajouterai pas le code ici; vous pouvez voir le code complet en téléchargeant les fichiers source.

// Met à jour une instance GameInput. function updateInput (input, channels) // remarque: voir les fichiers source

le updateInput () fonction regarde les canaux de périphérique d'entrée qui ont été ajoutés à un GameInput par exemple et travaille sur ce que le valeur propriété du GameInput instance devrait être réglé sur. Comme on le voit dans le Tirer sur les saucisses exemple de code, le valeur Cette propriété indique si un canal de périphérique d'entrée est activé et permet ainsi à un jeu de réagir en conséquence, par exemple en indiquant à Bob de tirer sur les zombies de saucisse mutants..

// Met à jour la valeur d'une instance GameInput. function updateValue (entrée, valeur, seuil) if (seuil! == non défini) if (valeur < threshold )  value = 0;   // The highest value has priority. if( input.value < value )  input.value = value;  

le updateValue () fonction détermine si le valeur propriété d'un GameInput instance devrait être mis à jour. le seuil est principalement utilisé pour empêcher les canaux d'entrée de périphérique analogique, tels que les boutons et les manettes de la manette de jeu, de ne pas se réinitialiser correctement GameInput exemple. Cela arrive assez souvent avec des manettes de jeu défectueuses ou sales.

Comme le updateInput () fonction, ce qui suit updatePointer () la fonction est assez longue donc je n’ajouterai pas le code ici. Vous pouvez voir le code complet en téléchargeant les fichiers sources.

// Met à jour les valeurs du pointeur. function updatePointer () // note: voir les fichiers source

le updatePointer () fonction met à jour les propriétés dans le __aiguille objet. En un mot, la fonction bloque la position du pointeur pour s'assurer qu'il ne quitte pas la fenêtre de visualisation du navigateur Web et calcule la distance parcourue par le pointeur depuis la dernière mise à jour..

// Appelé lorsqu'un périphérique de souris est détecté. fonction mouseDetected () if (__mouseDetected === false) __mouseDetected = true; // Ignorer les événements tactiles si une souris est utilisée. removeTouchListeners ();  // Appelé lorsqu'un périphérique de saisie à écran tactile est détecté. fonction touchDetected () if (__touchDetected === false) __touchDetected = true; // Ignorer les événements de la souris si un écran tactile est utilisé. removeMouseListeners (); 

le mouseDetected () et touchDetected () les fonctions indiquent au code d’ignorer un périphérique d’entrée ou l’autre. Si une souris est détectée avant un écran tactile, celui-ci sera ignoré. Si un écran tactile est détecté avant une souris, celle-ci sera ignorée.

// Appelé lorsqu'un périphérique de saisie de type pointeur est enfoncé. fonction pointerPressed (x, y, identifiant) __pointer.identifier = identifiant; __pointer.pressed = true; pointerMoved (x, y);  // Appelé lorsqu'un périphérique de saisie de type pointeur est libéré. fonction pointerReleased () __pointer.identifier = 0; __pointer.pressed = false;  // Appelé lorsqu'un périphérique de saisie semblable à un pointeur est déplacé. fonction 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; 

le pointerPressed (), pointeurReleased () et pointerMoved () Les fonctions gèrent la saisie depuis une souris ou un écran tactile. Les trois fonctions mettent simplement à jour les propriétés dans __aiguille objet.

Après ces trois fonctions, nous avons une poignée de fonctions standard de gestion d’événements JavaScript. Les fonctions sont explicites, je ne vais donc pas ajouter le code ici; vous pouvez voir le code complet en téléchargeant les fichiers source.

// Ajoute un canal de périphérique d'entrée à une instance GameInput. fonction inputAdd (entrée, canal) var i = __inputs.indexOf (entrée); if (i === -1) __inputs.push (entrée); __channels.push ([channel]); revenir;  var ca = __channels [i]; var ci = ca.indexOf (canal); if (ci === -1) ca.push (canal);  // Supprime un canal de périphérique d'entrée dans une instance GameInput. fonction inputRemove (entrée, canal) var i = __inputs.indexOf (entrée); if (i === -1) return;  var ca = __channels [i]; var ci = ca.indexOf (canal); if (ci! == -1) ca.splice (ci, 1); if (ca.length === 0) __inputs.splice (i, 1); __channels.splice (i, 1);  // Réinitialise une instance de GameInput. fonction inputReset (input) var i = __inputs.indexOf (input); if (i! == -1) __inputs.splice (i, 1); __channels.splice (i, 1);  input.value = 0; input.enabled = true; 

le inputAdd (), inputRemove () et inputReset () les fonctions sont appelées à partir d'un GameInput exemple (voir ci-dessous). Les fonctions modifient le __contributions et __channels des tableaux en fonction de ce qui doit être fait.

UNE GameInput instance est considérée comme active et ajoutée à la __contributions tableau, lorsqu'un canal de périphérique d'entrée a été ajouté à la GameInput exemple. Si un actif GameInput instance a tous ses canaux de périphérique d'entrée supprimés, le GameInput instance considérée comme inactive et retirée du __contributions tableau.

Maintenant nous arrivons au GameInput classe.

// constructeur GameInput. function GameInput ()  GameInput.prototype = valeur: 0, activé: true, // Ajoute un canal de périphérique d'entrée. add: function (channel) inputAdd (this, channel); , // Supprime un canal de périphérique d'entrée. remove: function (channel) inputRemove (this, channel); , // Supprime tous les canaux de périphérique d'entrée. reset: function () inputReset (this); ;

Oui, c'est tout ce qu'il y a - c'est une classe super légère qui agit essentiellement comme une interface avec le code principal. le valeur la propriété est un nombre qui va de 0 (zéro) jusqu'à 1 (un). Si la valeur est 0, cela signifie que le GameInput l'instance ne reçoit rien des canaux de périphérique d'entrée qui lui ont été ajoutés.

le GameInput la classe a quelques propriétés statiques, nous allons donc ajouter celles maintenant.

// La position X du pointeur dans la fenêtre de la fenêtre. GameInput.pointerX = 0; // La position Y du pointeur dans la fenêtre de la fenêtre. GameInput.pointerY = 0; // La distance que le pointeur doit déplacer, en pixels par image, // pour que la valeur d'une occurrence de GameInput soit égale à 1,0. GameInput.pointerSpeed ​​= 10;

Canaux de périphérique de clavier:

GameInput.KEYBOARD_A = CLAVIER << 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;

Canaux de périphérique de pointeur:

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;

Canaux de l'appareil 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;

Pour finaliser le code, il suffit d’appeler le principale() une fonction.

// Initialise le système de saisie. principale();

Et c'est tout le code. Encore une fois, tout est disponible dans les fichiers source.

Fuyez!

Avant de terminer le didacticiel par une conclusion, examinons un autre exemple de la façon dont le GameInput la classe peut être utilisée. Cette fois-ci, nous donnerons à Bob la capacité de bouger et de sauter car les hordes de zombies de saucisses mutants pourraient devenir trop difficiles à gérer pour lui..

// Crée les entrées. var jump = new GameInput (); var moveLeft = new GameInput (); var moveRight = new GameInput (); // Indique aux entrées à quoi réagir. 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); // Lors de chaque mise à jour du jeu, vérifiez les entrées. function update () if (jump.value> 0) // Dites à Bob de sauter.  else // Dites à Bob d'arrêter de sauter.  if (moveLeft.value> 0) // Dire à Bob de se déplacer / de courir à gauche.  else // Dites à Bob d'arrêter de bouger à gauche.  if (moveRight.value> 0) // Dire à Bob de se déplacer / de courir à droite.  else // Dites à Bob d'arrêter de bouger à droite. 

Nice et facile. Gardez à l'esprit que le valeur propriété de GameInput les instances vont de 0 à travers 1, afin que nous puissions faire quelque chose comme changer la vitesse de déplacement de Bob en utilisant cette valeur si l'un des canaux du périphérique d'entrée actif est analogique.

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

S'amuser!

Conclusion

Les jeux multiplateformes ont tous un point commun: ils doivent tous gérer une multitude de dispositifs de saisie (contrôleurs), et le traitement de ces dispositifs de saisie peut devenir une tâche ardue. Ce tutoriel a montré une façon de gérer plusieurs périphériques d’entrée en utilisant une API simple et unifiée..