Il presente articolo è stato tradotto automaticamente.

Sviluppo del gioco

Motori di gioco 2D per il Web

Michael Oneppo

Scarica il codice di esempio

Immaginare c'è uno sviluppatore che vuole scrivere un gioco. Questo sviluppatore si guarda intorno per vedere quali strumenti sono disponibili ed è deluso dalle opzioni. Così, questo sviluppatore decide allora di creare un motore personalizzato che soddisferà tutte le esigenze per il concetto di gioco corrente. Anche esso soddisferà tutte le esigenze di sviluppo futuro del gioco che chiunque avrà mai possibilmente. E, quindi, lo sviluppatore scrive mai in realtà un gioco.

È una tragedia che accade frequentemente. Fortunatamente, questa storia ha generato decine di motori di sviluppo del gioco per tutte le piattaforme possibili. Queste comprendono una vasta gamma di metodologie, strumenti e modelli di licenza. Ad esempio, JavaScripting.com attualmente liste 14 dedicato aprire librerie gioco source. Che ancora non comprende motori di fisica, librerie audio e sistemi di input specializzati per i giochi. Con quel tipo di selezione, c'è destinata a essere un motore di gioco che funzionerà bene per qualsiasi progetto di gioco che avete in mente.

Questo articolo guiderà attraverso tre motori 2D opensource popolare di gioco per il Web: Furbo, Pixi e Phaser. Per confrontare e contrapporre queste librerie, io ho portato il gioco Ping dal mio primo articolo (msdn.microsoft.com/magazine/dn913185) a ciascuno di essi per vedere come si sentono. Tenete a mente, limitando le cose a 2D, sto lasciando fuori un certo numero di motori di gioco 3D per il Web. Quelli tratterò in futuro. Per ora, mi concentrerò su 2D e le opportunità ci.

Furbo

Furbo (craftyjs.com) è destinato principalmente per i giochi basati su tegola, anche se funziona bene per un'ampia varietà di giochi 2D, compreso il mio gioco di Ping. La Fondazione di Crafty è un sistema di modello e dipendenze oggetto chiama componenti.

I componenti sono come le classi definiscono con attributi specifici e azioni. Ogni istanza di un componente è chiamato a un'entità. Componenti sono destinate ad essere pesantemente miscelati per rendere entità complesse, ricco. Ad esempio, se volete fare un foglio sprite in Crafty, utilizzare la funzione di sprite per generare componenti che rappresentano i vostri sprites. Ecco come definiscono i tipi di sprite da un'immagine:

Crafty.sprite("sprites.png", {
  PlayerSprite: [0, 128, 128, 128],
  OpponentSprite: [0, 0, 128, 128],
  BallSprite: [128, 128, 64, 64],
  UpSprite: [128,0,64,64],
  DownSprite: [128,64,64,64],
  LeftSprite: [192,0,64,64],
  RightSprite: [192,64,64,64]});

Ora, se volete fare un'entità di palla che utilizza l'immagine della palla sul foglio sprite, includere il componente BallSprite quando si crea l'entità, utilizzando la funzione "e", come segue:

Crafty.e("Ball, BallSprite");

Questo non disegnarla sullo schermo ancora. Devi dirle Crafty volete questa entità a vivere in 2D (quindi avrà una "x" e "y" di posizione), e si desidera disegnare utilizzando elementi DOM:

Crafty.e("Ball, 2D, DOM, BallSprite");

Se si desidera disegnare gli elementi in una tela, è semplice come la sostituzione del componente Document Object Model (DOM) con tela.

Entità agiscono come oggetti indipendenti sul vostro schermo di reagire agli eventi. Come molte librerie JavaScript, furbo entità hanno una funzione bind per reagire agli eventi. È importante ricordare che si tratta di eventi specifici Crafty. Ad esempio, se volete un'entità di fare qualcosa in ogni fotogramma, hanno reagito all'evento EnterFrame. Questo è esattamente ciò che è necessario spostare la palla basato su chi è in possesso di essa e la fisica appropriata, se necessario. Figura 1 spettacoli palla inizializzazione di entità con una funzione di evento EnterFrame che copre il movimento della palla.

