Loading deck

GAMEDEV WITH PHASER

Part 2 - Building Games with the Phaser Framework

Who we are

we love making useful apps
creating fun games
and training great developers

What we will build

basic platformer mechanics

with a level designer

Doodlejump clone

Start with a fork

https://github.com/gomagames/Phaser-Jump

tour the code

look at index.html  main.js

and configuration.js

set up the game's main file

initialize module properties

we'll initialize now and set these later

  Game.hero = null;
  Game.platformsGroup = null;
  Game.cursors = null;

Game preload

have just one texture atlas - preview it

  const preload = _ => {
    game.load.atlasJSONHash(
      CFG.ASSETS.GFX, 
      CFG.ASSETS.ATLAS_PNG_PATH, 
      CFG.ASSETS.ATLAS_JSON_PATH
    );
  };

Game create

initialize all the game elements

  const create = _ => {
    game.physics.startSystem(Phaser.Physics.ARCADE);
    game.physics.arcade.gravity.y = CFG.GRAVITY;
    game.stage.backgroundColor = CFG.BG_COLOR;

    Game.cursors = game.input.keyboard.createCursorKeys();

    Game.platformsGroup = game.add.group();
    Game.LevelDesigner.load(game, 1);

    Game.hero = new Game.Hero(game, 500, CFG.GAME_HEIGHT - 200);
    game.camera.follow(hero.sprite, null, CFG.CAMERA_LERP, CFG.CAMERA_LERP);

  };

Hero class

define a class

attach it to the Game module

do not copy and paste these class definitions

((Phaser, Game, CFG) => {
  // get or create Game module
  if( Game === undefined ){
    Game = window.Game = {};
  }

  Game.Hero = class{
    constructor(){

    }
  };

})(window.Phaser, window.Game, window.Game.Configuration);

Import the class

source file

<script src="./js/configuration.js"></script>
<script src="./js/LevelDesigner.js"></script>

<script src="./js/main.js"></script>

HERE

initialize constants

in the Hero Module

  const SCALE = 1;
  const MOVE_SPEED = 850;
  const JUMP_VELOCITY = 2950;
  // also height, gravity:9750 jumpVel:2950 = can clear 400px
  const ANIMATIONS = {
    IDLE_SPEED : 8,
    LEFT_SPEED : 8,
    RIGHT_SPEED : 8,
    JUMP_SPEED : 4,
  };

constructor

this.game = game;
this.sprite = this.game.add.sprite(x, y, CFG.ASSETS.GFX);
this.sprite.scale.set(SCALE);
this.game.physics.enable(this.sprite, Phaser.Physics.ARCADE);
this.sprite.body.setSize(30, 80, 52, 25);
this.animations = {
  idle : this.sprite.animations.add('idle', [ 'hero-idle-1.png', 'hero-idle-2.png' ]),
  left : this.sprite.animations.add('left', [ 'hero-left-1.png', 'hero-left-2.png' ]),
  right : this.sprite.animations.add('right', [ 'hero-right-1.png', 'hero-right-2.png' ]),
  jump : this.sprite.animations.add('jump', [ 'hero-jump-2.png', 'hero-jump-1.png' ]),
};

// allows passing through platforms
this.sprite.body.checkCollision.up =
this.sprite.body.checkCollision.left =
this.sprite.body.checkCollision.right = false;

this.sprite.update = this.update.bind(this);
constructor(game, x, y)

update loop

update(){
  let hitPlatform = this.game.physics.arcade.collide(this.sprite, Game.platformsGroup);
  let jumping = this.sprite.body.velocity.y !== 0;

  this.sprite.body.velocity.x = 0;
  if (Game.cursors.left.isDown) {
    this.sprite.body.velocity.x = -MOVE_SPEED;
    if(!jumping && this.sprite.animations.currentAnim !== this.animations.left){
      this.animations.left.play(ANIMATIONS.LEFT_SPEED, true);
    }
  } else if (Game.cursors.right.isDown) {
    this.sprite.body.velocity.x = MOVE_SPEED;
    if(!jumping && this.sprite.animations.currentAnim !== this.animations.right){
      this.animations.right.play(ANIMATIONS.RIGHT_SPEED, true);
    }
  } else if(!jumping){
    //  Stand still
    if(this.sprite.animations.currentAnim !== this.animations.idle){
      this.animations.idle.play(ANIMATIONS.IDLE_SPEED, true);
    }
  }

  //  Allow the player to jump if they are touching the ground.
  if (Game.cursors.up.isDown && this.sprite.body.touching.down && hitPlatform)
  {
    this.sprite.body.velocity.y = -JUMP_VELOCITY;
    if(this.sprite.animations.currentAnim !== this.animations.jump){
      this.animations.jump.play(ANIMATIONS.JUMP_SPEED, false);
    }
  }

  // wrap around stage
  if(this.sprite.x > CFG.GAME_WIDTH){
    this.sprite.x = -this.sprite.width;
  } else if(this.sprite.x < -this.sprite.width){
    this.sprite.x = CFG.GAME_WIDTH;
  }

}

