Game Mechanic 101 - Bewegung
Mit diesem Posting geht es an die erste Spielmechanik - im englischen hört sich das spannender an: game mechanic. Für mich war es zunächst wichtig was GameMechanics sind und ich habe mich auf den Weg gemacht das herauszufinden. Beim Lesen von Wikipedia, Blogartikeln und Expertenmeinungen kommt man dann schon etwas in Wanken und fragt sich ob dieser Begriff so eindeutig definiert ist. Von Spielmechanik, game mechanics, gameplay, game design, usability, game theory springen die erklärenden Seiten von Begriff zu Begriff.
Für mich definiere ich die Begriff nun wie folgt:
Game Theory
Der Begriff "Game Theory" ist für mich die akademische Annäherung an Games und untersucht die Einflüsse und Auswirkungen von Spielen oder Inhalte eines Spiels auf das Spiel, den Spieler oder den Spielproduzenten. Somit ist der Faktor von politischer Situation und wirtschaftliches Umfeld genauso einzubeziehen, wie die psychologischen, physiologischen und biologischen Eigenschaften von Spielern. Damit kann dann ein Spiel grob skizziert werden in der Planung oder bei der Bewertung des Effekts.
Game Design
Der Begriff "Game Design" ist das konkrete Planen eines Spieles. Game Designer sind die künstlerische Komponente in der Planungsphase, die mit ihrer Vorstellungskraft die technischen Möglichkeiten, (zu erstellenden) visuellen oder akustischen Effekte ausloten und verbinden mit einer Geschichte die erzählt werden möchte. Sie greifen bei der Erstellung des Konzepts auf Ergebnisse der "Game Theory" zurück und erzeugen mit dem Konzept das grundlegende Spiel von Singleplayer Jump&Run bis Multiplayer Fantasygame (das eine Story haben kann, aber nicht muss). Wenn das Spiel dann entwickelt wird, sind sie die Berater und bewerten "Soll" von "Ist" und passen notfalls die Idee und das Konzept an technische Machbarkeit an.
Game Mechanic
Der Begriff "Game Mechanic" ist ein Element in einem Spiel das dem Konzept aus dem "Game Design" folgt und unterstützt, die gewünschte Wirkung zu erreichen. Es ist der technische Aspekt in der Entwicklung, da eine Reihung von Spielmechaniken die Stimmung oder das "Gameplay" mitentwickelt. Eine Mechanik kann von Einfach bis Komplex das ganze Spiel bestimmen und reicht von nicht-interaktiven Inhalten bis hin zu interaktiven, körperlichen Einbringung.
GamePlay
GamePlay ist das Ergebnis für den Spieler, das mit GameDesign und GameMechanic erzeugt wurde. Im deutschen wäre es einfach der Spielspaß und die Bindung zu einem Spiel die man aufbauen kann.
Nachdem ich die Begriffe aufgeschrieben habe würde ich die nächsten Tätigkeiten eindeutig als "GameMechanic" sehen, denn es geht nur um einen kleinen Teil in einem Spiel. Es ist kein Design und kein Konzept, sondern nur ein Baustein in einem Puzzle: Die GameMechanic "Bewegung" - die Bewegung einer Spielfigur in einer Welt.
Direkt zum Github Repository geht es mit diesem Link und zur Online Demo mit diesem Link
In einem vorherigen Artikel habe ich meine Entwicklungsumgebung beschrieben und diese werde ich für dieses
Projekt natürlich auch nutzen. Zuerst wird ein neuer Ordner erstellt und in dem Ordner lasse ich den
Yeoman Generator phaser-alt
seine Arbeit erledigen, sodass das Grundgerüst steht.
$ mkdir mech01
$ cd mech01
$ yo phaser-alt
Um sich vorzustellen zu können wie das Ergebnis aussehen sein soll, habe ich mit Inkscape eine grobe Zeichnung erstellt auf der ein Boden (schwarz), drei Hindernisse (blau) und die Startposition der Spielfigur (rot) zu sehen ist.
Diese Blockformen werden wir auch als einfachen Grafikersatz nutzen, sprich es gibt keine künstlerisch animierten Charakter oder detaillierte Texturen. Es geht mir nur um den Aufbau und die Möglichkeiten der Mechanik "Bewegung". Die Grafiken sind drei quadratische Bilder (32x32px) mit den jeweiligen Farben:
Nun kann man endlich mit dem coden anfangen und wir beginnen damit die Image Assets im preload
State
einzubinden und zu laden:
'use strict';
/**
* Preload state
*
* Preloader for the game to load all kind of
* assets and lastly start the menu state
*/
function Preload() {
}
Preload.prototype = {
/**
* Preload the preload state
*
* In the preload state all assets are loaded
* via the `this.load` reference to Phaser.Loader.
*
* The visually indicate for the use the loading,
* setup the preloadSprite system.
*/
preload: function preload() {
var cx = this.game.width / 2;
var cy = this.game.height / 2;
this.loadBackground = this.game.add.sprite(cx - 100, cy - 30, 'preload-bg');
this.loadBar = this.game.add.sprite(cx - 100, cy - 30, 'preload-fg');
this.load.setPreloadSprite(this.loadBar);
// load the play button spritesheet
this.load.spritesheet(
'menu-btn-play',
'/assets/images/menu-btn-play.png',
128, 32 // width+height of each frame
);
// load the audio for the menu
this.load.audio('audio-menu-tick', '/assets/audio/menu-tick.mp3');
this.load.audio('audio-menu-select', '/assets/audio/menu-select.mp3');
//******
// Load the 3 quadratic 32x32 images
//******
this.load.image('block-black', '/assets/images/sprite_blockblack.png');
this.load.image('block-blue', '/assets/images/sprite_blue.png');
this.load.image('block-red', '/assets/images/sprite_red.png');
},
/**
* Create the preload state
*
* When the `create` method is called, then the preload
* of assets has been done and we can jump over
* to the `play` state.
*/
create: function create() {
this.state.start('menu');
}
};
module.exports = Preload;
Für die Übersicht folgen nun nochmal die 3 neuen Zeilen die entscheidend sind für das Laden der Bilder.
Phaser bietet via Phaser.Loader
(nutzbar mit this.load
) die einfache Möglichkeit über ein Imagekey (block-xyz)
und der URL zum Bild (/assets/images/sprite_xyz), das Bild vorladen und später zu nutzen über den ImageKey
//******
// Load the 3 quadratic 32x32 images
// this.load.image('image-key', 'path-to-image-on-server');
//******
this.load.image('block-black', '/assets/images/sprite_blockblack.png');
this.load.image('block-blue', '/assets/images/sprite_blue.png');
this.load.image('block-red', '/assets/images/sprite_red.png');
Und damit verlassen wir auch die preload.js
State Datei und begeben uns zum play
State (/app/js/states/play.js
).
Der Play-State wird nun der State sein in dem wir uns die nächste Zeit bewegen werden. Hierbei sind die Funktionen
create()
und update()
die Hauptakteure und daher werde ich das Grundgerüst der play.js
Datei einmal
komplett aufzeigen und bei den folgenden Veränderungen nur noch die entsprechenden Änderungen dokumentieren.
'use strict';
/**
* Play state - the actual game
*/
function Play() {
}
Play.prototype = {
/**
* Preload the game
*/
preload: function preload() {
// prevent window jumping, by binding the keys that
// trigger the browser to scroll
this.input.keyboard.addKeyCapture([
Phaser.Keyboard.LEFT,
Phaser.Keyboard.RIGHT,
Phaser.Keyboard.UP,
Phaser.Keyboard.DOWN,
Phaser.Keyboard.SPACEBAR
]);
// if arrow keys needed, use `createCursorKeys`
this.cursors = this.input.keyboard.createCursorKeys();
// enable advance (precission) timing
this.game.time.advancedTiming = true;
},
/**
* Create the game instance that makes a
* level playable. Constructs the entities and
* the basic logic that is needed
*/
create: function create() {
},
/**
* Update function - called on every frame
*/
update: function update() {
},
/**
* Render function, mostly used for debugging
*/
render: function render() {
// example: debug a sprite object when physics are enabled
// this.game.debug.body(this.player);
},
};
module.exports = Play;
Schritt 1: Laden der Bilder in den play state
Die Bilder sind im preload
State bereits geladen, jedoch noch nicht dem play
State bekannt gemacht.
Also werden wir jetzt bei der Erstellung des Play State (function create()
) die Bilder adden und weisen
die Referenzen auf die hinzugefügten Bilder direkt passenden Variablen zu.
Play.prototype = {
create: function create() {
this.stage.setBackgroundColor(0xffffff); // set backgroundcolor of stage (white)
this.platform = this.game.add.sprite(0, this.game.height / 2, 'block-black');
this.pole1 = this.game.add.sprite(200, this.game.height / 2 - 32, 'block-blue');
this.pole2 = this.game.add.sprite(350, this.game.height / 2 - 32, 'block-blue');
this.pole3 = this.game.add.sprite(500, this.game.height / 2 - 32, 'block-blue');
this.player = this.game.add.sprite(64, this.game.height / 2 - 32, 'block-red');
},
};
Alle 5 Sprites werden hinzugefügt mittels this.game.add.sprite(x-pos, y-pos, image-key)
- die Syntax ist
grundlegend selbsterklärend - der image-key
Parameter ist der Name, den man im preload
State gewählt hat.
Wenn das Spiel nun im Browser geöffnet wird, dann sollte man folgendes sehen
Schritt 2: Plattform und Hindernisse skalieren
Die schwarze Plattform ist noch keine durchgehende Plattform und die blauen Blöcke als Hindernisse sind keine wirklichen Hindernisse, daher Skalieren wir die Elemente auf der Y-Achse.
create: function create() {
this.stage.setBackgroundColor(0xffffff); // set backgroundcolor of stage (white)
this.platform = this.game.add.sprite(0, this.game.height / 2, 'block-black');
this.pole1 = this.game.add.sprite(200, this.game.height / 2 - 32, 'block-blue');
this.pole2 = this.game.add.sprite(350, this.game.height / 2 - 32, 'block-blue');
this.pole3 = this.game.add.sprite(500, this.game.height / 2 - 32, 'block-blue');
this.player = this.game.add.sprite(64, this.game.height / 2 - 32, 'block-red');
this.platform.scale.x = this.game.width / 32;
this.pole1.scale.y = 4;
this.pole2.scale.y = 6;
this.pole3.scale.y = 4;
},
Ankerpunkte (Anchors) eines Sprites
Das Ergebnis der Skalierung zeigt eine durchgehende schwarze Plattform und wachsende Hindernisse. Jedoch sind die blauen Blöcke nicht mehr richtig ausgerichtet und gehen durch den schwarzen Boden. Was ist passiert? Phaser (und vermutlich jede andere Engine auch) hat einen Referenzpunkt auf einem Sprite und bei Aktionen die die Größe, Position oder Rotation des Sprites verändern wird, abhängig von diesem Referenzpunkt, die Aktion ausgeführt. Der Referenzpunkt heißt Ankerpunkt (engl. Anchor) und ist der Punkt x:0, y:0 des Sprites. Bei Phaser befindet sich dieser Nullpunkt immer links-oben, alle Aktionen werden also ausgehend von der oberen, linken Ecke des Sprites ausgeführt.
Als einfaches Beispiel ist die Positionierung eines Sprites in unserem Spiel. Wenn wir ein Sprite an der Position x:100, y:100 setzen dann wird der Anchor (0, 0) des Sprites auf die Position gesetzt und von dieser Position das Bild gezeichnet:
Das verändern des Anchorpoints in Phaser pro Sprite funktioniert über die Eigenschaft anchor
this.pole1.anchor.setTo(0, 1);
Die Zahlen 0, 1
sind zu erklären: Ausgehend vom Nullpunkt eines Sprites kann auf einer Werteskala zwischen
0 und 1 für den X und Y Ankerpunkt gewählt werden. Um sich das merken zu können eine kleine Zeichnung:
In dem obigen Code-Beispiel setzen wir den x Ankerpunkt auf 0 (unverändert zum Defaultwert) und den y Ankerpunkt auf 1, also die maximale Breite vom Sprite. Man kann sich das auch merken mit Prozentangaben. Die Werteskala zwischen 0 und 1 entspricht 0 und 100% der Breite (x-Achse), bzw. Höhe (y-Achse) des Bildes. Das setzen des Ankerpunkts auf die Mitte des Bildes (also 50% der Breite und 50% der Höhe) würde bedeuten, den Ankerpunkt auf x:0,5 und y:0,5 zu setzen (50%/100=0,5).
Wenn wir diesen Ankerpunkt nun gesetzt haben und führen eine Aktion auf das Sprite aus, führt das natürlich zu unterschiedlichen Ergebnissen. Als Beispiel habe ich nun die Rotation eines Bildes gewählt mit unterschiedlichen Anchorpoints und der Auswirkung der unterschiedlichen Anchors:
Um das nun für alle Elemente im Spiel anzuwenden, ändern wir die jeweiligen Anchors und müssen dann die initiale Positionierung des jeweiligen Element anpassen.
Play.prototype = {
create: function create() {
this.stage.setBackgroundColor(0xffffff); // set backgroundcolor of stage (white)
this.platform = this.game.add.sprite(0, this.game.height / 2, 'block-black');
this.pole1 = this.game.add.sprite(200, this.game.height / 2, 'block-blue');
this.pole2 = this.game.add.sprite(350, this.game.height / 2, 'block-blue');
this.pole3 = this.game.add.sprite(500, this.game.height / 2, 'block-blue');
this.player = this.game.add.sprite(64, this.game.height / 2 - 32, 'block-red');
this.platform.scale.x = this.game.width / 32;
this.pole1.scale.y = 4;
this.pole2.scale.y = 6;
this.pole3.scale.y = 4;
this.pole1.anchor.setTo(0, 1);
this.pole2.anchor.setTo(0, 1);
this.pole3.anchor.setTo(0, 1);
},
};
Das Ergebnis sollte dann wie folgt aussehen und der Vorstellung entsprechen:
Physik aktivieren
Das Layout unserer Sprites ist abgeschlossen und es geht nun darum die Interaktivität zu aktivieren. Interaktivität bedeutet in Spielen das Arbeiten gegen etwas und in diesem einfachen Beispiel arbeiten wir auch gegen etwas einfaches: Nämlich Physik.
Phaser bietet das grundlegende Feature an und lässt wählen zwischen den Physik Engines Arcade, Ninja und P2. Arcade die einfache und P2 eine komplexere Physik Engine, zudem kann man weitere Physik Engines einbinden um die Komplexität der Physikberechnung zu erhöhen. Auch hier gilt, einfaches Spiel -> einfache Physik Engine und es wird die Arcade Engine genutzt.
Im Boot State ist die Engine bereits eingestellt mittels
this.game.physics.startSystem(Phaser.Physics.ARCADE);
Jetzt geht es darum zu sagen, auf welche Elemente im Spiel die Physik Engine angewandt werden soll. Dazu überlegen wir uns was eine Physik Engine macht.
Basics Physik Engine
Eine Physik Engine macht aus einem Sprite einen Gegenstand und ein Gegenstand hat unter anderem eine Masse und die Spielwelt erhält eine Gravitation die einen Gegenstand je nach Masse nach unten zieht.
Wenn in einem Phaser Game die Physik Engine aktiviert wird, müssten somit alle Element (wie zum Beispiel der Boden oder die Hindernisse) nach unten fallen. Das passiert allerdings nicht, denn der Physik Engine wurden zwei Merkmale noch nicht bekannt gemacht: Welche Elemente haben eine Masse und müssen in der Physik Engine berechnet werden und welche Gravitation herrscht in der Spielwelt.
Standardmässig wird kein Sprite oder Gegenstand im Spiel der Physik Engine bekannt gemacht und die Gravitation ist x:0, y:0 - sprich es ist ein Raum ohne Gravitation. Das ist bei einem Jump&Run eher unvorteilhaft, denn ohne Gravitation macht das Springen wenig Sinn. Der Hintergrund warum Phaser kein Objekt, trotz aktivierter Physik, keine Masse gibt ist vermutlich dem Umstand geschuldet, das nicht jedes Element ein interaktives Element sein muss. So kann ein Sprite auch nur ein dekoratives Bild im Hintergrund sein oder das muss die Physik Engine ja nicht berechnen. Das sollte man aus dem Absatz auch mitnehmen: Physik Engine kostet Performance - je weniger die Physik Engine zu berechnen hat, desto flüssiger wird das Spiel laufen.
Also werden nun die Spielobjekte der Physik Engine bekannt gemacht und die Spielwelt erhält eine y-Gravitation und Gegenstände fallen nach unten.
Play.prototype = {
create: function create() {
this.stage.setBackgroundColor(0xffffff); // set backgroundcolor of stage (white)
this.platform = this.game.add.sprite(0, this.game.height / 2, 'block-black');
this.pole1 = this.game.add.sprite(200, this.game.height / 2, 'block-blue');
this.pole2 = this.game.add.sprite(350, this.game.height / 2, 'block-blue');
this.pole3 = this.game.add.sprite(500, this.game.height / 2, 'block-blue');
this.player = this.game.add.sprite(64, this.game.height / 2 - 32, 'block-red');
this.platform.scale.x = this.game.width / 32;
this.pole1.scale.y = 4;
this.pole2.scale.y = 6;
this.pole3.scale.y = 4;
this.pole1.anchor.setTo(0, 1);
this.pole2.anchor.setTo(0, 1);
this.pole3.anchor.setTo(0, 1);
// ****
// enable pyhsics for these objects
this.game.physics.arcade.enable([this.platform, this.pole1, this.pole2, this.pole3, this.player]);
// enable gravity, so that elements "fall down"
this.physics.arcade.gravity.y = 2000;
},
};
Mit diesen zwei Zeilen ist das ganze schon erledigt und nun ergeben sich neue Probleme. Denn die Gravitation wirkt sich auf alle Objekte aus, die eine Masse haben (der Physik Engine bekannt sind) und beim starten/reloaden das Spieles fällt alles nach unten aus dem Bildschirm. Blöd aber logisch.
Damit das unterbunden werden kann muss der Physik Engine gesagt werden das zum Beispiel der Boden und die blauen Hindernisse zwar in der Physik Engine vorhanden sein sollen, aber nicht von der Gravitation beeinflusst werden dürfen. Ebenso wird direkt gesagt, das auch kein anderer Einfluss diese Gegenstände die Position verändern können. Ein "anderer Einfluss" könnte später die Spielfigur sein, die gegen die Hindernisse läuft und damit diese wegschiebt. Das kann für manche Arten von Spielen natürlich ein gewünschter Effekt sein (Puzzle Game, bei dem man Kisten verschieben muss), aber hier in der Erklärung der Mechanik "Bewegung einer Spielfigur" macht es kein Sinn.
Play.prototype = {
create: function create() {
this.stage.setBackgroundColor(0xffffff); // set backgroundcolor of stage (white)
this.platform = this.game.add.sprite(0, this.game.height / 2, 'block-black');
this.pole1 = this.game.add.sprite(200, this.game.height / 2, 'block-blue');
this.pole2 = this.game.add.sprite(350, this.game.height / 2, 'block-blue');
this.pole3 = this.game.add.sprite(500, this.game.height / 2, 'block-blue');
this.player = this.game.add.sprite(64, this.game.height / 2 - 32, 'block-red');
this.platform.scale.x = this.game.width / 32;
this.pole1.scale.y = 4;
this.pole2.scale.y = 6;
this.pole3.scale.y = 4;
this.pole1.anchor.setTo(0, 1);
this.pole2.anchor.setTo(0, 1);
this.pole3.anchor.setTo(0, 1);
// enable pyhsics for these objects
this.game.physics.arcade.enable([
this.platform, this.pole1, this.pole2, this.pole3, this.player]);
// enable gravity, so that elements "fall down"
this.physics.arcade.gravity.y = 2000;
// *****
// Set platform and poles to be static
this.platform.body.immovable = true;
this.platform.body.allowGravity = false;
this.pole1.body.immovable = true;
this.pole1.body.allowGravity = false;
this.pole2.body.immovable = true;
this.pole2.body.allowGravity = false;
this.pole3.body.immovable = true;
this.pole3.body.allowGravity = false;
},
};
Bei einem neuen Test des Spieles werden die Plattform und Poles stehen bleiben, aber die
rote Spielfigur wird weiterhin nach unten fallen. Es würde keinen Sinn machen, die
Eigenschaften immovable
und allowGravity
wie bei der Plattform/Poles zu setzen, denn
eine Spielfigur die man nicht bewegen kann macht nicht besonders viel Spaß. Das Thema wird
später in der update()
Funktion gelöst.
Wir belassen die create()
Funktion erst einmal so wie sie ist und führen eine kleine
Zusammenfassung durch. In dieser Funktion wurden die drei Sprites in den Play State
hinzugefügt, positioniert und skaliert. Anschließend ist diesen Objekten die Physik
und der gesamten Spielwelt eine Gravitation auf der vertikalen Achse zugewiesen.
Damit die Physik auf die statischen Elemente (Boden, Hindernisse) keine Auswirkung hat,
sind diese explizit von der Beeinflussung durch die Gravitation (allowGravity = false)
und einem generellen verändern der Position (immovable = true) ausgenommen. Der
Spieler hat weiterhin die Eigenschaft, das er den Regeln der Physik folgt und fällt
durch den Boden. Der Code ist derzeit nicht schön und sehr repetetiv und muss noch aufgeräumt werden.
GameLoop update()
In Phaser wird wenig von einem GameLoop gesprochen, sondern der GameLoop besteht daraus,
das bei jedem Frame die update()
Funktion im State aufgerufen wird. Somit wird
jede Bewegung der Spielfigur und die Auswirkung der Bewegung in update()
gesteuert.
Das erste Thema das angegangen wird ist das fallen der roten Spielfigur. Der Spieler soll sich nach links/rechts/oben bewegen können und dabei mit dem Boden und den Hindernissen kollidieren - sprich er läuft auf dem Boden und gegen die Hindernisse. In der Aussage ist auch gleichzeitig die Lösung für den fallen roten Klotz gegeben: Die Spielfigur muss mit dem Boden in der Physik Engine kollidieren, sodass die Physik Engine die Bewegung (fallen) stoppt.
Play.prototype = {
// other code
update: function update() {
// check physics between player and ground
this.game.physics.arcade.collide(this.player, this.platform);
},
};
Mit dieser einen Zeile ist die Kollision zwischen Spieler und Boden bereits definiert und die rote Spielfigur fällt nicht mehr durch den Boden durch. Das gleiche können wir auch direkt schon für die blauen Hindernisse tun und die Kollision zwischen Player und Pole definieren.
Play.prototype = {
// other code
update: function update() {
// check physics between player and ground
this.game.physics.arcade.collide(this.player, this.ptlatform);
this.game.physics.arcade.collide(this.player, [this.pole1, this.pole2, this.pole3]);
},
};
Die Spielfigur bewegen
Das nun entscheidene Element im Spiel und der eigentlichen GameMechanic kommt endlich:
Die Bewegung der Spielfigur! Dazu ist es wichtig zu verstehen, was denn Bewegung in diesem
kleinen Spiel eigentlich bedeutet: Wenn wir mit den Pfeiltasten auf der Tastatur eine
Bewegung vom Spiel fordern, wird dieser Tastendruck länger andauernd als 1 Frame (16ms) und
pro Frame und gedrückter Taste müssen die x/y Position der Spielfigur verändert werden.
Das könnte einfach über this.player.position.setTo(x, y)
erfolgen, doch da müssen wir
zuerst noch die aktuelle Position des Spielers ermitteln und eine Anzahl von Pixel addieren.
Also ist die Bewegung der Spielfigur im Spiel pro Frame(!) die aktuelle Position plus
die vom Entwickler gewünschte Veränderung "v" pro Pixel pro Frame. Wenn wir die Variable "v"
nun hoch ansetzen, bewegt sich unsere Spielfigur pro Frame schneller vorwärts und das Spiel
wirkt schnell und aktionreich. Wenn dieser Wert aber gering ist, bewegt sich die Spielfigur
eben langsamer.
Phaser als GameEngine hat zusammen mit der Physik Engine natürlich bereits alles an Board um das sehr schnell und einfach zu lösen. Wir brauchen die Informationen über Input vom Spieler (Tastendruck Keyboard) um damit der Physik Engine zu sagen das die Figur bewegt werden soll um einen Wert "v" (pro Frame). Die Physik Engine wird zunächst eine Kollision prüfen und wenn das erfolgreich ist, die Position der Spielfigur aktualisieren.
Die Überwachung der Pfeiltasten der Tastatur wurde bereits in Play::preload() aktiviert mit
der Zeile this.cursors = this.input.keyboard.createCursorKeys();
und der Status über einen
Tastendruck liegt nun in this.cursors
vor. Da wir die Bewegung "links|rechts|oben" in
dem Spiel simulieren wollen, prüfen wir pro Frame ob die entsprechen Pfeiltasten gedrückt werden.
Wenn einer dieser Taste gedrückt wird verändern wir die "velocity" der Masse des Spielers,
was bei Phaser der "body" ist. Velocity ist eigentlich die Geschwindigkeit und ich finde
den Begriff nicht deutlich, daher ist für mich Velocity hier die Veränderung der aktuellen
Position um einen Faktor "v" in Pixel und laut Phaser Dokumentation ist der Faktor "v" die
Anzahl der Pixel pro Sekunde (nicht Frame).
Wir lassen die Spielfigur sich bewegen:
Play.prototype = {
// other code
update: function update() {
// check physics between player and ground
this.game.physics.arcade.collide(this.player, this.platform);
this.game.physics.arcade.collide(this.player,
[this.pole1, this.pole2, this.pole3]);
var v = 300;
if(this.cursors.left.isDown) {
this.player.body.velocity.x = -v;
} else if(this.cursors.right.isDown) {
this.player.body.velocity.x = v;
} else {
this.player.body.velocity.x = 0;
}
if(this.cursors.up.isDown) {
this.player.body.velocity.y = -v;
} else {
this.player.body.velocity.y = 0;
}
},
};
Wenn die Taste 'Pfeil links' oder 'Pfeil rechts' gedrückt ist, verändere die Velocity auf der x-Achse um den Wert "v" (sprich hier 300px pro Sekunde). Wenn keiner der beiden Tasten gedrückt werden, dann setzte die Velocity wieder auf 0 zurück (ansonsten rutscht die Spielfigur einfach weiter). Das Prinzip funktioniert bei der 'Pfeil oben' Abhandlung das identisch.
Dabei wird man vielleicht zwei Probleme feststellen:
- Die Spielfigur kann aus dem Bildschirm gehen
- Die Spielfigur springt nicht einmal, sie schwebt wie mit einem JetPack
Und auch das wird nun gefixt. Für Punkt 1 bieter Phaser bereits eine einfache Lösung. In create()
wird
der Player auf die Spielwelt limitiert mit der Zeile:
Play.prototype = {
// other code
create: function create() {
// other code
this.player.body.collideWorldBounds = true;
},
// other code
}
Bei Punkt 2 wird der Aufwand etwas größer, denn die Situation sieht wie folgt aus:
Während es beim laufen nach links oder rechts okay ist, wenn der Spieler durchgehen laufen kann (es entspricht ein wenig der Logik das man lange laufen kann), so sieht es beim Springen anders aus. Springen ist ein Akt, bei dem einmal Kraft aufgewandt und sich vom Boden abgestoßen wird. Ausgehend von der Kraft springt man hoch und wird dann sofort von der Schwerkraft herunter gezogen, jedoch hier kann der Spieler so lange wie er will auf die Pfeiltaste "up" drück und damit ist die Kraft unendlich.
Als Optionen gibt es zwei Wege: Das drücken der Sprungtaste hält nur für ~100ms an und alle Tastendrücke danach werden ignoriert oder man prüft beim Tastendruck ob der Spieler Kontakt mit einem Untergrund hat und lässt ihn dann springen. Der zweite Weg unterbindet damit auch einen anderen Trick den der Spieler anwenden kann: Fällt er von einem Hindernis kann er in der Luft immer noch "springen", da der Timer erst nach dem ersten Sprung anläuft. Somit wird der zweite Weg, die Prüfung ob der Spieler auf dem Grund steht, gegangen.
Hierbei ist Phaser aber auch wieder hilfreich, denn es gibt über das Physik System die Option zu prüfen,
ob das Player Sprite Berührung zu einem anderen (der Physik Engine bekannten) Sprite hat.
Die Implementierung in update()
sieht wie folgt aus:
Play.prototype = {
// other code
update: function update() {
// other code
var isOnGround = this.player.body.touching.down; // check if touching ground
if(isOnGround && this.cursors.up.isDown) {
this.player.body.velocity.y = -v * 2;
}
},
// other code
}
Damit sollten die Probleme gefixt sein, jedoch wird der Sprung auf das erste Hindernis schon zum
Ärgernis, denn es ist einfach zu hoch oder wie springen nicht hoch genug. Hierbei einfach mit den
scale
Werten der Hindernisse, Gravitation oder dem v
Velocity Wert spielen, bis ein angenehmes
Spielgefühl entsteht.
Zusammenfassung
Der Post war sehr lang und enthält eigentlich auch nicht viel Code und Inhalt, aber es die erste grundlegende Spielmechanik oder so wichtig für das Spielgeschehen, das es mir wichtig war jeden einzelnen Punkt genau zu erklären.
Die Beispiele die man bei der Entwicklung entdeckt hat geben viele Möglichkeiten für Spiele. Nehmen wir nur die Thematik um das durchgehende Springen beim drücken der Pfeiltaste "up". Aus einem Sprung ist ein JetPack verhalten geworden nur durch eine Eigenschaft der Physik Engine und dem verhalten wie ein simpler Code reagiert.
Weiter wird man beim einstellen der Werte für Hohe der Hindernisse, Velocity oder Gravity merken, das man eine breite an Spielen nur mit den Werten erstellen und ein anderes Spielgefühl erstellen kann. Fastpace Jump & Run oder sneaky hiding Game? Die Werte machen eine Menge aus.
Das der Code nicht schön aussieht, wartbar ist und schlecht zu erweitern ist, ist ein Umstand der gefixt werden muss. In dem Zusammenhang sollte ich auch die Erweiterung der Welt und Platzierung von Hindernissen angehen. Aber das ist ein Thema für ein anderen Blogpost.