Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Menu di gioco e livelli

Espandiamo il game engine e iniziamo con la creazione del menu iniziale, col caricare gli asset: font, immagini e sprite, musica ed effetti sonori. Impariamo a gestire i livelli
Espandiamo il game engine e iniziamo con la creazione del menu iniziale, col caricare gli asset: font, immagini e sprite, musica ed effetti sonori. Impariamo a gestire i livelli
Link copiato negli appunti

Una volta compresa la struttura e le funzionalità di base del game engine possiamo concentrarci sulla sua implementazione e sulla costruzione delle diverse parti che compongono il gioco. Iniziamo col creare un menu principale utilizzando risorse grafiche (sprite) e testi.

Al fine di rendere tutto più semplice, ho sviluppato un platformer in stile Megaman, che fin da adesso prenderemo come riferimento. È possibile provare il gioco, scaricare lo zip in allegato e sentirsi liberi di forkare il repository git contenente il codice sorgente e le risorse grafiche su github.

Considerando come risultato finale questo screenshot:

Il nostro menu, sarà composto da:

  • Elementi testuali cliccabili
  • Uno sfondo
  • Un logo col titolo del gioco + immagine laterale del personaggio
  • Eventualmente un footer coi crediti

Il font per i testi

Iniziamo con gli elementi testuali e inseriamo nel nostro index.html un CSS per ottenere un font personalizzato:

<style>
@font-face
{
	font-family: 'PixelFont';
	src: url('font/pixelfont.eot') format('embedded-opentype'),
	url('font/pixelfont.woff') format('woff'),
	url('font/pixelfont.ttf') format('truetype');
	font-weight: normal;
	font-style: normal;
}
</style>

Quindi creiamo una cartella font nel progetto, in cui inseriamo i file del font in tre formati (pixelfont.eot, pixelfont.ttf, pixelfont.woff), per avere compatibilità completa con tutti i browser.

Esistono svariati servizi online che convertono i normali fonts in webfont, ad esempio FontSquirrel. In alternativa potete utilizzare dei webfonts già pronti da Google Fonts

Caricare gli asset grafici

Proseguiamo, caricando le risorse grafiche che ci servono: all'interno della funzione Game in main.js inseriamo il seguente codice

rh = new ResourcesHandler( function() {
	game.LoadLevel(0);
	game.GameLoop();
});
// Ricordiamo la sintassi di LoadSprite:
// ResourceHandler.LoadSprite(src, subimages, callback);
this.sprLogo        = rh.LoadSprite("img/logo.png",1);
this.sprSplashLogo  = rh.LoadSprite("img/splashLogo.png",1);
//cursore del mouse
this.sprCursor = rh.LoadSprite("img/cursor.png",1); 
this.backgroundMenu = rh.LoadSprite("img/backgroundmenu.png", 1, function() {
	game.patternMenu = game.ctx.createPattern(game.backgroundMenu,"repeat");
});

Creiamo un istanza di ResourceHandler e inseriamo come callback una funzione che definiremo successivamente, con il compito di caricare il menu (Livello 0) e avviare la funzione GameLoop. Carichiamo quindi 2 sprites: il logo e l'immagine affiancata.

Per lo sfondo useremo un pattern (ovvero un immagine ripetuta), ma è necessario che l'immagine sia già caricata, quindi inseriamo in LoadSprite una callback con la funzione di creare un nuovo pattern.

Caricare la musica

Carichiamo anche la musica che sarà riprodotta in loop:

// Suoni
this.sndMusic = rh.LoadSound("audio/datagrove",["ogg", "mp3"]);

Aggiungiamo funzioni al game engine

Nonostante siamo già in piena fase di creazione del gioco, possiamo continuare ad implementare il game engine. Definiamo la funzione Inputs.MouseInsideText in inputs.js.

Inputs.MouseInsideText = function(str, x, y, col1, col2) {
	var w = game.ctx.measureText(str).width;
	var h = 30;
	var inside = (Inputs.mouseX > x - w/2 && Inputs.mouseY > y - h && Inputs.mouseX < x + w/2 && Inputs.mouseY < y+4 );
	if(inside) game.ctx.fillStyle = col2;
	else game.ctx.fillStyle = col1;
	game.ctx.fillText(str, x, y);
	return inside;
};

Questa funzione misura la lunghezza del testo e verifica se le coordinate del mouse sono all'interno del testo, come nel rettangolo mostrato in questa immagine:

Poi disegna il testo alle coordinate x, y, del colore "col2" se il mouse è sopra il testo, altrimenti utilizza "col1". Infine ritorna true se il mouse è all'interno del testo.

Nota: purtroppo, in HTML5 non è possibile ottenere l'altezza del testo in modo performante, quindi è consigliabile utilizzare un ulteriore argument nella funzione e definire a mano tale valore.

Torniamo al gioco

Finalmente creiamo un file hud.js che conterrà tutte le funzioni relative all'interfaccia grafica e inseriamo la seguente funzione:

function MainMenu() {
	game.sndMusic.loop = true;
	game.sndMusic.play();
	this.Draw = function() {
	    // disegna lo sfondo
		game.ctx.save();
		game.ctx.fillStyle = game.patternMenu;
		game.ctx.fillRect(0, 0, game.canvas.width, game.canvas.height);
		game.ctx.restore();
		// mostra logo e personaggio
		game.ctx.drawImage(game.sprLogo, game.canvas.width/2 - game.sprLogo.width/2 , 80);
		game.ctx.drawImage(game.sprSplashLogo, 70 , 180);
		game.ctx.shadowColor = "#000";
		game.ctx.shadowOffsetX = 1;
		game.ctx.shadowBlur = 3;
		// imposta il font
		game.ctx.font = "32pt 'PixelFont'"
		game.ctx.textAlign = "center";
		// centro del canvas
		var cx = game.canvas.width/2;
		var cy = game.canvas.height/2;
		// disegna il menu e rileva le azioni dell'utente
		if(Inputs.MouseInsideText("New Game",cx, cy+10,"#eee", "#ea4") && Inputs.GetMousePress(MOUSE_LEFT)) {
			//carica il livello 1
			game.LoadLevel(1);
		}
		if(Inputs.MouseInsideText("Other games",cx, cy+80,"#eee", "#ea4") && Inputs.GetMousePress(MOUSE_LEFT)) {
			window.location.href = "http://google.com";
		}
		game.ctx.shadowOffsetX = 0;
		game.ctx.shadowBlur = 0;
	}
}

Esaminiamo questa dichiarazione. Quando viene istanziato, l'oggetto MainMenu imposta l'esecuzione della musica in loop.

Nella funzione Draw definiamo prima di tutto il background: dopo aver impostato il fillStyle del context, disegnamo un rettangolo grande quanto il canvas, che sarà riempito col pattern (save e restore si occupano di salvare e ripristinare lo status del context).

I parametri di createPattern possono essere repeat, repeat-x, repeat-y oppure no-repeat.

Creato lo sfondo si piazzano sullo schermo il logo e l'immagine del personaggio.

Definire un gradiente in HTML5 Canvas

Se non vogliamo utilizzare un pattern come sfondo, potremmo creare dei gradienti in questo modo:

var gradient = context.createLinearGradient(0, 0, 0, game.canvas.height);
gradient.addColorStop(0,    '#171');
gradient.addColorStop(0.33, '#5a5');
gradient.addColorStop(0.66, '#8a8');
gradient.addColorStop(1,   '#8a8');
game.ctx.fillStyle = gradient;

Qui createLinearGradient crea un gradiente da [x1,y1 = 0,0] a [x2,y2 = 0, game.canvas.height] con le istruzioniaddColorStop(stop, colore) aggiungiamo i colori che ci servono e li posizioniamo all'interno della sfumatura, utilizzando un range 0 a 1 (0=inizio della sfumatura e 1=fine).

Una volta definiti i colori, è sufficiente impostare il gradient come fillStyle del context. Il risultato sarà simile a questo:

Scritte e azioni

Prima del rendering del testo, definiamo i parametri per l'ombreggiatura, il font da usare e l'allineamento. Poi invochiamo Inputs.MouseInsideText che oltre ad occuparsi del rendering, ritornerà true nel caso in cui il mouse fosse sopra al testo.

Se l'utente preme il tasto sinistro del mouse (Inputs.GetMousePress(MOUSE_LEFT)), passiamo alla schermata di selezione dei livelli che momentaneamente lasciamo vuota.

In fondo alla funzione, reimpostiamo i valori di shadow a 0 per evitare che l'effetto ombra sia applicato ad altri testi e immagini.

Funzioni per la gestione dei livelli

All'interno di main.js, inseriamo nella function Game il codice seguente:

this.ResetLevel = function() {
	this.mainMenu = null;
	this.levelCompleted = null;
	this.score = 0;
}
this.LoadLevel = function(lev) {
	this.level = lev;
	this.ResetLevel();
	if(lev == 0) {
		this.mainMenu = new MainMenu();
	}
	else {
		//carica un livello di gioco
	}
}
this.Draw = function() {
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	if(this.level == 0) {
		//menu principale
		this.mainMenu.Draw();
	}
	else {
		//disegna il livello di gioco
	}
	//disegna il cursore
	game.ctx.drawImage(game.sprCursor, Inputs.mouseX - game.sprCursor.width/2, Inputs.mouseY - game.sprCursor.height/2);
}
this.Update = function(){
}

Funzione Descrizione
ResetLevel Si occupa di azzerare tutte le variabili al cambio di ogni livello.
LoadLevel Crea un'istanza di MainMenu se carichiamo il livello 0, altrimenti carica un livello di gioco.
Draw Esegue clearRect per pulire la schermata del canvas dal precedente draw ed effettua il rendering del menu o del livello corrente.

Nota: la gestione dei livelli potrebbe essere aggiunta al game engine, generalizzando una modalità di gestione delle scene. In questo caso abbiamo scelto di lasciare il motore più snello possibile, lasciando questa parte definita nel gioco.

Nei prossimi capitoli vedremo come caricare una tilemap, creare un personaggio che si muove e può raccogliere monete.

Ti consigliamo anche