Figura 1 la definizione di entità palla

Crafty.e("Ball, 2D, DOM, BallSprite, Collision")
  .attr({ x: width/2, y: height/2, velocity: [0,0], owner: 'User' })
  .bind('EnterFrame', function () {
    // If there's an owner, have the ball follow the owner.
    // Nudge the ball to the left or right of the player
    // so it doesn't overlap.
    if (this.owner) {
      var owner = Crafty(this.owner);
      switch(this.owner) {
        case 'User':
          this.x = owner.x + 128;
          break;
        case 'Opponent':
          this.x = owner.x - 64;
            }
            this.y = owner.y + 32;
            return;
    }
    // Bounce the ball off the ceiling or floor.
    if (this.y <= 0 || this.y >= (height - 64))
      this.velocity[1] = -this.velocity[1];
    // Move the ball based on its velocity, which is defined in
    // pixels per millisecond.
    var millis = 1000 / Crafty.timer.FPS();
    this.x += this.velocity[0] * millis;
    this.y += this.velocity[1] * millis;
  })

Se guardate attentamente, vedrete "questo" usato frequentemente per riferirsi alla proprietà dell'entità. L'incasso furbo "this. x" e "this" definire la posizione della palla. Quando si crea la palla con il componente 2D, questa funzionalità è aggiunto. La dichiarazione, "this.velocity," è personalizzato per il mio codice. Si può vedere che e definita come una proprietà utilizzando la funzione attr. Lo stesso vale con this.owner, che io uso per capire se sia il giocatore è in possesso di palla.

Crafty ha diversi componenti incorporati, ma io non li uso un enorme nell'esempio Ping. Qui ci sono pochi:

  • Gravità: Crafty ha un motore semplice gravità che tira automaticamente un'entità verso il basso. È possibile definire qualsiasi numero di tipi di elemento di fermare il suo movimento.
  • TwoWay/FourWay/MultiWay Motion: Con questi componenti, si ottiene una varietà di metodi di input di moto in stile arcade. TwoWay è per Platform, con la sinistra, destra e salto opzioni associabili a qualsiasi tasti desiderati. FourWay permette movimento verticistica in direzioni cardinali. MultiWay permette ingressi personalizzati, ciascuno con una direzione arbitraria.
  • Particelle: Crafty include un sistema di particelle veloce, che può essere utilizzato solo con il disegno della tela. Come morbido, rotondo, immagini a colore unico, le particelle sono particolarmente adatti per fumo, incendi ed esplosioni.

Il codice per Ping, implementato in Crafty, è disponibile nel download del codice che accompagna questo articolo. Date un'occhiata per avere un quadro completo di come portato le cose, specialmente il per l'avversario.

Pixi.js

Pixi.js (pixijs.com) è lo scarno, renderer Web 2D di chiud---metal. Pixi.js può rinunciare la tela 2D e tuffarsi direttamente in WebGL, che gli conferisce un aumento significativo delle prestazioni. Un gioco di Pixi.js dispone di due parti principali: una fase che descrive il layout di gioco in un grafico di scena e un renderer che effettivamente disegna gli elementi nella fase sullo schermo utilizzando tela o WebGL. Per impostazione predefinita, Pixi.js utilizzerà WebGL, se disponibile:

$(document).ready(function() {
  // The stage to play your game.
  stage = new PIXI.Stage(0xFFFFFFFF);
  lastUpdate = 0;
  // autoDetectRenderer() finds the fastest renderer you've got.
  renderer = PIXI.autoDetectRenderer(innerWidth, innerHeight);
  document.body.appendChild(renderer.view);
});

