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

Fisica per un platform game

Aggiungiamo effetti cinematici ai nostri giochi, introducendo la gravità e servendoci di alcuni blocchetti solidi su cui far camminare i personaggi.
Aggiungiamo effetti cinematici ai nostri giochi, introducendo la gravità e servendoci di alcuni blocchetti solidi su cui far camminare i personaggi.
Link copiato negli appunti

Nelle pagine precedenti abbiamo realizzato il nostro giocatore e implementate le collisioni. Ora possiamo aggiungere un po' di cinematica introducendo la gravità e alcuni blocchetti solidi su cui far camminare il nostro personaggio.

Iniziamo subito definendo "Block" di dimensioni 32x32 pixels. Aggiungiamo all'oggetto Game una variabile cellSize che ci sarà utile più avanti quando importeremo il livello da una tilemap:

function Game(...){
	// ...
	this.cellSize = 32;
	// ...
}
function Block(x, y) {
	this.x = x;
	this.y = y;
	this.width = game.cellSize;
	this.height = game.cellSize;
	this.bbox = new BoundingBox(x, y, this.width, this.height);
}

Creiamo quindi un array di blocchi in ResetLevel sempre dentro Game, creiamo qualche istanza appena sotto il player e aggiungiamo in Draw il rendering del contorno dei blocchetti, al solo scopo di Debug

this.ResetLevel = function() {
	// ...
	this.blocks = [];
}
this.LoadLevel = function(lev) {
	// ...
	if(lev == 0) {
		// ...
	}
	else {
		// ...
		for(i=0; i<10; i++){
			this.blocks.push(new Block(150 + i*32, 500));
		}
	}
}
this.Draw = function() {
	// ...
	if(this.level == 0) {
		// ...
	}
	else {
		// ...
		// debug dei blocchi (cambio colore e spessore)
		this.ctx.strokeStyle = "#c00";
		this.ctx.lineWidth = 1;
		for(var i = 0; i < this.blocks.length; i ++) {
			this.ctx.strokeRect(this.blocks[i].x-game.viewX+0.5, this.blocks[i].y-game.viewY+0.5,this.blocks[i].width,this.blocks[i].height);
		}
	}
	// ...
}

Ecco il risultato:

Aggiungiamo quindi la variabile gravity al nostro oggetto Player e facciamo in modo che questa influenzi la sua velocità verticale (vSpeed)

this.gravity = 0.4;
this.Update = function(){
	// ...
	this.vSpeed += this.gravity;
	this.y += this.vSpeed;
}

Se avviamo il gioco adesso, il personaggio cadrà nel vuoto, perciò dobbiamo aggiungere un check per le collisioni con i blocchi sottostanti. Definiamo il bounding box dell'oggetto Player:

this.bbox = new BoundingBox(this.x - this.xOffset, this.y - this.yOffset, this.width, this.height);

Aggiungiamo una funzione GetCollision al nostro player, che ritornerà l'istanza con cui collide il nostro Player, oppure null.

this.GetCollision = function(gameObjList, x,y){
	for(var i = 0; i < gameObjList.length; i++){
		if(this.bbox.CollidesAt(gameObjList[i].bbox, x, y)){
			return gameObjList[i];
		}
	}
	return null;
}

Questa funzione prende come parametri una lista di GameObjects, e un offset x, y verso cui controllare la collisione.

Più avanti provvederemo a integrare le funzioni di collisione in un unico oggetto generico, sfruttando al meglio l'ereditarietà.

Sfruttiamo la funzione appena creata per controllare la collisione col terreno. All'interno di Update del player aggiungiamo:

this.Update = function() {
	// ...
	this.vSpeed += this.gravity;
	if( !this.GetCollision(game.blocks, 0, this.vSpeed)) {
		this.vSpeed = 0;
	}
	this.y += this.vSpeed;
}

