Talking about Serious Scramblers game, Game development, HTML5, Javascript, Phaser and TypeScript.
As said a couple of days ago on my Twitter account (follow me if you didn’t already), I made a big update to my Serious Scramblers prototype, adding sprites and animations taken from Pixel Adventure assets.
So now the hero is some kind of ninja frog, enemies are angry pigs and platforms look better.
Just like in DROP’d prototype, now platforms are RenderTexture object, to render them as 9-slice scaling objects, although in this example is used only three slices: one for the left edge, one for the platform itself and one for the right edge.
Another feature added in this step is a slight size difference between sprite size and physics body size. I wanted the physics bodies to be smaller than actual sprites, because I didn’t want pixel perfect collisions.
Let’s have a look at the game:
Tap and hold left or right to move the character left or right. Once you move, platforms will scroll up. Reach the top of the stage, and it’s game over.
Fall from platform to platform without falling too down, if you reach the bottom of the stage, it’s game over.
Touch an enemy, and it’s game over. But you can kill enemies by jumping on their head.
The game is made of 10 TypeScript files and one HTML file used in this prototype. Again, I made a lot of changes since previous step, so I am not highlighting the new code, but each and every line has been commented.
index.html
The webpage which hosts the game, just the bare bones of HTML and main.ts
is called.
Also look at the thegame
div, this is where the game will run.
<!DOCTYPE html>
<html>
<head>
<style type = "text/css">
body {
background: #000000;
padding: 0px;
margin: 0px;
}
</style>
<script src = "scripts/main.ts"></script>
</head>
<body>
<div id = "thegame"></div>
</body>
</html>
main.ts
The main TypeScript file, the one called by index.html
.
Here we import most of the game libraries and define both Scale Manager object and Physics object.
Here we also initialize the game itself.
// MAIN GAME FILE
// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame} from './playGame';
import { GameOptions } from './gameOptions';
// object to initialize the Scale Manager
const scaleObject: Phaser.Types.Core.ScaleConfig = {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: 'thegame',
width: GameOptions.gameSize.width,
height: GameOptions.gameSize.height
}
// object to initialize Arcade physics
const physicsObject: Phaser.Types.Core.PhysicsConfig = {
default: 'arcade',
arcade: {
gravity: {
y: GameOptions.gameGravity
}
}
}
// game configuration object
const configObject: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
backgroundColor:0x444444,
scale: scaleObject,
scene: [PreloadAssets, PlayGame],
physics: physicsObject,
pixelArt: true
}
// the game itself
new Phaser.Game(configObject);
preloadAssets.ts
Class to preload all assets used in the game.
// CLASS TO PRELOAD ASSETS
// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {
// constructor
constructor() {
super({
key: 'PreloadAssets'
});
}
// preload assets
preload(): void {
this.load.image('platform', 'assets/platform.png');
this.load.image('background', 'assets/background.png');
this.load.image('leftplatformedge', 'assets/leftplatformedge.png');
this.load.image('rightplatformedge', 'assets/rightplatformedge.png');
this.load.spritesheet('enemy', 'assets/enemy.png', {
frameWidth: 36,
frameHeight: 30
});
this.load.spritesheet('enemy_hit', 'assets/enemy_hit.png', {
frameWidth: 36,
frameHeight: 30
});
this.load.spritesheet('hero', 'assets/hero.png', {
frameWidth: 32,
frameHeight: 32
});
this.load.spritesheet('hero_run', 'assets/hero_run.png', {
frameWidth: 32,
frameHeight: 32
});
}
// method to be called once the instance has been created
create(): void {
// call PlayGame class
this.scene.start('PlayGame');
}
}
gameOptions.ts
Game options which can be changed to tune the gameplay are stored in a separate module, ready to be reused.
// CONFIGURABLE GAME OPTIONS
export const GameOptions = {
// game size, in pixels
gameSize: {
width: 750,
height: 1334
},
// game scale ratio
pixelScale: 3,
// first platform vertical position. 0 = top of the screen, 1 = bottom of the screen
firstPlatformPosition: 4 / 10,
// game gravity, which only affects the hero
gameGravity: 1200,
// hero speed, in pixels per second
heroSpeed: 300,
// platform speed, in pixels per second
platformSpeed: 90,
// platform length range, in pixels
platformLengthRange: [150, 250],
// platform horizontal distance range from the center of the stage, in pixels
platformHorizontalDistanceRange: [0, 250],
// platform vertical distance range, in pixels
platformVerticalDistanceRange: [150, 250],
// platform tint colors
platformColors: [0xffffff, 0xff0000, 0x00ff00],
// bounce velocity when landing on bouncing platform
bounceVelocity: 500,
// disappearing platform time before disappearing, in milliseconds
disappearTime: 1000,
// enemy patrolling speed range, in pixels per second
enemyPatrolSpeedRange: [40, 80],
// chances of an enemy appearing on a platform, 0: no chance, 1: certainly appears
enemyChance: 1
}
playGame.ts
The game itself, the biggest class, game logic is stored here.
// THE GAME ITSELF
// modules to import
import { GameOptions } from './gameOptions';
import PlayerSprite from './playerSprite';
import PlatformSprite from './platformSprite';
import EnemySprite from './enemySprite';
import PlatformGroup from './platformGroup';
import EnemyGroup from './enemyGroup';
// this class extends Scene class
export class PlayGame extends Phaser.Scene {
// group to contain all platforms
platformGroup: PlatformGroup;
// group to contain all enemies
enemyGroup: EnemyGroup;
// the hero of the game
hero: PlayerSprite;
// is it the first time player is moving?
firstMove: Boolean;
// enemy pool, built as an array
enemyPool: EnemySprite[];
// just a debug text to print some info
debugText: Phaser.GameObjects.Text;
// background image
backgroundImage: Phaser.GameObjects.TileSprite;
// left edge platform sprite
leftPlatform: Phaser.GameObjects.Sprite;
// right edge platform sprite
rightPlatform: Phaser.GameObjects.Sprite;
// middle platform sprite
middlePlatform: Phaser.GameObjects.Sprite;
// constructor
constructor() {
super({
key: 'PlayGame'
});
}
// method to be called once the class has been created
create(): void {
// method to initialize platform sprites
this.initializePlatformSprites();
// method to inizialize animations
this.initializeAnimations();
// method to place the background image
this.setBackground();
// add the debug text to the game
this.debugText = this.add.text(16, 16, '', {
color: '#000000',
fontFamily: 'monospace',
fontSize: '48px'
});
// initialize enemy pool as an empty array
this.enemyPool = [];
// this is the firt move
this.firstMove = true;
// create a new physics group for the platforms
this.platformGroup = new PlatformGroup(this.physics.world, this);
// create a new physics group for the enemies
this.enemyGroup = new EnemyGroup(this.physics.world, this);
// let's create ten platforms. They are more than enough
for (let i: number = 0; i < 10; i ++) {
// create a new platform
let platform: PlatformSprite = new PlatformSprite(this, this.platformGroup, this.leftPlatform, this.middlePlatform, this.rightPlatform);
// if it's not the first platform...
if (i > 0) {
// place some stuff on it
this.placeStuffOnPlatform(platform);
}
}
// add the hero
this.hero = new PlayerSprite(this);
// input listener to move the hero
this.input.on("pointerdown", this.moveHero, this);
// input listener to stop the hero
this.input.on("pointerup", this.stopHero, this);
}
// method to set background image
setBackground(): void {
// add a tileSprite
this.backgroundImage = this.add.tileSprite(0, 0, GameOptions.gameSize.width / GameOptions.pixelScale, GameOptions.gameSize.height / GameOptions.pixelScale, 'background');
// set background origin to top left corner
this.backgroundImage.setOrigin(0, 0);
// set background scale
this.backgroundImage.scale = GameOptions.pixelScale;
}
// method to inizialize animations
initializeAnimations(): void {
// hero idle animation
this.anims.create({
key: "idle",
frames: this.anims.generateFrameNumbers('hero', {
start: 0,
end: 10
}),
frameRate: 20,
repeat: -1
});
// hero run animation
this.anims.create({
key: "run",
frames: this.anims.generateFrameNumbers('hero_run', {
start: 0,
end: 11
}),
frameRate: 20,
repeat: -1
});
// enemy run animation
this.anims.create({
key: "enemy_run",
frames: this.anims.generateFrameNumbers('enemy', {
start: 0,
end: 11
}),
frameRate: 20,
repeat: -1
});
// enemy falling animation
this.anims.create({
key: "enemy_falling",
frames: this.anims.generateFrameNumbers('enemy_hit', {
start: 0,
end: 2
}),
frameRate: 20
});
}
// method to inizialize platform sprites
initializePlatformSprites(): void {
// add left platform edge sprite
this.leftPlatform = this.add.sprite(0, 0, 'leftplatformedge');
// set registration point to top left corner
this.leftPlatform.setOrigin(0, 0);
// set sprite to invisible
this.leftPlatform.setVisible(false);
// add right platform edge sprite
this.rightPlatform = this.add.sprite(0, 0, 'rightplatformedge');
// set registration point to top right corner
this.rightPlatform.setOrigin(1, 0);
// set sprite to invisible
this.rightPlatform.setVisible(false);
// add middle platform sprite
this.middlePlatform = this.add.sprite(0, 0, 'platform');
// set registration point to center
this.middlePlatform.setOrigin(0, 0);
// set sprite to invisible
this.middlePlatform.setVisible(false);
}
// method to place stuff on platform
// argument: the platform
placeStuffOnPlatform(platform: PlatformSprite): void {
// should we add an enemy?
if (Math.random() < GameOptions.enemyChance) {
// is the enemy pool empty?
if (this.enemyPool.length == 0) {
// create a new enemy sprite
new EnemySprite(this, platform, this.enemyGroup)
}
// enemy pool is not empty
else {
// retrieve an enemy from the enemy pool
let enemy: EnemySprite = this.enemyPool.shift() as EnemySprite;
// move the enemy from the pool to enemy group
enemy.poolToGroup(platform, this.enemyGroup);
}
}
}
// method to move the hero
// argument: the input pointer
moveHero(e: Phaser.Input.Pointer): void {
// set hero movement according to input position
this.hero.setMovement((e.x > GameOptions.gameSize.width / 2) ? this.hero.RIGHT : this.hero.LEFT);
// is it the first move?
if (this.firstMove) {
// it's no longer the first move
this.firstMove = false;
// move platform group
this.platformGroup.setVelocityY(-GameOptions.platformSpeed);
}
}
// method to stop the hero
stopHero(): void {
// ... just stop the hero :)
this.hero.setMovement(this.hero.STOP);
}
// method to handle collisions between hero and enemies
// arguments: the two colliding bodies
handleEnemyCollision(body1: Phaser.GameObjects.GameObject, body2: Phaser.GameObjects.GameObject): void {
// first body is the hero
let hero: PlayerSprite = body1 as PlayerSprite;
// second body is the enemy
let enemy: EnemySprite = body2 as EnemySprite;
// the following code will be executed only if the hero touches the enemy on its upper side (STOMP!)
if (hero.body.touching.down && enemy.body.touching.up) {
// move the enemy from enemy group to enemy pool
enemy.groupToPool(this.enemyGroup, this.enemyPool);
// play "enemy_falling" animation
enemy.anims.play('enemy_falling', true);
// flip the enemy vertically
enemy.setFlipY(true);
// make the hero bounce
hero.setVelocityY(GameOptions.bounceVelocity * -1);
}
// hero touched an enemy without stomping it
else {
// restart the game
this.scene.start("PlayGame");
}
}
// method to handle collisions between hero and platforms
// arguments: the two colliding bodies
handlePlatformCollision(body1: Phaser.GameObjects.GameObject, body2: Phaser.GameObjects.GameObject): void {
// first body is the hero
let hero: PlayerSprite = body1 as PlayerSprite;
// second body is the platform
let platform: PlatformSprite = body2 as PlatformSprite;
// the following code will be executed only if the hero touches the platform on its upper side
if (hero.body.touching.down && platform.body.touching.up) {
// different actions according to platform type
switch (platform.platformType) {
// breakable platform
case 1:
// if the platform is not already fading out...
if (!platform.isFadingOut) {
// flag the platform as a fading out platform
platform.isFadingOut = true;
// add a tween to fade the platform out
this.tweens.add({
targets: platform,
alpha: 0,
ease: 'bounce',
duration: GameOptions.disappearTime,
callbackScope: this,
onComplete: function() {
// reset the platform
this.resetPlatform(platform);
}
});
}
break;
// bouncy platform
case 2:
// make the hero jump changing vertical velocity
hero.setVelocityY(GameOptions.bounceVelocity * -1);
break;
}
}
}
// method to reset a platform
// argument: the platform
resetPlatform(platform: PlatformSprite): void {
// recycle the platform
platform.initialize();
// place stuff on platform
this.placeStuffOnPlatform(platform);
}
// method to handle collisions between enemies and platforms
// arguments: the two colliding bodies
handleEnemyPlatformCollision(body1: Phaser.GameObjects.GameObject, body2: Phaser.GameObjects.GameObject): void {
// first body is the enemy
let enemy: EnemySprite = body1 as EnemySprite;
// second body is the platform
let platform: PlatformSprite = body2 as PlatformSprite;
// set the platform to patrol
enemy.platformToPatrol = platform;
}
// method to be executed at each frame
update(): void {
// if the hero is already moving...
if (!this.firstMove) {
// scroll a bit the background texture
this.backgroundImage.tilePositionY += 0.2;
}
// move the hero
this.hero.move();
// handle collision between hero and platforms
this.physics.world.collide(this.hero, this.platformGroup, this.handlePlatformCollision, undefined, this);
// handle collision between enemies and platforms
this.physics.world.collide(this.enemyGroup, this.platformGroup, this.handleEnemyPlatformCollision, undefined, this);
// handle collisions between hero and enemies
this.physics.world.collide(this.hero, this.enemyGroup, this.handleEnemyCollision, undefined, this);
// get all platforms
let platforms: PlatformSprite[] = this.platformGroup.getChildren() as PlatformSprite[];
// loop through all platforms
for (let platform of platforms) {
// get platform bounds
let platformBounds: Phaser.Geom.Rectangle = platform.getBounds();
// if a platform leaves the stage to the upper side...
if (platformBounds.bottom < 0) {
// reset the platform
this.resetPlatform(platform);
}
}
// get all enemies
let enemies: EnemySprite[] = this.enemyGroup.getChildren() as EnemySprite[];
// update debug text
this.debugText.setText("Enemies in group: " + enemies.length.toString() + "\nEnemies in pool: " + this.enemyPool.length.toString());
// loop through all enemies in enemyPool array
for (let enemy of this.enemyPool) {
// if the enemy falls down the stage...
if (enemy.y > GameOptions.gameSize.height + 100) {
// set enemy velocity to zero
enemy.setVelocity(0 ,0);
// do not let enemy to be affacted by gravity
enemy.body.setAllowGravity(false);
}
}
// loop through all enemies
for (let enemy of enemies) {
// make enemy patrol
enemy.patrol();
// get enemy bounds
let enemyBounds: Phaser.Geom.Rectangle = enemy.getBounds();
// if the enemy leaves the screen...
if (enemyBounds.bottom < 0) {
// move enemy from enemy group to enemy pool
enemy.groupToPool(this.enemyGroup, this.enemyPool);
// do not show the enemy
enemy.setVisible(false);
}
}
// if the hero falls down or leaves the stage from the top...
if (this.hero.y > GameOptions.gameSize.height || this.hero.y < 0) {
// restart the scene
this.scene.start("PlayGame");
}
}
}
playerSprite.ts
Class to define the player Sprite, the main actor of the game, the one players control.
// PLAYER SPRITE CLASS
// modules to import
import { GameOptions } from './gameOptions';
// player sprite extends Arcade Sprite class
export default class PlayerSprite extends Phaser.Physics.Arcade.Sprite {
// assign LEFT property a -1 value, just like if it were a constant
LEFT: number = -1;
// assign RIGHT property a 1 value, just like if it were a constant
RIGHT: number = 1;
// assign STOP property a 0 value, just like if it were a constant
STOP: number = 0;
// player current movment is STOP
currentMovement: number = this.STOP;
// constructor
// argument: game scene
constructor(scene: Phaser.Scene) {
super(scene, GameOptions.gameSize.width / 2, GameOptions.gameSize.height * GameOptions.firstPlatformPosition - 100, 'hero');
// add the player to the scnee
scene.add.existing(this);
// add physics body to platform
scene.physics.add.existing(this);
// set player scale
this.scale = GameOptions.pixelScale;
// shrink a bit player pyhsics body size to make the game forgive players a bit
this.body.setSize(this.displayWidth / GameOptions.pixelScale * 0.6, this.displayHeight / GameOptions.pixelScale * 0.7, false);
// set player physics body offset. This has to be done manually, no magic formula
this.body.setOffset(7, 9);
}
// method to set player movement
// argument: the new movement
setMovement(n: number): void {
// set currentMovement to n
this.currentMovement = n;
}
// method to move the player
move(): void {
// set player horizontal velocity according to currentMovement value
this.setVelocityX(GameOptions.heroSpeed * this.currentMovement);
// various cases according to player movememt
switch (this.currentMovement) {
// player is moving left
case this.LEFT:
// flip sprite horizontally
this.setFlipX(true);
// play "run" animation
this.anims.play('run', true);
break;
// player is moving right
case this.RIGHT:
// do not flip sprite horizontally
this.setFlipX(false);
// play "run" animation
this.anims.play('run', true);
break;
// player is not moving
case this.STOP:
// play "idle" animation
this.anims.play('idle', true);
break;
}
}
}
platformSprite.ts
Class to define the platforms.
// PLATFORM SPRITE CLASS
// modules to import
import PlatformGroup from './platformGroup';
import { GameOptions } from './gameOptions';
import { randomValue } from './utils';
// platform sprite extends RenderTexture class
export default class PlatformSprite extends Phaser.GameObjects.RenderTexture {
// platform physics body
body: Phaser.Physics.Arcade.Body;
// platform type
platformType: number = 0;
// is the platform fading out?
isFadingOut: Boolean = false;
// platform group
platformGroup: PlatformGroup;
// the three sprites forming the platform: left edge, middle, and right edge
leftSprite: Phaser.GameObjects.Sprite;
middleSprite: Phaser.GameObjects.Sprite;
rightSprite: Phaser.GameObjects.Sprite;
// constructor
// arguments: the game scene, the platform group, sprite for left edge, sprite for the middle, sprite for the right edge
constructor(scene: Phaser.Scene, group: PlatformGroup, leftSprite: Phaser.GameObjects.Sprite, middleSprite: Phaser.GameObjects.Sprite, rightSprite: Phaser.GameObjects.Sprite) {
super(scene, 0, 0, 1, 16);
// set left, middle and right sprites
this.leftSprite = leftSprite;
this.middleSprite = middleSprite;
this.rightSprite = rightSprite;
// RenderTexture object does not have default origin at 0.5, so we need to set it
this.setOrigin(0.5);
// add the platform to the scnee
scene.add.existing(this);
// add physics body to platform
scene.physics.add.existing(this);
// add the platform to group
group.add(this);
// platform body does not react to collisions
this.body.setImmovable(true);
// platform body is not affected by gravity
this.body.setAllowGravity(false);
// save platform group
this.platformGroup = group;
// let's initialize the platform, with random position, size and so on
this.initialize();
// set platform scale
this.scale = GameOptions.pixelScale;
}
// method to initialize the platform
initialize(): void {
// platform is not fading out
this.isFadingOut = false;
// platform alpha is set to fully opaque
this.alpha = 1;
// get lowest platform Y coordinate
let lowestPlatformY: number = this.platformGroup.getLowestPlatformY();
// is lowest platform Y coordinate zero? (this means there are no platforms yet)
if (lowestPlatformY == 0) {
// position the first platform
this.y = GameOptions.gameSize.height * GameOptions.firstPlatformPosition;
this.x = GameOptions.gameSize.width / 2;
}
else {
// position the platform
this.y = lowestPlatformY + randomValue(GameOptions.platformVerticalDistanceRange);
this.x = GameOptions.gameSize.width / 2 + randomValue(GameOptions.platformHorizontalDistanceRange) * Phaser.Math.RND.sign();
// set a random platform type
this.platformType = 0//Phaser.Math.Between(0, 2);
}
// platform width
let newWidth: number = randomValue(GameOptions.platformLengthRange) / GameOptions.pixelScale;
// set platform size
this.setSize(newWidth, 16);
// set platform body size
this.body.setSize(newWidth, 16);
// set middle sprite display width equal to entire platform width
this.middleSprite.displayWidth = newWidth;
// draw middle sprite
this.draw(this.middleSprite, 0, 0);
// draw left edge sprite
this.draw(this.leftSprite, 0, 0);
// draw right edge sprite
this.draw(this.rightSprite, newWidth, 0);
}
}
platformGroup.ts
Class to define the Phaser Group, dedicated to the group which contains all platforms.
// PLATFORM GROUP CLASS
// modules to import
import PlatformSprite from "./platformSprite";
// platform group extends Arcade Group class
export default class PlatformGroup extends Phaser.Physics.Arcade.Group {
// constructor
// arguments: the physics world, the game scene
constructor(world: Phaser.Physics.Arcade.World, scene: Phaser.Scene) {
super(world, scene);
}
// method to get the lowest platform
getLowestPlatformY(): number {
// lowest platform value is initially set to zero
let lowestPlatformY: number = 0;
// get all group children
let platforms: PlatformSprite[] = this.getChildren() as PlatformSprite[];
// loop through all platforms
for (let platform of platforms) {
// get the highest value between lowestPlatform and platform y coordinate
lowestPlatformY = Math.max(lowestPlatformY, platform.y);
};
// return lowest platform coordinate
return lowestPlatformY;
}
}
enemySprite.ts
The class to define the patrolling enemy.
// ENEMY SPRITE CLASS
// modules to import
import EnemyGroup from "./enemyGroup";
import PlatformSprite from "./platformSprite";
import { randomValue } from './utils';
import { GameOptions } from './gameOptions';
// enemy sprite extends Arcade Sprite class
export default class EnemySprite extends Phaser.Physics.Arcade.Sprite {
// the platform where the enemy is patrolling
platformToPatrol: PlatformSprite;
// enemy physics body
body: Phaser.Physics.Arcade.Body;
// constructor
// arguments: the game scene, the platform where the enemy is on, and enemy group
constructor(scene: Phaser.Scene, platform: PlatformSprite, group: EnemyGroup) {
super(scene, platform.x, platform.y - 100, 'enemy');
// add the platform to the scnee
scene.add.existing(this);
// add physics body to platform
scene.physics.add.existing(this);
// set enemy scale
this.scale = GameOptions.pixelScale;
// shrink a bit enemy pyhsics body size to make the game forgive players a bit
this.body.setSize(this.displayWidth / GameOptions.pixelScale * 0.6, this.displayHeight / GameOptions.pixelScale * 0.75, true);
// set enemy physics body offset. This has to be done manually, no magic formula
this.body.setOffset(7, 7)
// the enemy is patrolling the current platform
this.platformToPatrol = platform;
// add the enemy to the group
group.add(this);
// set enemy horizontal speed
this.setVelocityX(randomValue(GameOptions.enemyPatrolSpeedRange) * Phaser.Math.RND.sign());
// play "enemy_run" animation
this.anims.play('enemy_run', true);
}
// method to make the enemy patrol a platform
patrol(): void {
// flip enemy sprite if moving right
this.setFlipX(this.body.velocity.x > 0);
// get platform bounds
let platformBounds: Phaser.Geom.Rectangle = this.platformToPatrol.getBounds();
// get enemy bounds
let enemyBounds: Phaser.Geom.Rectangle = this.getBounds();
// get enemy horizontal speeds
let enemyVelocityX: number = this.body.velocity.x
// if the enemy is moving left and is about to fall down the platform to the left side
// or the enemy is moving right and is about to fall down the platform to the right side
if ((platformBounds.right + 25 < enemyBounds.right && enemyVelocityX > 0) || (platformBounds.left - 25 > enemyBounds.left && enemyVelocityX < 0)) {
// invert enemy horizontal speed
this.setVelocityX(enemyVelocityX * -1);
}
}
// method to remove the enemy from a group and place it into the pool
// arguments: the group and the pool
groupToPool(group: EnemyGroup, pool: EnemySprite[]): void {
// remove enemy from the group
group.remove(this);
// push the enemy in the pool
pool.push(this);
}
// method to remove the enemy from the pool and place it into a group
// arguments: the platform to patrol and the group
poolToGroup(platform: PlatformSprite, group: EnemyGroup): void {
// set the platform to patrol
this.platformToPatrol = platform;
// place the enemy in the center of the platform
this.x = platform.x;
// place the enemy a little above the platform
this.y = platform.y - 120;
// set the enemy visible
this.setVisible(true);
// add the enemy to the group
group.add(this);
// allow gravity to affect the enemy
this.body.setAllowGravity(true);
// set enemy horizontal speed
this.setVelocityX(randomValue(GameOptions.enemyPatrolSpeedRange) * Phaser.Math.RND.sign());
// play "enemy_run" animation
this.anims.play('enemy_run', true);
// do not vertically flip enemy sprite
this.setFlipY(false);
}
}
enemyGroup.ts
Actually this class to extend the Phaser Group which contains all enemies does not add any custom feature, but I preferred to create a custom class like I did with platformGroup.ts
.
// ENEMY GROUP CLASS
// enemy group extends Arcade Group class
export default class EnemyGroup extends Phaser.Physics.Arcade.Group {
// constructor
// arguments: the physics world, the game scene
constructor(world: Phaser.Physics.Arcade.World, scene: Phaser.Scene) {
super(world, scene);
}
}
utils.ts
This file contains only one custom function, but I thought it was useful to group all custom functions in a separate file, to be reused whenever I need them.
// function to toss a random value between two elements in an array
// argument: an array with two items
export function randomValue(a: number[]): number {
// return a random integer between the first and the second item of the array
return Phaser.Math.Between(a[0], a[1]);
}
It would be nice to add another enemy type, then release the game and see if people like it. How would you improve it? Download the source code of the entire project.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.