Platform class

Define the platform class

import the source file

initialize constants

  const SCALE = 1;
  const ATLAS_LABELS = {
    1 : 'platform-1.png',
    2 : 'platform-2.png',
    3 : 'platform-3.png',
    4 : 'platform-4.png',
  };

constructor

/*
 * y is the height from the BOTTOM of the stage
 * only the top edge should be collidable
 *
 */
constructor(game, x, y, size){
  if(size < 1 || size > 4){
    throw new RangeError('Platform size must be 1-4');
  }
  this.game = game;
  this.sprite = this.game.add.sprite(x, CFG.GAME_HEIGHT - y, CFG.ASSETS.GFX, ATLAS_LABELS[size]);
  this.sprite.scale.set(SCALE);
  this.game.physics.enable(this.sprite, Phaser.Physics.ARCADE);
  this.sprite.body.allowGravity = false;
  this.sprite.body.immovable = true;
}
constructor(game, x, y, size)

Enemy Superclass

define the enemy class

import the source file

constants

  const SCALE = 1;

constructor

this.game = game;
this.sprite = this.game.add.sprite(x, CFG.GAME_HEIGHT - y, CFG.ASSETS.GFX, spriteLabel);
this.sprite.scale.set(SCALE);
this.game.physics.enable(this.sprite, Phaser.Physics.ARCADE);

// allows passing through platforms
// #TODO this will be bad for checking other objects though
this.sprite.body.checkCollision.up =
this.sprite.body.checkCollision.left =
this.sprite.body.checkCollision.right = false;

this.sprite.update = this.update.bind(this);
constructor(game, x, y, spriteLabel)

update loop

update(){
  let hitPlatform = this.game.physics.arcade.collide(this.sprite, Game.platformsGroup);

  // wrap around stage
  if(this.sprite.x > CFG.GAME_WIDTH){
    this.sprite.x = -this.sprite.width;
  } else if(this.sprite.x < -this.sprite.width){
    this.sprite.x = CFG.GAME_WIDTH;
  }

}

Ghost enemy subclass

extends enemy class

Define the ghost enemy subclass

import the source file

constants

  const SPAWN_Y_OFFSET = 97;

constructor

    constructor(game, x, y){
      super(game, x, y + SPAWN_Y_OFFSET, 'baddie-2.png');
      this.sprite.body.setSize(5, 72, 100, 22);
    }

spark enemy subclass

extends enemy class

Define the spark enemy subclass

import the source file

constants

  const SPAWN_Y_OFFSET = 103;

constructor

    constructor(game, x, y){
      super(game, x, y + SPAWN_Y_OFFSET, 'baddie-1.png');
      this.sprite.body.setSize(15, 75, 75, 27);
    }

review leveldesigner module

get the basic gist of what each function does

what does this module provide?

how do you use this module?

Playtesting

left, right

up => jump

What you will build

PUT ON YOUR DEV HATS

one way of learning new tech

is to take something that works, continue the patterns, experiment by extending

what you know that works

let the hero spit pellets

should look like this

see /assets/pngs/ directory to find the asset name

let the hero spit pellets

start with spawning ammo

 

see if you can follow the patterns in the LevelDesigner module

  const levels = {
    1 :
`-----------------------------|
                              |
                              |
                              |
                              |
___o___     ___o___    ___o___|
                              |
                              |
                              |
                              |
     _______                  |

spawn 3 ammo pellets using 'o' character

collect ammo pellets

should look like this

collect ammo pellets

collision detection from pt. 1

 

follow the patterns in the first Phaser Workshop

you'll need this for building a ui

to stick to the camera

fire pellets

should look like this

fire pellets

follow the patterns in the first Phaser Workshop

hint

after creating the "fire" key
in main.js create()

Game.cursors.fire.onUp.add( Game.hero.handleFire.bind(Game.hero) );

destroy baddies

should look like this

destroy baddies

follow the patterns in the first Phaser Workshop

track enemies in a sprite group

    // keep track of enemies
    Game.enemiesGroup = game.add.group();

in main.js create()

in LevelDesigner.js create()

switch(SpawnableClass){
  case Game.GhostEnemy:
    Game.enemiesGroup.add(new Game.GhostEnemy(game, x, y).sprite);
    break;
  case Game.SparkEnemy:
    Game.enemiesGroup.add(new Game.SparkEnemy(game, x, y).sprite);
    break;
  case Game.Ammo: new Game.Ammo(game, x, y); break;
}

baddies hurt - game over

should look like this

baddies hurt - game over

follow the patterns in the first Phaser Workshop

main.js  update()

you do the rest

  const update = _ => {
    if( Game.enemiesGroup.children.some( enemySprite =>
          enemySprite.overlap(Game.hero.sprite))
    ){
      gameOver();
    }
  };

support touch devices

touch controls ?

add touch controls

  const handlePointerPressed = pointer => {
    console.log(pointer.clientX,pointer.clientY);
  };
    game.input.onTap.add( handlePointerPressed.bind(this) );

in game.js create()

in game.js

you do the math