Introduzione
Quindi vuoi creare un gioco utilizzando Canvas e HTML5? Segui questo tutorial e sarai subito operativo.
Il tutorial presuppone almeno un livello intermedio di conoscenza di JavaScript.
Puoi prima giocare al gioco o passare direttamente all'articolo e visualizzare il codice sorgente del gioco.
Creazione della tela
Per disegnare, dobbiamo creare una tela. Poiché si tratta di una guida senza lacrime, utilizzeremo jQuery.
var CANVAS_WIDTH = 480;
var CANVAS_HEIGHT = 320;
var canvasElement = $("<canvas width='" + CANVAS_WIDTH +
"' height='" + CANVAS_HEIGHT + "'></canvas>");
var canvas = canvasElement.get(0).getContext("2d");
canvasElement.appendTo('body');
Ciclo di gioco
Per simulare l'aspetto di un gameplay fluido e continuo, vogliamo aggiornare il gioco e ridisegnare lo schermo più velocemente di quanto la mente e l'occhio umano possano percepire.
var FPS = 30;
setInterval(function() {
update();
draw();
}, 1000/FPS);
Per il momento possiamo lasciare vuoti i metodi update e draw. L'importante è sapere che setInterval()
si occupa di chiamarli periodicamente.
function update() { ... }
function draw() { ... }
Hello World
Ora che abbiamo un ciclo di gioco, aggiorniamo il metodo draw per disegnare del testo sullo schermo.
function draw() {
canvas.fillStyle = "#000"; // Set color to black
canvas.fillText("Sup Bro!", 50, 50);
}
È fantastico per il testo fisso, ma dato che abbiamo già configurato un loop di gioco, dovremmo riuscire a farlo muovere abbastanza facilmente.
var textX = 50;
var textY = 50;
function update() {
textX += 1;
textY += 1;
}
function draw() {
canvas.fillStyle = "#000";
canvas.fillText("Sup Bro!", textX, textY);
}
Ora provaci. Se stai seguendo la procedura, l'indicatore dovrebbe muoversi, ma anche lasciare le volte precedenti in cui è stato disegnato sullo schermo. Prova a capire perché potrebbe essere così. Questo perché non stiamo cancellando lo schermo. Aggiungiamo del codice per la cancellazione dello schermo al metodo draw.
function draw() {
canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
canvas.fillStyle = "#000";
canvas.fillText("Sup Bro!", textX, textY);
}
Ora che hai del testo che si muove sullo schermo, hai quasi completato la procedura per avere un vero e proprio gioco. Basta perfezionare i controlli, migliorare il gameplay e rifinire la grafica. Ok, forse 1/7 del percorso per avere un vero gioco, ma la buona notizia è che il tutorial offre molto di più.
Creazione del player
Crea un oggetto per contenere i dati del giocatore e gestisci aspetti come il disegno. Qui creiamo un oggetto player utilizzando un semplice letterale oggetto per contenere tutte le informazioni.
var player = {
color: "#00A",
x: 220,
y: 270,
width: 32,
height: 32,
draw: function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
}
};
Per il momento utilizziamo un semplice rettangolo colorato per rappresentare il player. Quando disegniamo il gioco, cancelliamo la tela e disegniamo il giocatore.
function draw() {
canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
player.draw();
}
Controlli da tastiera
Utilizzare i tasti di scelta rapida jQuery
Il plug-in jQuery Hotkeys semplifica notevolmente la gestione dei tasti nei vari browser.
Invece di piangere per problemi indecifrabili relativi a keyCode
e
charCode
su più browser, possiamo associare gli eventi nel seguente modo:
$(document).bind("keydown", "left", function() { ... });
Non dover preoccuparti dei dettagli relativi a quali chiavi hanno quali codici è un gran vantaggio. Vogliamo solo poter dire cose come "quando il giocatore preme il pulsante su, fai qualcosa". jQuery Hotkeys lo consente.
Movimento del giocatore
Il modo in cui JavaScript gestisce gli eventi della tastiera è completamente basato sugli eventi. Ciò significa che non esiste una query integrata per verificare se una chiave è inattiva, quindi dovremo utilizzare la nostra.
Potresti chiederti: "Perché non utilizzare semplicemente un metodo di gestione delle chiavi basato su eventi?" Il motivo è che la frequenza di ripetizione della tastiera varia da un sistema all'altro e non è vincolata alla temporizzazione del loop di gioco, quindi il gameplay può variare notevolmente da un sistema all'altro. Per creare un'esperienza coerente, è importante integrare strettamente il rilevamento degli eventi della tastiera con il loop di gioco.
La buona notizia è che ho incluso un wrapper JS di 16 righe che renderà disponibili le query sugli eventi. Si chiama key_status.js e puoi eseguire query sullo stato di una chiave in qualsiasi momento controllando keydown.left
e così via.
Ora che abbiamo la possibilità di eseguire query per verificare se i tasti sono premuti, possiamo utilizzare questo semplice metodo di aggiornamento per spostare il player.
function update() {
if (keydown.left) {
player.x -= 2;
}
if (keydown.right) {
player.x += 2;
}
}
Provaci.
Potresti notare che il player può essere spostato fuori dallo schermo. Blocchiamo la posizione del giocatore per mantenerlo entro i limiti. Inoltre, il player sembra un po' lento, quindi aumentiamo anche la velocità.
function update() {
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
Aggiungere altri input sarà altrettanto facile, quindi aggiungiamo una sorta di proiettili.
function update() {
if (keydown.space) {
player.shoot();
}
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
player.shoot = function() {
console.log("Pew pew");
// :) Well at least adding the key binding was easy...
};
Aggiunta di altri oggetti di gioco
Proiettili
Ora aggiungiamo i proiettili veri e propri. Per prima cosa, abbiamo bisogno di una raccolta in cui archiviarli tutti:
var playerBullets = [];
Successivamente, abbiamo bisogno di un costruttore per creare istanze di elenchi puntati.
function Bullet(I) {
I.active = true;
I.xVelocity = 0;
I.yVelocity = -I.speed;
I.width = 3;
I.height = 3;
I.color = "#000";
I.inBounds = function() {
return I.x >= 0 && I.x <= CANVAS_WIDTH &&
I.y >= 0 && I.y <= CANVAS_HEIGHT;
};
I.draw = function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
};
I.update = function() {
I.x += I.xVelocity;
I.y += I.yVelocity;
I.active = I.active && I.inBounds();
};
return I;
}
Quando il giocatore spara, dobbiamo creare un'istanza di proiettile e aggiungerla alla raccolta di proiettili.
player.shoot = function() {
var bulletPosition = this.midpoint();
playerBullets.push(Bullet({
speed: 5,
x: bulletPosition.x,
y: bulletPosition.y
}));
};
player.midpoint = function() {
return {
x: this.x + this.width/2,
y: this.y + this.height/2
};
};
Ora dobbiamo aggiungere l'aggiornamento dei punti elenco alla funzione del passaggio di aggiornamento. Per evitare che la raccolta di elenchi di punti elenco si riempia a tempo indeterminato, filtriamo l'elenco in modo da includere solo i punti elenco attivi. In questo modo possiamo anche rimuovere i proiettili che hanno colpito un nemico.
function update() {
...
playerBullets.forEach(function(bullet) {
bullet.update();
});
playerBullets = playerBullets.filter(function(bullet) {
return bullet.active;
});
}
Il passaggio finale consiste nel disegnare i punti elenco:
function draw() {
...
playerBullets.forEach(function(bullet) {
bullet.draw();
});
}
Nemici
Ora è il momento di aggiungere i nemici nello stesso modo in cui abbiamo aggiunto i proiettili.
enemies = [];
function Enemy(I) {
I = I || {};
I.active = true;
I.age = Math.floor(Math.random() * 128);
I.color = "#A2B";
I.x = CANVAS_WIDTH / 4 + Math.random() * CANVAS_WIDTH / 2;
I.y = 0;
I.xVelocity = 0
I.yVelocity = 2;
I.width = 32;
I.height = 32;
I.inBounds = function() {
return I.x >= 0 && I.x <= CANVAS_WIDTH &&
I.y >= 0 && I.y <= CANVAS_HEIGHT;
};
I.draw = function() {
canvas.fillStyle = this.color;
canvas.fillRect(this.x, this.y, this.width, this.height);
};
I.update = function() {
I.x += I.xVelocity;
I.y += I.yVelocity;
I.xVelocity = 3 * Math.sin(I.age * Math.PI / 64);
I.age++;
I.active = I.active && I.inBounds();
};
return I;
};
function update() {
...
enemies.forEach(function(enemy) {
enemy.update();
});
enemies = enemies.filter(function(enemy) {
return enemy.active;
});
if(Math.random() < 0.1) {
enemies.push(Enemy());
}
};
function draw() {
...
enemies.forEach(function(enemy) {
enemy.draw();
});
}
Caricamento e disegno di immagini
È bello vedere tutte queste caselle che volano, ma avere delle immagini sarebbe ancora più bello. Caricare e disegnare immagini su tela è solitamente un'esperienza snervante. Per evitare questo dolore e questa sofferenza, possiamo utilizzare una semplice classe di utilità.
player.sprite = Sprite("player");
player.draw = function() {
this.sprite.draw(canvas, this.x, this.y);
};
function Enemy(I) {
...
I.sprite = Sprite("enemy");
I.draw = function() {
this.sprite.draw(canvas, this.x, this.y);
};
...
}
Rilevamento di collisioni
Abbiamo tutti questi elementi sullo schermo, ma non interagiscono tra loro. Per far sapere a tutto quando esplodere, dobbiamo aggiungere una sorta di rilevamento di collisione.
Utilizziamo un semplice algoritmo di rilevamento delle collisioni rettangolari:
function collides(a, b) {
return a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y;
}
Ci sono un paio di collisioni che vogliamo controllare:
- Proiettili del giocatore => Astronavi nemiche
- Giocatore => Astronavi nemiche
Creiamo un metodo per gestire le collisioni che possiamo chiamare dal metodo di aggiornamento.
function handleCollisions() {
playerBullets.forEach(function(bullet) {
enemies.forEach(function(enemy) {
if (collides(bullet, enemy)) {
enemy.explode();
bullet.active = false;
}
});
});
enemies.forEach(function(enemy) {
if (collides(enemy, player)) {
enemy.explode();
player.explode();
}
});
}
function update() {
...
handleCollisions();
}
Ora dobbiamo aggiungere i metodi di esplosione al giocatore e agli nemici. Verranno segnalati per la rimozione e verrà aggiunta un'esplosione.
function Enemy(I) {
...
I.explode = function() {
this.active = false;
// Extra Credit: Add an explosion graphic
};
return I;
};
player.explode = function() {
this.active = false;
// Extra Credit: Add an explosion graphic and then end the game
};
Suono
Per completare l'esperienza, aggiungeremo degli effetti sonori. I suoni, come le immagini, possono essere un po' complicati da usare in HTML5, ma grazie alla nostra formula magica senza lacrime sound.js, i suoni possono essere resi estremamente semplici.
player.shoot = function() {
Sound.play("shoot");
...
}
function Enemy(I) {
...
I.explode = function() {
Sound.play("explode");
...
}
}
Anche se l'API ora è senza interruzioni, l'aggiunta di suoni è attualmente il modo più rapido per far arrestare in modo anomalo l'applicazione. Non è raro che i suoni vengano interrotti o che l'intera scheda del browser venga interrotta, quindi tieni a portata di mano i fazzoletti.
Farewell
Ecco di nuovo la demo del gioco completa e funzionante. Puoi anche scaricare il codice sorgente in formato ZIP.
Spero che ti sia piaciuto scoprire le nozioni di base per creare un semplice gioco in JavaScript e HTML5. Programmando al giusto livello di astrazione, possiamo difenderci dalle parti più difficili delle API, nonché essere resilienti di fronte a cambiamenti futuri.