Grundlagen der Spieleprogrammierung – Game Over

Game Over

 

Die Programmierung von Leben und Tod

 

Im Beitrag Objekte kollidieren lassen haben wir die Klasse Meteor erstellt und die Klasse Bullet mit einer Kollisions-Abfrage versehen, die ĂŒberprĂŒft ob ein Projektil mit einem Meteor-Objekt kollidiert. Der Spieler soll aber nicht nur reine ZielĂŒbungen meistern, sondern den Meteoren ausweichen, bevor er getroffen wird.

Der aktuelle Stand des Spiels lĂ€sst die Meteore nur ĂŒber unser Raumschiff hinweg fliegen. Da Meteore aber bekanntlich Dinge zerstören die sie treffen, sollten wir das mithilfe einer Kollisions-Abfrage an unserem Raumschiff berĂŒcksichtigen. Dadurch bekommt der Spieler einen Anreiz die vorbei fliegenden Objekte nicht nur zu zerstören, sondern diesen auch auszuweichen, wenn ein Offensiv-Angriff keine Option mehr darstellt.

Wir benötigen also eine Abfrage, ob Meteore in unserer Array myMeteors mit dem Raumschiff kollidieren und sollten auch testen ob unser Raumschiff zu viel Schaden abbekommen hat. Der Schaden wird durch die Anzahl der Leben visualisiert, die wir bereits im Beitrag Vorbereitung und Planung in unserer Klasse Spaceship erstellt und deklariert haben.

Dank der update()-Funktion unseres Raumschiffs können wir eine neue Funktion zur Timeline der Main-Loop hinzufĂŒgen und diese in unserem Schiff unterbringen. Die Funktion collision() wird unser PrĂŒf-Element, das mit einer Schleife durch die Positionen aller Meteore iteriert um eine mögliche Kollision mithilfe von checkCollision() festzustellen. Sollte eine Kollision zwischen einem Meteor und dem Raumschiff stattfinden wird zuerst Leben vom Raumschiff abgezogen und der Meteor wird im Anschluss darauf zerstört, indem dessen Variable Destroyed einen bool'schen Wert true erhĂ€lt. Durch die Integration beider Algorithmen in der gleichen Funktion verhindern wir eine mögliche Doppel-Kollision.

/* ... */
class Spaceship {
	constructor( args ) {
		/* ... */
	}
	update() {
		if( !GameOver ) {
			if( typeof myInput !== "undefined" ) {
				this.move();
				this.shoot();
				this.collision();
			}
			/* ... */
		}
	}
	move() {
		/* ... */
	}
	shoot() {
		/* ... */
	}
	collision() {
		
		for( var i=0; i < myMeteors.length; i++ )
			if( !myMeteors[ i ].Destroyed )
				if( checkCollsion( this, myMeteors[ i ] ) ) {
					
					this.Life -= 1;
					if( this.Life <= 0 )
						GameOver = true;
					
					myMeteors[ i ].Destroyed = true;
					
				}
		
	}
}
/* ... */

 

Unser Raumschiff kann nun zwar Schaden erleiden, kann aber immer noch nicht zerstört werden. Denn der Algorithmus fĂŒr den Status GameOver wurde noch nicht in unserem Code implementiert.

FĂŒr diesen Status benötigen wir eine Erweiterung der Renderer-Klasse. Sie wird zukĂŒnftig ĂŒberprĂŒfen, ob das Raumschiff noch Leben besitzt und andernfalls eine Nachricht auf unsere Leinwand projizieren, die dem Spieler mitteilt, dass das Spiel beendet ist.

Die Ausgabe wird durch ein neues HTML-Element gesetzt, das eine eindeutige ID besitzt, damit wir noch ein paar spezielle CSS-Formatierungen vornehmen können. Ich habe fĂŒr Dich eine Formatierung erstellt, die Du gerne fĂŒr Dein eigenes Spiel verwenden kannst. Du kannst gerne die Typografie und Farben nach Belieben Ă€ndern.