Pixi.js ha un grafico di scena e trasformazioni, ma è importante notare che è necessario per eseguire il rendering della scena da soli. Così, per il mio gioco di Ping, tengo il ciclo di rendering andando utilizzando requestAnimationFrame, come segue:

function update(time) {
  var t = time - lastUpdate;
  lastUpdate = time;
  ball.update(t);
  ai.update();
  // New: You actually have to render the scene
  renderer.render(stage);
  requestAnimationFrame(update);
}

A differenza di Crafty, Pixi.js non dettano molto su come strutturare il vostro gioco, quindi praticamente posso utilizzare lo stesso codice del gioco originale di Ping, con lievi modifiche.

La grande differenza che vedrete è che Pixi.js permette sia di caricare gli sprite come immagini da file uno per uno, o come un'immagine affiancata utilizzando uno speciale strumento chiamato TexturePacker. Questo strumento sarà unire automaticamente un gruppo di singole immagini in un unico file ottimizzato con un accompagnamento file JSON che descrive le posizioni di immagine. Ho usato TexturePacker per fare il foglio di sprite. Figura 2 illustrato aggiornata funzione ready, caricamento delle immagini sprite.

Figura 2 caricamento un foglio Sprite in Pixi.js

$(document).ready(function() {
  // Create an new instance of a Pixi stage.
  stage = new PIXI.Stage(0xFFFFFFFF);
  lastUpdate = 0;
  // Create a renderer instance.
  renderer = PIXI.autoDetectRenderer(innerWidth, innerHeight);
  document.body.appendChild(renderer.view);
  var images = ["sprites.json"];
  var loader = new PIXI.AssetLoader(images);
  loader.onComplete = startGame;
  loader.load();
});

Il caricatore è asincrono, in quanto richiede una funzione da chiamare per quando è fatto carico. Le inizializzazioni gioco rimanenti, come la creazione, il giocatore, l'avversario e la palla, sono nella nuova funzione startGame. Per utilizzare questi sprite appena inizializzati, utilizzare il PIXI. Sprite.from -­metodo che accetta un indice nell'array di sprite nel file JSON da cornice.

Una ultima differenza che Pixi.js fornisce è un sistema di input touch e del mouse. Per fare qualsiasi sprite pronto ingresso, ho impostato la proprietà interattiva su "true" e direttamente aggiungere alcuni gestori eventi a sprite. Per esempio, per i pulsanti su/giù, associato eventi per spostare il giocatore su e giù, come illustrato Figura 3.

Figura 3 associare eventi per spostare il giocatore su e giù

up = new PIXI.Sprite.fromFrame(3);
up.interactive = true;
stage.addChild(up);
up.touchstart = up.mousedown = function() {
  player.move(-distance);
}
down = new PIXI.Sprite.fromFrame(4);
down.interactive = true;
stage.addChild(down);
down.touchstart = down.mousedown = function() {
  player.move(distance);
}

È importante notare che dovevo aggiungere manualmente ogni sprite alla scena per renderlo visibile. È possibile controllare fuori la mia versione di Ping costruito in Pixi.js nel download del codice che accompagna questo articolo.

Phaser

Phaser (phaser.io) è un motore di gioco completo che utilizza Pixi sotto il cofano per eseguire tutte le operazioni di rendering. Phaser ha un lungo elenco di caratteristiche, tra cui sprite basato su frame animazione, musica e audio, un sistema di gioco di stato e fisica con tre diverse librerie che contribuisce.

Perché già portato il Ping sul Pixi.js, porting su Phaser è stato semplice. Phaser semplifica la creazione di un oggetto basato su Pixi.js:

ball = game.add.sprite(x, y, 'sprites');

Mentre Phaser può essere più conciso, ci sono luoghi dove le cose sono più complesse. Per esempio, l'ultimo parametro nel codice precedente, "sprite'" si riferisce a un'immagine caricata all'avvio:

game.load.image('sprites', 'sprites.png');

