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

OOP e Videogame, l'oggetto GameObj

Sfruttare la programmazione a oggetti con JavaScript per ottimizzare il codice del game engine creando oggetti con caratteristiche simili da riusare.
Sfruttare la programmazione a oggetti con JavaScript per ottimizzare il codice del game engine creando oggetti con caratteristiche simili da riusare.
Link copiato negli appunti

Come visto nei capitoli precedenti, nonostante JavaScript non sia propriamente un linguaggio Object Oriented, possiamo sfruttare l'ereditarietà di primo livello tramite prototype. Vediamo quindi come sfruttare l'ereditarietà per eliminare il codice ripetuto e includere il codice riusabile in un unico oggetto GameObj.

function GameObj(x, y) {
	// variabili di uso comune inizializzate dal costruttore
	this.x = x;
	this.y = y;
	this.animSpeed = 1;
	this.curFrame = 0;
	this.xOffset = 0;
	this.yOffset = 0;
	// questa funzione imposta l'offset di
	// rendering al centro dello sprite
	this.OffsetCenter = function() {
		this.xOffset = this.sprite.w/2;
		this.yOffset = this.sprite.height/2;
	}
	// cicla una lista di gameobject
	// ritorna l'id di un oggetto con cui
	// collide il bounding box
	this.GetCollision = function(gameobj_list, x, y) {
		var len = gameobj_list.length;
		for(var i = 0; i < len; i++){
			if(this.bbox.CollidesAt(gameobj_list[i].bbox, x, y)) {
				return gameobj_list[i];
			}
		}
		return null;
	}
	// metodo di draw per GameObject semplici
	this.__DrawSimple = function() {
		game.ctx.drawImage(this.sprite, this.x - game.viewX - this.xOffset, this.y - game.viewY - this.yOffset);
	}
	// metodo di draw per GameObject animati
	this.__DrawAnimated = function() {
		var ox = Math.floor(this.curFrame) * this.sprite.w;
		game.ctx.drawImage(this.sprite, ox, 0, this.sprite.w,this.sprite.height,this.x - game.viewX - this.xOffset, this.y - game.viewY - this.yOffset, this.sprite.w, this.sprite.height);
	}
	// imposta lo sprite e seleziona l'evento draw corretto
	this.SetSprite = function(sprite) {
		this.sprite = sprite;
		if(sprite.frames > 0) {
			// imposta l'evento draw animato
			this.Draw = this.__DrawAnimated;
		} else {
			//imposta l'evento draw semplice e cancella l'evento animazione
			this.Draw = this.__DrawSimple;
			this.UpdateAnimation = function(){};
		}
	}
	// aggiorna i frames dell'animazione in base a animSpeed,
	// che può essere sia positiva che negativa.
	this.UpdateAnimation = function() {
		this.curFrame += this.animSpeed;
		if(this.animSpeed > 0) {
			var diff = this.curFrame - this.sprite.frames;
			if(diff >= 0) {
				this.curFrame = diff;
				// al termine dell'animazione, chiama questa funzione
				this.OnAnimationEnd();
			}
		}
		else if(this.curFrame < 0) {
			this.curFrame = (this.sprite.frames + this.curFrame) - 0.0000001;
			// al termine dell'animazione, chiama questa funzione
			this.OnAnimationEnd();
		}
	}
    // distrugge l'istanza corrente eliminandola dalla lista dei
	// gamobjects e attiva l'evento OnDestroy
	this.Destroy = function() {
		this.OnDestroy();
		game.gameobjects.splice(game.gameobjects.indexOf(this), 1);
	}
	// inizializza le variabili degli eventi non generici con funzioni vuote
	this.Draw = function(){}
	this.Update = function(){}
	this.OnAnimationEnd = function(){}
	this.OnDestroy = function(){}
	//aggiunge questo gameobject alla lista di gameobjects
	game.gameobjects.push(this);
}

Il prossimo passo, è definire l'array gameobject in ResetLevel

this.ResetLevel = function() {
	//...
	this.gameobjects = [];
}

Generalizziamo le funzioni Update, EndLoop e Draw di Game:

// aggiorna tutti i gameobjects
this.Update = function() {
	if(this.level > 0) {
		for(var i = 0; i < this.gameobjects.length; i ++) {
			this.gameobjects[i].Update();
		}
	}
}
// aggiorna tutte le animazioni
this.EndLoop = function() {
	if(this.level > 0) {
		for(var i = 0; i < this.gameobjects.length; i ++) {
			this.gameobjects[i].UpdateAnimation();
		}
	}
}
// disegna le tiles e i gameobjects
this.Draw = function() {
	//...
	// al posto di player.Draw()
	for(var i = 0; i < this.gameobjects.length; i ++) {
		this.gameobjects[i].Draw();
	}
	//...
}

In questo modo stabiliamo che, a ogni loop, gestiremo tutto ciò che viene dichiarato nei rispettivi metodi di tutti i game object.

Ereditarietà dei game object

Definiamo una nuova funzione, che chiamiamo Inherit, che ci consente di creare nuovi oggetti eredi di GameObj

function Inherit(obj, parent) {
	// impostiamo GameObj come parent predefinito
	if(parent == undefined)
		parent = GameObj;
	//imposta il prototype per ereditare dal parent
	obj.prototype = Object.create(parent.prototype);
	obj.prototype.constructor = obj;
}

