Tra le ultime cose che ci rimangono da fare, c'è l'aggiunta di un HUD (Head Up Display) che ci mostrerà quante vite abbiamo, il punteggio relativo alle monete raccolte , dei tasti per il menu di pausa e fullscreen.
Carichiamo gli sprites necessari:
this.sprLife = rh.LoadSprite("img/life.png",1);
this.sprLifeLost = rh.LoadSprite("img/lifeLost.png",1);
this.sprPause = rh.LoadSprite("img/pause.png",1);
this.sprResume = rh.LoadSprite("img/resume.png",1);
this.sprFullscreen = rh.LoadSprite("img/fullscreen.png",1);
All'interno di hud.js aggiungiamo un nuovo oggetto Hud
function Hud(){
this.Draw = function() {
//disegna il numero di vite disponibili
for(var i = 0; i < 5; i++){
if(game.player.lives > i)
game.ctx.drawImage(game.sprLife, 70 + i * 50 , game.canvas.height - 60);
else
game.ctx.drawImage(game.sprLifeLost, 70 + i * 50 , game.canvas.height - 60);
}
//variabili per i bottoni
var buttonX = 10,
buttonY = game.canvas.height - 60,
buttonW = game.sprPause.width,
buttonH = game.sprPause.height;
//Se il gioco è in pausa, disegna il menu
if(game.paused) {
game.ctx.drawImage(game.sprResume, buttonX, buttonY);
//menu di pausa
game.ctx.shadowColor = "#000";
game.ctx.shadowOffsetX = 1;
game.ctx.font = "38pt 'PixelFont'"
game.ctx.textAlign="center";
game.ctx.shadowBlur = 3;
var cx = game.canvas.width/2;
var cy = game.canvas.height/2;
if(Inputs.MouseInsideText("Resume",cx, cy,"#eee", "#ea4") && Inputs.GetMousePress(MOUSE_LEFT)) {
game.paused = false;
}
//se clicco main menu
if(Inputs.MouseInsideText("Main Menu",cx, cy+60, "#eee", "#ea4")&& Inputs.GetMousePress(MOUSE_LEFT)) {
//carica il menu principale
game.LoadLevel(0);
}
//resetta l'ombra del testo e il centramento
game.ctx.shadowOffsetX = 0;
game.ctx.shadowBlur = 0;
game.ctx.textAlign="start";
} else {
game.ctx.drawImage(game.sprPause, buttonX, buttonY);
if(Inputs.GetMousePress(MOUSE_LEFT))
{
//se premo il bottone di pausa
if(Inputs.MouseInsideRect(buttonX, buttonY, buttonW, buttonH)){
game.paused = !game.paused;
}
}
}
//disegno il bottone per il fullscreen
game.ctx.drawImage(game.sprFullscreen, game.canvas.width-60, buttonY);
//Disegna il punteggio e il livello corrente
game.ctx.fillStyle = "#fff";
game.ctx.shadowColor = "#000";
game.ctx.shadowOffsetX = 1;
game.ctx.font = "24pt 'PixelFont'"
game.ctx.shadowBlur = 3;
game.ctx.fillText("Score: "+game.score, 380, game.canvas.height - 20);
game.ctx.fillText("Level: "+game.level, 600, game.canvas.height - 20);
game.ctx.shadowOffsetX = 0;
game.ctx.shadowBlur = 0;
}
}
In ResetLevel
aggiungiamo:
this.hud = null;
this.paused = false;
In LoadLevel, dopo la creazione del player, istanziamo l'oggetto Hud
this.hud = new Hud();
In Draw dell'oggetto Game, dopo il rendering dei GameObjects e il debug dei blocchi, aggiungiamo:
this.hud.Draw();
Fullscreen
Anche per il fullscreen ci sono problemi di compatibilità tra i browser. Perciò utilizzeremo screenfull.js , un polyfill molto leggero, completo e semplice da usare.
Per questioni di sicurezza, si è scelto di concedere l'abilitazione del fullscreen solo tramite l'evento click di un elemento del DOM. Quindi aggiungiamo al nostro canvas un listener per il click del mouse, da cui attiveremo il fullscreen:
// evento click del canvas
this.canvas.addEventListener("click", function() {
// se clicco sull'icona del fullscreen
if(Inputs.MouseInsideRect(game.canvas.width-60, game.canvas.height - 60, game.sprFullscreen.width, game.sprFullscreen.height)) {
//abilita o disabilita il fullscreen
screenfull.toggle(game.canvas);
}
}, false);
utilizziamo la funzione onchange per gestire il ridimensionamento del canvas:
screenfull.onchange = function() {
// se è pieno schermo
if(screenfull.isFullscreen) {
// imposta la dimensione del canvas in base alla grandezza
//dello schermo mantenendo le proporzioni
ratio = game.canvas.height/game.canvas.width;
//grandeza dell'area visualizzata
game.canvas.width = window.innerWidth*ratio;
game.canvas.height = window.innerHeight*ratio;
//grandezza effettiva del canvas a schermo
game.canvas.style.width = window.innerWidth + "px";
game.canvas.style.height = window.innerHeight + "px";
} else {
//grandezza dell'area visualizzata
game.canvas.width = game.canvas.defaultWidth;
game.canvas.height = game.canvas.defaultHeight;
//grandezza effettiva del canvas a schermo
game.canvas.style.width = game.canvas.defaultWidth + "px";
game.canvas.style.height = game.canvas.defaultHeight + "px";
}
}
Fps counter
Per misurare le prestazioni del gioco, creiamo un semplice fps counter
Dichiariamo in Game alcune variabili
// variabili fps counter
this.dt = 0;
this.fps = 0;
this.frames = 0;
this.millisec = 0;
this.prevTime = Date.now();
Quindi nella funzione gameloop aggiungiamo il seguente codice:
// calcola il delta time in millisecondi
this.dt = Date.now() - this.prevTime;
// accumula i millisecondi trascorsi
this.millisec += this.dt;
// se sono trascorsi più di 1000 millisecondi (un secondo)
if(this.millisec >= 1000) {
// calcola i millisecondi extra
this.millisec = this.millisec % 1000;
// salva nella variabile fps i frames dell'ultimo secondo
this.fps = this.frames;
this.frames = 0;
}
// salva il tempo corrente
this.prevTime = Date.now();
this.frames++;
Per mostrare a schermo gli fps correnti, usiamo fillText in Draw dell'oggetto Game:
this.ctx.fillText( this.fps, 30, 30);
Canvas offscreen
È possibile creare altri canvas nascosti dal documento e renderizzarci sopra immagini, altri canvas(tramite la funzione drawImage), testi, e primitives.
Il codice da utilizzare è semplice:
var canvas = document.createElement("canvas");
canvas .width = w;
canvas .height = h;
var ctx = canvas.getContext("2d");
Possiamo quindi utilizzare tutte le funzioni di draw che ha il context del nostro canvas. Ad esempio, è possibile ottenere un array di pixel (imageData
) tramite la funzione canvas.getImageData();
Manipolando i pixel, si riescono ad ottenere svariati effetti (bianco e nero, effetto seppia, saturazione, ecc). C'è da considerare che questi effetti di post processing non sono applicabili in realtime, perché eseguiti sulla CPU sono parecchio lenti.
Ma se si vogliono modificare alcuni sprite cambiandone il colore, applicare maschere di trasparenza, ottenere screenshot con filtri, questa tecnica potrebbe tornare utile anche in un gioco.
Ad esempio, per applicare un inversione di colore, inseriamo il seguente codice in fondo al Draw dell'oggetto Game:
var imgData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
var w = imgData.width;
var h = imgData.height;
var data = imgData.data;
var len = data.length;
console.log(len);
var r,g,b,a;
for(var i = 0; i < len; i+=4) {
data[i ] = 255 - data[i ]; //rosso
data[i+1] = 255 - data[i+1] ; //verde
data[i+2] = 255 - data[i+2]; // blu
// data[i+ 3] //alpha
}
this.ctx.putImageData(imgData,0,0);
Nota: Le funzioni imageData
sono disponibili solo se utilizzate un webserver come Mongoose o caricando i files sul vostro sito Web.