var GameOver = false;
/* ... */
(function($) {
	/* ... */
	class Renderer {
		constructor() {
			/* ... */
		}
		update() {
			if( typeof this.Spaceship !== "undefined" ) {
				this.updateSpaceship();
				this.checkGameOver();
			}
			/* ... */
		}
		updateSpaceship() {
			/* ... */
		}
		updateBullets() {
			/* ... */
		}
		updateMeteors() {
			/* ... */
		}
		checkDestroyed() {
			/* ... */
		}
		checkGameOver() {
			
			if( GameOver && this.Spaceship.length ) {
				this.Spaceship.remove();
			}
			
		}
	}
})(jQuery);
#gameover {
	background-color: rgba( 0, 0, 0, .6 );
	color: #ffffff;
	position: absolute;
	height: 100%;
	width: 100%;
	left: 0;
	top: 0;
	z-index: 99;
	
	font-size: 50px;
	line-height: 100%;
	padding: 275px 0;
	text-align: center;
	font-family: monospace;
}

 

Verloren hat der Spieler, wenn seine 5 Leben von einem Meteor auf 0 dezimiert wurden. Leider hat der Spieler aber keine Möglichkeit anzusehen wie viele Leben sein Raumschiff noch besitzt. Lass uns das als nÀchstes Àndern.

Unsere OberflĂ€che hat viel Platz um eine BenutzerflĂ€che zu gestalten, in dem der Benutzer seine aktuellen Statistiken betrachten kann. Die Leben seines Schiffs sollten dazu gehören. Es gibt viele Möglichkeiten die Leben auszugeben, eine Zahl oder ein Balken sind zwei davon. Ich habe mich dafĂŒr entschieden die Anzahl an Leben als Herzen auszugeben. DafĂŒr habe ich eine Grafik vorbereitet, die Du gerne auch fĂŒr dein Projekt verwenden kannst. Wenn Du möchtest kannst Du gerne eine eigene Art ausdenken, wie Du die Leben des Raumschiffs darstellen möchtest.

 

Die HTML-Elemente werden Absolut positioniert in unseren Main-Wrapper einfĂŒgt. Das erreichen wir durch eine Grundformatierung per CSS, die das Hintergrundbild setzt, sowie die GrĂ¶ĂŸe und weitere wichtige Formatierungen angibt. Du hast beim Downloaden der PNG-Datei bestimmt bemerkt, dass die 4 Bilder nicht in unterschiedlichen Dateien liegen, sondern als gebĂŒndeltes Spritesheet erstellt wurden. Wir verwenden fĂŒr diesen Beitrag nur das erste Bild in der Reihe, werden uns aber in einem kommenden Beitrag mit den 3 anderen Bildern beschĂ€ftigen.

Durch die Verwendung von einem Spritesheet sparen wir uns Dateivolumen und somit wertvolle Ladezeit beim Aufruf der Webseite. Wie bereits mit unserem Raumschiff im Beitrag Bewegung entwickeln können wir den Bildausschnitt des Spritesheets mit der Angabe der background-position verÀndern. Durch die Limitierung des Bildausschnitts und der Eigenschaft overflow: hidden wird dadurch nur der Bereich dargestellt, der auf der Leinwand erscheinen darf.

.life {
	background-image: url( '/wp-content/themes/atomik_theme/scheme/post/game/games/SpaceShooter/img/powerups.png' );
	background-size: 100px 25px;
	background-repeat: no-repeat;
	background-position: 0px 0px;
	
	width: 25px;
	height: 25px;
	position: absolute;
	left: 0;
	top: 10px;
	z-index: 98;
}

 

 

Der Renderer wird sich darum kĂŒmmern die Anzahl der Herzen immer gleich mit den Leben zu halten und an die richtige Stelle im GUI zu setzen. Dazu erweitern wir unsere Klasse erneut um die Funktion checkLifes(). In Ihr implementieren wir einen Algorithmus der pro Durchlauf feststellen soll, ob die Anzahl der Leben des Raumschiffs gleich der Herzen auf dem GUI ist. Andernfalls wird die Ausgabe der Leben erneuert, indem die Leben komplett entfernt und mit einer Schleife neu aufgebaut werden. Da die Herzen komplett erneuert werden könntest Du sogar ein Item erstellen, das die Leben des Raumschiffs wieder auffĂŒllen kann. Das wird kein Bestandteil dieser Beitrags-Reihe, aber du solltest im Anschluss genĂŒgend Wissen haben, damit du dieses Item und noch mehr selbststĂ€ndig integrieren kannst.