Facciamo le opportune modifiche all'oggetto Player, per ereditare da GameObj. Intanto aggiungiamo il codice relativo ai colpi subiti e impostiamo un invulnerabilità di qualche secondo dopo l'impatto, mostrata visivamente da un l'effetto grafico "trasparenza".

Ecco la function completa:

function Player() {
	// imposto le coordinate di partenza
	this.xStart = game.canvas.width/2;
	this.yStart = game.canvas.height/2-80;
	this.x = this.xStart;
	this.y = this.yStart;
	// chiamo il costruttore del parent
	GameObj.call(this, this.x, this.y);
	//imposto lo sprite e il metodo Draw
	this.SetSprite(game.sprPlayerRun, true);
	this.width = this.sprite.w;
	this.height = this.sprite.height;
	this.xOffset = Math.floor(this.width/2);
	this.yOffset = this.height;
	this.animSpeed = 0.2;
	this.maxSpeed = 5;
	this.hSpeed = 0;
	this.vSpeed = 0;
	this.gravity = 0.4;
	this.scaling = 1;
	this.lives = 5;
	this.shotTime = 0;
	this.canShot = true;
	// variabile che indica se il personaggio è stato colpito
	this.hit = false;
	//durata dell'invulnerabilità dopo il colpo
	this.hitTimer = 0;
	//variabili trasparenza
	this.hitAlpha = 0;
	this.hitAlphaTimer = 30;
	this.bbox = new BoundingBox(this.x - this.xOffset, this.y - this.yOffset, this.width, this.height);
	this.Draw = function() {
		game.ctx.save();
		// se è stato colpito, utilizza la trasparenza "a intermittenza"
		if(this.hit) {
			if(this.hitAlpha)
				game.ctx.globalAlpha = 0.3;
			else
				game.ctx.globalAlpha = 0.6;
		}
		game.ctx.translate(this.x-game.viewX,this.y-game.viewY);
		game.ctx.scale(this.scaling, 1);
		var ox = Math.floor(this.curFrame) * this.width;
		game.ctx.drawImage(this.sprite, ox, 0, this.sprite.w, this.sprite.height, -this.xOffset, -this.sprite.height, this.sprite.w, this.sprite.height);
		game.ctx.restore();
	}
	this.Update = function() {
		if(Inputs.GetKeyDown(KEY_RIGHT)) {
			if(this.hSpeed < 0) this.hSpeed = 0;
			if(this.hSpeed < this.maxSpeed) this.hSpeed += 0.4;
		}
		else if(Inputs.GetKeyDown(KEY_LEFT)) {
			if(this.hSpeed > 0) this.hSpeed = 0;
			if(this.hSpeed > -this.maxSpeed) this.hSpeed-=0.4;
		} else {
			this.hSpeed/=1.1;
			if(Math.abs(this.hSpeed) < 1) {
				this.hSpeed = 0;
				this.sprite = game.sprPlayerIdle;
				this.curFrame = 0;
			}
		}
		if(Inputs.GetKeyPress("Z") && this.GetCollision(game.blocks, 0 , 1)){
			this.vSpeed -= 9;
		}
		this.vSpeed += this.gravity;
		var 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;
		}
		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(this.hSpeed != 0) {
			// sposto il player
			this.scaling = (this.hSpeed < 0) ? -1 : 1;
			if(this.sprite != game.sprPlayerRun) {
				this.sprite = game.sprPlayerRun;
				this.curFrame = 0;
			}
			this.animSpeed = 0.1 + Math.abs( this.hSpeed / this.maxSpeed * 0.12);
		}
		if(this.vSpeed > 0){
			this.sprite = game.sprPlayerFall;
			this.curFrame = 0;
		} else if(this.vSpeed < 0) {
			this.sprite = game.sprPlayerJump;
			this.curFrame = 0;
		}
		// aggiorna le variabii relative allo stato di "colpito"
		if(this.hit) {
			this.hitTimer--;
			this.hitAlphaTimer--;
			if(this.hitAlphaTimer < 0) {
				this.hitAlpha = !this.hitAlpha;
				this.hitAlphaTimer = 10;
			}
			if(this.hitTimer <= 0){
				this.hit = false;
			}
		}
		this.bbox.Move(this.x - this.xOffset, this.y - this.yOffset);
		//se il player cade sotto il livello, muore
		if(this.y > game.areaH) {
			this.Die();
		}
		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 personaggio, 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));
	}
	//imposta lo status in "colpito" per un certo tempo
	this.Hit = function() {
		this.hit = true;
		this.hitTimer = 140;
		this.hitAlpha = true;
		this.hitAlphaTimer = 10;
		// riduce le vite di 1
		this.lives--;
		if(this.lives <= 0) {
			this.Die();
		}
		//simula un contraccolpo verso l'alto
		this.vSpeed = Math.clamp(this.vSpeed-5, -5, 0);
	}
	// riporta il personaggio all'inizio del livello
	// azzera le vite e il punteggio
	this.Die = function() {
		this.x = this.xStart;
		this.y = this.yStart;
		this.lives = 5;
		game.score = 0;
	}
}

Inseriamo appena sotto la funzione Player, il seguente codice:

// Player eredita da GameObj
Inherit(Player);

Ti consigliamo anche