Phaser ha un sistema di fogli diversi sprite per immagini da Pixi.js. È possibile suddividere un'immagine in piastrelle. Per usarlo, chiamare qualcosa come questo:

game.load.spritesheet('sprites', 'mySprites.png',
  spriteWidth, spriteHeight, totalSprites);

Per il mio esempio, io in realtà non uso questa funzione. Il codice appena mostrato che si assume tutti gli sprite sono della stessa dimensione, ma il foglio di sprite Ping ha sprite di 64 pixel e 128 pixel. Come tale, ho dovuto ritagliare manualmente ogni immagine di sprite. Per ritagliare l'immagine per la palla verso il basso, ho impostato un rettangolo di ritaglio su sprite:

ball.cropRect = new Phaser.Rectangle(0, 64, 64, 64);
ball.updateCrop();

Speriamo che questo dimostra alcune della flessibilità del Phaser. Tale flessibilità si manifesta in altri modi, pure. Può rendere il vostro gioco basato su eventi, controllare o per eventi e rispondere loro in sequenza in una funzione di aggiornamento. Per esempio, per gestire un ingresso chiave, è possibile rispondere a un evento:

game.input.keyboard.onDownCallback = function(key) {
  console.log(key + " was pressed.");
}

o controllare se il tasto è premuto nel vostro ciclo di aggiornamento:

function update() {
  if (game.input.keyboard.isDown('a'.charCodeAt(0)) {
    console.log("'A' key was pressed");
  }
}

Questo vale per un sacco di eventi nel Phaser, utilizzando sequenziale o asincrona codifica a seconda della vostra preferenza o necessità. Occasionalmente, Phaser fornisce solo quest'ultima forma di gestione degli eventi. Un buon esempio è che l'Arcade fisica di sistema, dove si deve esplicitamente fare una funzione chiamata per ogni aggiornamento controllare collisioni tra oggetti:

game.physics.arcade.collide(player, ball, function() {
  ballOwner = player;
});

Questo codice determina se la palla ha venuto a contatto con lo sprite del giocatore e dà il controllo della palla al giocatore quando ciò accade. Scopri la mia implementazione di Ping su Phaser con il download del codice per questo articolo.

Avvolgendo

Se stai cercando un dedicato 2D JavaScript motore di gioco, ci sono un sacco di opzioni per soddisfare le vostre esigenze. Qui ci sono due fattori importanti da tenere a mente quando picking un quadro:

Utilizzare solo le funzionalità che necessarie: Don' t essere abbagliati da un sacco di funzioni, strutture monolitiche onnicomprensivo e altre campane e fischietti. A meno che le campane e fischietti affrontare aspetti specifici del gioco che si vuole fare, si otterrà probabilmente nel modo. Anche considerare come le caratteristiche sono a componenti. Will l'intero sistema a funzionare se si rimuove il sistema di fisica? Fa il quadro affidamento sul sistema di fisica per fornire altre funzionalità?

Comprendere il funzionamento di ogni motore: Cercare di capire come si può personalizzare o estendere il framework. Se stai facendo qualcosa un po' fuori dal comune, è probabile che sarà necessario scrivere codice personalizzato che si basa fortemente sul quadro che hai scelto. A quel punto, è possibile scrivere una funzione nuova, indipendente o estendere le funzionalità del framework. Se si estende l'ambito stesso invece di aggiungere ad esso, vi ritroverete con più codice mantenibile.

I quadri coperti qui affrontano molti di questi problemi, quindi sono sicuramente la pena di verificare. Ci sono decine di motori, librerie e framework là fuori, così faccio qualche ricerca prima di intraprendere la prossima avventura di sviluppo del gioco.


Michael Oneppo *è un tecnologo creativo ed ex program manager di Microsoft Direct3D squadra. Suoi sforzi recenti includono il lavoro come CTO presso la tecnologia biblioteca per tutti senza scopo di lucro e ad esplorare un master presso la NYU interattivo programma di telecomunicazioni.*​

Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Justin Garrett