class Renderer {
	constructor() {
		/* ... */
	}
	update() {
		if( typeof this.Spaceship !== "undefined" ) {
			this.updateSpaceship();
			this.checkGameOver();
			this.checkLifes();
		}
		/* ... */
	}
	updateSpaceship() {
		/* ... */
	}
	updateBullets() {
		/* ... */
	}
	updateMeteors() {
		/* ... */
	}
	checkDestroyed() {
		/* ... */
	}
	checkGameOver() {
		/* ... */
	}
	checkLifes() {
		
		var spaceshipLifes = mySpaceship.Life;
		var currentGUILifes = $( '#main_wrapper .life' ).length;
		if( spaceshipLifes != currentGUILifes ) {
			$( '#main_wrapper .life' ).remove();
			for( var i=0; i < spaceshipLifes; i++ ) {
				$( '#main_wrapper' ).append( '
' ); } } } }

 

Da unsere Meteore im letzten Beitrag ebenfalls Leben erhalten haben, die wir bisher aber noch nicht verwenden, werden wir jetzt unsere Klasse Bullet um einen kleinen Code-Schnipsel erweitern, der den Meteor nicht sofort zerstört, sondern erst SchwĂ€cht bevor er eliminiert wird. Eine Abfrage, ob das Leben des Meteors kleiner oder gleich 0 betrĂ€gt, wird nach der Subtraktion des Projektil-Schadens vom Leben durchgefĂŒhrt.

class Bullet {
	constructor( args ) {
		/* ... */
	}
	
	update() {
		/* ... */
	}
	move() {
		/* ... */
	}
	collision() {
		
		if( !this.Destroyed )
			for( var i=0; i < myMeteors.length; i++ )
				if( !myMeteors[ i ].Destroyed )
					if( checkCollsion( this, myMeteors[ i ] ) ) {
						myBullets[ this.ID ].Destroyed = true;
						
						// Um den Schaden zu berechnen mĂŒssen wir nur das Leben des Meteors mit dem verursachenden Schaden des Projektils subtrahieren
						myMeteors[ i ].Life -= this.Damage;
						if( myMeteors[ i ].Life <= 0 ) {
							// Danach prĂŒfen wir ob das Leben des Meteors kleiner gleich 0 ist und setzen die Variable "Destroyed" erst dann auf "true"
							myMeteors[ i ].Destroyed = true;
						}
						
					}
		
	}
	
}

 

In vielen FĂ€llen möchte man dem Spieler die Möglichkeit geben sich mit anderen zu messen, um einen Wett. Welche Möglichkeit ist dafĂŒr besser geeignet als ein Score?

Mit Score bezeichnet man die Punkte, die der Spieler in einer Sitzung erlangt hat. Wir als Programmierer des Spiels mĂŒssen festlegen, wie viele Punkte der Spieler fĂŒr welche Ereignisse erhĂ€lt. Unser SpaceShooter gibt schon fast vor, dass der Spieler Punkte erhĂ€lt, wenn er Meteore abschießt. Da wir zwei unterschiedliche Meteore haben können wir fĂŒr diese 2 Typen auch unterschiedlich viele Punkte verteilen. Ich habe definiert, dass kleine Meteore 25 Punkte und große Meteore 100 Punkte einbringen. Diese beiden Werte kannst Du aber gerne nach Belieben individualisieren.

FĂŒr den Score benötigen wir zunĂ€chst eine globale Variable, die beim zerstören des Meteors mit dem definierten Wert addiert wird. Das passiert in der Funktion collision() unseres Projektils.

var Score = 0;
/* ... */
(function($) {
	/* ... */
	class Bullet {
		constructor( args ) {
			/* ... */
		}
		
		update() {
			/* ... */
		}
		move() {
			/* ... */
		}
		collision() {
			
			if( !this.Destroyed )
				for( var i=0; i < myMeteors.length; i++ )
					if( !myMeteors[ i ].Destroyed )
						if( checkCollsion( this, myMeteors[ i ] ) ) {
							myBullets[ this.ID ].Destroyed = true;
							
							myMeteors[ i ].Life -= this.Damage;
							if( myMeteors[ i ].Life <= 0 ) {
								myMeteors[ i ].Destroyed = true;
								
								// Es wird geprĂŒft, von welchem Typ der Meteor ist und dementsprechend wird der Score um 25 oder 100 Punkte erhöht 
								if( myMeteors[ i ].Type == "Big" )
									Score += 100;
								else 
									Score += 25;
							}
							
						}
			
		}
		
	}
	/* ... */
})(jQuery);

 

 

Außerdem soll der Renderer unsere Variable auf der GUI ausgeben, damit der Spieler seinen Score immer im Überblick behĂ€lt. Die update()-Funktion wird um einen neuen Aufruf der Funktion changeScore() erweitert, die den Score Ă€ndern soll, sobald die angezeigte Punktausgabe nicht mit der Variable ĂŒbereinstimmt. jQuery besitzt bereits eine Funktion, mit der Inhalte von HTML-Elementen in der Laufzeit bearbeitet werden können. Das HTML-Element wird per ID selektiert und mit dem .html()-Befehl kann der Inhalt des Elements, also den Score des Spielers, eingefĂŒgt werden.

class Renderer {
	constructor() {
		/* ... */
	}
	update() {
		/* ... */
	}
	updateSpaceship() {
		/* ... */
	}
	updateBullets() {
		/* ... */
	}
	updateMeteors() {
		/* ... */
	}
	checkDestroyed() {
		/* ... */
	}
	checkGameOver() {
		
		if( GameOver )
			if( $( '#spaceship' ).length ) {
				this.Spaceship.remove();
				$( '#main_wrapper' ).append( '
GameOver
Score: '+Score+'
' ); } } checkLifes() { /* ... */ } changeScore() { var $Score = $( '#main_wrapper #score' ); if( $Score.length ) { if( $Score.html() !== Score ) $Score.html( Score ); } else { $( '#main_wrapper' ).append( '
'+Score+'
' ); } } }

 

Zu guter Letzt können wir den Score noch einmal ausgeben lassen, sobald der Spieler besiegt wurde und es GameOver heißt. Der Score wird in ein HTML-Element gepackt und im Wrapper der GameOver-Nachricht angehĂ€ngt. Mit CSS können wir die Formatierung des Scores ein wenig verĂ€ndern um eine kleinere SchriftgrĂ¶ĂŸe zu erhalten, als der Text „Game Over“. Du hast natĂŒrlich wieder die Möglichkeit deine eigene Formatierung zu setzen, wenn Du das möchtest.

#score {
	position: absolute;
	right: 25px;
	top: 10px;
	z-index: 98;
	
	font-size: 22px;
	color: #ffffff;
	font-weight: 600;
	font-family: monospace;
}
/* ... */
#gameover .score {
	font-size: 32px;
}

 

Nun mĂŒssen wir nur noch den Renderer ein klein wenig erweitern, indem wir in der Funktion checkGameOver() den Score in den String der html()-Funktion hinzufĂŒgen. Eine CSS-Klasse .score hilft dem Score seine CSS-Formatierung zu erhalten.

 

Mit diesen Änderungen können sich unsere Spieler messen. Wenn Du Dich mit Datenbanken auskennen solltest kannst du die Scores pro Spieler sichern und einen Highscore entwickeln, der die besten Spieler neben Deinem Spiel ausgibt. Damit können sich die Spieler sogar auf deiner Webseite verewigen.

Der nĂ€chste Beitrag behandelt temporĂ€re PowerUps, die es ermöglichen werden das Raumschiff fĂŒr einen kurzen Zeitraum zu verbessern. Diese Beispiele kannst du verwenden um Deine eigenen PowerUps zu entwickeln, um den Spieler mit noch mĂ€chtigeren Boni zu belohnen.

 

Codepalm
Spieleprogrammierung fĂŒr Einsteiger
Teil 8: Game Over

Abonniere die Fanpage von Codepalm und verpasse keine BeitrÀge mehr

Codepalm auf Facebook
 
 
 
 
Das könnte Dir gefallen:

Unity5 Spieleentwicklung: Umfassendes Training

EUR 34,99

Zu Amazon Mehr erfahren

Einstieg in Unity: 2D- und 3D-Programmierung

EUR 29,90

Zu Amazon Mehr erfahren

Spieleprogrammierung mit Cocoa und OpenGL

EUR 49,70

Zu Amazon Mehr erfahren

Kommentare

Sei der Erste, der einen Kommentar erstellt!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.