In questo modo, verifichiamo se il personaggio, spostato di una quantità di pixel pari a vSpeed collide con un blocco, e in tal caso fermiamo la velocità verticale.

Questo causa un piccolo errore che si nota particolarmente se il framerate è basso o gravità maggiori di 1.

Il Player, come si vede nell'immagine sotto, effettua un controllo spostando il bounding box di tot unità sull'asse verticale, e questo lo farà fermare a mezz'aria in prossimità del terreno.

Per risolvere questo problema, dobbiamo spostare il personaggio verso il basso quanto basta per non entrare nel blocco.

Modifichiamo quindi il codice in Update, sostituiamo:

this.y += this.vSpeed;

con questo

this.Update = function() {
	//...
	this.vSpeed += this.gravity;
	collides = false;
	for(var a = Math.abs(this.vSpeed); a > 0; a-=Math.abs(this.gravity)) {
		if(this.vSpeed > 0) {
			if( !this.GetCollision(game.blocks, 0, a)) {
				this.y += a;
				break;
			} else {
				collides = true;
			}
		}
		else {
			if(!this.GetCollision(game.blocks, 0 , -a)) {
				this.y -= a;
				break;
			} else {
				collides = true;
			}
		}
	}
	if(collides) {
		this.vSpeed = 0;
	}
}

Grazie a questo codice, quando si verifica una collisione verticale, il personaggio verrà spostato automaticamente nel punto libero più vicino.

Controlliamo se la velocità è maggiore o minore di 0, in modo da eseguire il controllo nella direzione giusta e facciamo la stessa cosa con hSpeed sull'asse x

var collides = false;
for(var a = Math.abs(this.hSpeed); a > 0; a--) {
	if(this.hSpeed > 0) {
		if( !this.GetCollision(game.blocks, a , 0)) {
			this.x += a;
			break;
		} else
			collides = true;
	}
	else {
		if( !this.GetCollision(game.blocks, - a , 0)) {
			this.x -= a;
			break;
		} else
			collides = true;
	}
}
if(collides) {
	this.hSpeed = 0;
}

Aggiungiamo la possibilità di saltare col tasto Z, applicando una velocità verticale negativa se il player collide con un blocco di sotto. Inseriamo il seguente codice in Update

if(Inputs.GetKeyPress("Z") && this.GetCollision(game.blocks, 0, 1)) {
	this.vSpeed -= 9;
}

facciamo in modo che quando il player salta o sta precipitando, cambi sprite, inserendo prima di bbox.Move il seguente codice:

if(this.vSpeed > 0) {
	this.sprite = game.sprPlayerFall;
	this.curFrame = 0;
} else if(this.vSpeed < 0) {
	this.sprite = game.sprPlayerJump;
	this.curFrame = 0;
}

La view (effetto telecamera)

Prima di proseguire, facciamo in modo che la view (area visualizzata) segua il personaggio. Per rendere meno fastidioso lo spostamento, aggiungiamo qualche funzione di supporto per rendere più dolci i movimenti della telecamera.

In utils.js aggiungiamo 2 funzioni all'oggetto Math di JavaScript

// limita il valore tra un minimo e un massimo scelto
Math.clamp = function(x, min, max) {
	return x < min ? min : (x > max ? max : x);
};
// interpolazione lineare
Math.lerp = function(a, b, u) {
	return (1 - u) * a + u * b;
};

In fondo all'evento Update del Player aggiungiamo

// imposta il target da raggiungere, limitando la visuale
// alla dimensione della mappa
var targetX = Math.clamp(this.x - game.canvas.width/2,  0, game.areaW - game.canvas.width);
var targetY = Math.clamp(this.y - game.canvas.height/2, 0, game.areaH - game.canvas.height);
// muove la telecamera verso il puntotarget, in modo smussato
game.viewX = Math.floor(Math.lerp(game.viewX, targetX, 0.2));
game.viewY = Math.floor(Math.lerp(game.viewY, targetY, 0.2));

Ti consigliamo anche