Set

PHASERS

to

JAVASCRIPT !!!

Jesse Harlin
@5imian

PHASERS

PHASERS

PHASERS

JAVASCRIPT !!!

JAVASCRIPT !!!

Or rather....

Phaser.js

and

The Rest of the Owl

Hi, I'm Jesse!

HIRE ME

React

React-Native

Data Viz

Mobile Apps

CI

Whizbang in your browser

info@

  simiancraft

      .com

I program a lot

My favorite people are Ada, Dorian and Amanda

sometimes I compose music and I also like synthesizers

Over the years I've use a lot of js game engines

How to make a game

1. Check out cool demos

2. Now make the rest of the game

It always feels like this...

Lately i've been using this...

Phaser has the important stuff.

and  the fancy stuff.

Scene Management...

import Phaser from 'phaser/src/phaser.js';

export default class sceneTemplate extends Phaser.Scene {
  constructor() {
    super({ key: 'Game' });
  }
  preload() {} //get assets
  create() {} //put assets in scene
  update() {} //called every game update
  render() {} //called every draw
}
//switch to a scene
this.scene.start('SomeScene');

Basic Scene Management...

const config = {
  type: Phaser.AUTO,
  width: constants.WIDTH,
  height: constants.HEIGHT,
  physics: {
    default: 'arcade', //<---------------Physics, turn on!!
    arcade: {
      gravity: { y: 400 },
      debug: false
    }
  },
  scene: [StartScene, GameScene],
  pixelArt: true,
  antialias: false,
  callbacks: {
    postBoot: game => {
      game.renderer.addPipeline('Custom', new CustomPipeline(game));
    }
  }
};

const game = new Phaser.Game(config);

window.game = game;

Physics..

Cameras...

 create() {
    this.createBackgrounds(SCALE);
    //create Level

    this.createStaticLayers();

    this.createCamera(); /// <---------------Make cameras in create!; 
    this.handleDebugging();
    this.player.create();
  }

createCamera() {
    this.cameras.main.startFollow(this.player); //<--------------- Follows Player
    this.cameras.main.setBounds(
      0,
      0,
      this.map.widthInPixels,
      this.map.heightInPixels
    );
    this.cameras.main.setBackgroundColor('#111111');
}

Animation...

function preload () {
    this.load.spritesheet(
        'muybridge', 
        'assets/animations/muybridge01.png', 
        { frameWidth: 119, frameHeight: 228 }
    );
}

function create () {
    var config = {
        key: 'run',
        frames: 'muybridge',
        frameRate: 15,
        repeat: -1
    };

    this.anims.create(config);
    group = this.add.group();
    group.createMultiple({ key: 'muybridge', frame: 0, repeat: 8 });
    Phaser.Actions.GridAlign(group.getChildren(), {
        width: 9,
        height: 1,
        cellWidth: 119,
        y: 170
    });
    this.anims.staggerPlay('run', group.getChildren(), -100);
}

Particles...

function preload () {
    this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
}

function create () {
    var particles = this.add.particles('flares');

    var well = particles.createGravityWell({
        x: 400,
        y: 300,
        power: 3,
        epsilon: 100,
        gravity: 100
    });

    var emitter = particles.createEmitter({
        frame: [ 'red', 'green' ],
        x: 600,
        y: 400,
        lifespan: 4000,
        speed: 200,
        scale: { start: 0.7, end: 0 },
        blendMode: 'ADD'
    });
}

So now what?

1.

1.A

Make GameWorlds Quickly

Tilemap

{
    "height": 34,
    "infinite": false,
    "layers": [
        {
            "image": "background-with-planets.png",
            "name": "BackgroundGradient",
            "offsetx": 16,
            "offsety": 16,
            "opacity": 1,
            "properties": { "fixed": true, "role": "background" },
            "propertytypes": { "fixed": "bool", "role": "string" },
            "type": "imagelayer",
            "visible": true,
            "x": 0,
            "y": 0
        },

Edit level

Export Json

Load Tilemap

And Level.json

in Phaser

This Tileset can make this level

createStaticLayers() {
  this.map = this.make.tilemap({ key: 'level-0' });
  const tiles = this.map.addTilesetImage('rock-moss-plants-doors', 'tiles');
  const tilemapLayers = _.filter(level.layers, layer => {
    return layer.type === 'tilelayer';
  });

  this.tilemapLayers = {};
  _.each(tilemapLayers, tilemapLayer => {
    let layerName = _.camelCase(tilemapLayer.name);

    this.tilemapLayers[layerName] = this.map.createStaticLayer(
      tilemapLayer.name,
      tiles,
      0,
      0
    );
    if (tilemapLayer.properties) {
      if (tilemapLayer.properties.collision) {
        this.tilemapLayers[layerName].setCollisionBetween(0, 999);
        this.physics.add.collider(this.player, this.tilemapLayers[layerName]);
      }
    }
  });
}

Level Generation

via

Binary space Parition Tree

Gif from

Level Generator by the-simian

Tiled Automapper

Layer 1

Layer 2

Layer 3

Input Tiles

INPUT

OUTPUT

Closeup

Automapped Level

Taking Control

   if (this.cursors.left.isDown) {
        this.body.velocity.x = -this.walking_speed;
    } else if (this.cursors.right.isDown) {
        this.body.velocity.x = this.walking_speed;
    } else {
        this.body.velocity.x = 0;
    }
  }

1. Do like the Tutorial Do.

2. Idle? Jump? ...ing?

this.cursors = this.input.keyboard.createCursorKeys();

if (this.cursors.left.isDown) {
  this.player.setVelocityX(-160);
  this.player.anims.play('left-walk', true);
  this.player.direction = 'left';
} else if (this.cursors.right.isDown) {
  this.player.setVelocityX(160);
  this.player.anims.play('right-walk', true);
  this.player.direction = 'right';
} else {
  this.player.setVelocityX(0);
  this.player.anims.play(`${this.player.direction}-idle`, true);
}

if (this.cursors.space.isDown && this.player.body.onFloor()) {
  this.player.setVelocityY(-350);
  this.player.anims.play(`${this.player.direction}-crouchjump`, true);
}
  

Hovering?

Sliding?

Turning?

Shooting

Shooting and walking at the same time?

3.

const { direction, movementState } = this.player;
this.cursors = this.input.keyboard.createCursorKeys();

if (this.cursors.left.isDown) {
  if (direction === 'left') {
    this.player.direction = 'left';
    this.player.movementState = 'walk';
    this.player.setVelocityX(-this.speeds.walking);
  } else if (direction === 'right') {
    this.player.direction = 'right2left';
    const way = Math.round(Math.random()) ? 'front' : 'back';
    this.player.movementState = `walkturn-${way}`;
    this.player.setVelocityX(0);
  }
} else if (this.cursors.right.isDown) {
  if (direction === 'right') {
    this.player.direction = 'right';
    this.player.movementState = 'walk';
    this.player.setVelocityX(this.speeds.walking);
  } else if (direction === 'left') {
    this.player.direction = 'left2right';
    const way = Math.round(Math.random()) ? 'front' : 'back';
    this.player.movementState = `walkturn-${way}`;
    this.player.setVelocityX(0);
  }
} else {
  this.player.setVelocityX(0);
  this.player.movementState = 'idle';
}





if (this.cursors.down.isDown && this.player.body.onFloor()) {
  if (this.player.movementState.indexOf('crouch' === -1)) {
    this.player.movementState = 'crouch-updwn';
  }
}

if (this.cursors.space.isDown && this.player.body.onFloor()) {
  if (this.player.movementState.indexOf('crouch' > -1)) {
    this.player.movementState = 'crouchjump';
    this.player.setVelocityY(-350);
  }
}

this.setaAnimation();

animcomplete(animation, frame) {
const { key } = animation;
if (key.indexOf('right2left-walkturn') > -1) {
  this.player.direction = 'left';
  this.player.movementState = 'walk';
}

if (key.indexOf('left2right-walkturn') > -1) {
  this.player.direction = 'right';
  this.player.movementState = 'walk';
}
  
setaAnimation() {
 const { direction, movementState } = this.player;
 this.player.anims.play(`${direction}-${movementState}`, true);
}
  

Oh  no...

Input

Behavior

Animation

Animation

[
{ "name": "left-aerial", "frames": 1 },
{ "name": "left-crouch", "frames": 1 },
{ "name": "left-crouch-dwn2up", "frames": 5 },
{ "name": "left-crouch-up2dwn", "frames": 5 },
{ "name": "left-crouchjump", "frames": 4 },
{ "name": "left-crouchjumpprep", "frames": 4 },
{ "name": "left-damage", "frames": 5 },
{ "name": "left-die", "frames": 59 },
{ "name": "left-firecannon-dwn", "frames": 7, "repeat": -1 },
{ "name": "left-firecannon-dwnfwd", "frames": 7, "repeat": -1 },
{ "name": "left-firecannon-fwd", "frames": 7, "repeat": -1 },
{ "name": "left-firecannon-up", "frames": 7, "repeat": -1 },
{ "name": "left-firecannon-upfwd", "frames": 7, "repeat": -1 },
{ "name": "left-firecannonwalk-dwn", "frames": 23, "repeat": -1 },
{ "name": "left-firecannonwalk-dwnfwd", "frames": 23, "repeat": -1 },
{ "name": "left-firecannonwalk-fwd", "frames": 23, "repeat": -1 },
{ "name": "left-firecannonwalk-up", "frames": 24, "repeat": -1 },
{ "name": "left-firecannonwalk-upfwd", "frames": 23, "repeat": -1 },
{ "name": "left-firemissile", "frames": 6 },
{ "name": "left-firemissile-fire2stand", "frames": 6 },
{ "name": "left-firemissile-stand2fire", "frames": 11 },
{ "name": "left-idle", "frames": 47, "repeat": -1 },
{ "name": "left-slide-slide2stand", "frames": 3 },
{ "name": "left-slide-stand2slide", "frames": 8 },
{ "name": "left-walk", "frames": 23, "repeat": -1 },
{ "name": "left2right-aerial", "frames": 5 },
{ "name": "left2right-walkturn-back", "frames": 12 },
{ "name": "left2right-walkturn-front", "frames": 12 },
{ "name": "right-aerial", "frames": 1 },
{ "name": "right-crouch", "frames": 1 },
{ "name": "right-crouch-dwn2up", "frames": 6 },
{ "name": "right-crouch-up2dwn", "frames": 5 },
{ "name": "right-crouchjump", "frames": 4 },
{ "name": "right-crouchjumpprep", "frames": 4 },
{ "name": "right-damage", "frames": 5 },
{ "name": "right-die", "frames": 59 },
{ "name": "right-firecannon-dwn", "frames": 7, "repeat": -1 },
{ "name": "right-firecannon-dwnfwd", "frames": 7, "repeat": -1 },
{ "name": "right-firecannon-fwd", "frames": 7, "repeat": -1 },
{ "name": "right-firecannon-up", "frames": 7, "repeat": -1 },
{ "name": "right-firecannon-upfwd", "frames": 7, "repeat": -1 },
{ "name": "right-firecannonwalk-dwn", "frames": 23, "repeat": -1 },
{ "name": "right-firecannonwalk-dwnfwd", "frames": 23, "repeat": -1 },
{ "name": "right-firecannonwalk-fwd", "frames": 23, "repeat": -1 },
{ "name": "right-firecannonwalk-up", "frames": 23, "repeat": -1 },
{ "name": "right-firecannonwalk-upfwd", "frames": 23, "repeat": -1 },
{ "name": "right-firemissile", "frames": 5 },
{ "name": "right-firemissile-fire2stand", "frames": 6 },
{ "name": "right-firemissile-stand2fire", "frames": 12 },
{ "name": "right-idle", "frames": 47, "repeat": -1 },
{ "name": "right-slide-slide2stand", "frames": 3 },
{ "name": "right-slide-stand2slide", "frames": 8 },
{ "name": "right-walk", "frames": 23, "repeat": -1 },
{ "name": "right2left-aerial", "frames": 5 },
{ "name": "right2left-walkturn-back", "frames": 12 },
{ "name": "right2left-walkturn-front", "frames": 12 }
]

Input

if (onFloor) {
  if (dirLeft.isDown) {
    behaviors.handle('walk', {direction: 'left'});
  } else if (dirRight.isDown) {
    behaviors.handle('walk', {direction: 'right'});
  }

  if (missiles.isDown && this.missileCount > 0) {
    behaviors.handle('shootMissiles');
  }

  if (dirDown.isDown) {
    behaviors.handle('crouch');
  } else if (!dirDown.isDown) {
    behaviors.handle('uncrouch');
  }

  if (jump.isDown) {
    behaviors.handle('jump');
  }

  if (boost.isDown) {
    behaviors.handle('boost');
  } else if (!boost.isDown) {
    behaviors.handle('unboost');
  }

  if (this.hasNoInput()) {
    behaviors.handle('idle');
  }

  behaviors.handle('land');
}

if (!onFloor) {
  if (dirLeft.isDown) {
    behaviors.handle('veer', {direction: 'left'});
  } else if (dirRight.isDown) {
    behaviors.handle('veer', {direction: 'right'});
  }

  if (boost.isDown) {
    behaviors.handle('boost');
  } else if (!boost.isDown) {
    behaviors.handle('unboost');
  }

  behaviors.handle('unland', { onFloor, velocities });
}

Behavior

states: {
    idling: {
      _child: directions,
      _onEnter: function() {},
      walk: function(data) {},
      crouch: function() {},
      jump: function(data) {},
      shootMissiles: 'missilesLaunch',
      boost: function({ velocities }) {},
      shoot: function(data) {}
    },
    walking: {
      _child: directions,
      _onEnter: function() {},
      idle: function(data) {},
      walk: function(data) {},
      turn: function() {},
      crouch: function() {},
      jump: function(data) {},
      unland: function() {},
      shootMissiles: 'missilesLaunch',
      boost: function({ velocities }) {},
      shoot: function(data) {}
    },
    turning: {
      _child: directions,
      _onEnter: function() {},
      walk: function(data) {},
      shoot: function() {}
    },
    crouchingDown: {},
}

Behavior

Finite State Machine

Behavior

Walk Left   

Slide Left   

Jump Left   

Turning

Walk Right 

Slide Right  

Jump Right

states: {
    idling: {
      _child: directions,
      _onEnter: function() {},
      walk: function(data) {},
      crouch: function() {},
      jump: function(data) {},
      shootMissiles: 'missilesLaunch',
      boost: function({ velocities }) {},
      shoot: function(data) {}
    },
    walking: {
      _child: directions,
      _onEnter: function() {},
      idle: function(data) {},
      walk: function(data) {},
      turn: function() {},
      crouch: function() {},
      jump: function(data) {},
      unland: function() {},
      shootMissiles: 'missilesLaunch',
      boost: function({ velocities }) {},
      shoot: function(data) {}
    },
    turning: {
      _child: directions,
      _onEnter: function() {},
      walk: function(data) {},
      shoot: function() {}
    },
    crouchingDown: {},
}

Behavior

Finite State Machine

HIERARCHICAL

Input

Behavior

Animation

Enemy Behavior

Animation

????

Timers?

Game State?

Player State?

Trained Neural Network ?

All the above ?

PHASER

QUICK TIPS

QUICK TIPS

QUICK TIPS

PHASER

Pixelart

Secret Handshake

html,body {    
    /* PIXELART PROPERTIES */
    filter: none;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-crisp-edges;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
    -ms-interpolation-mode: nearest-neighbor;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: -webkit-crisp-edges;
    image-rendering: -moz-crisp-edges;
    image-rendering: -o-crisp-edges;
    image-rendering: pixelated;
}
const config = {
  type: Phaser.AUTO,
  width: constants.WIDTH,
  height: constants.HEIGHT,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 400 },
      debug: false
    }
  },
  scene: [StartScene, GameScene],
  pixelArt: true, //<------------THIS
  antialias: false //<------------THAT
};

const game = new Phaser.Game(config);
window.game = game;

Game Config

CSS Config

Texturepackers

Use Them.

$39.99

FREE

Consider

Systems Music

  • Possibly Generative
  • Looping is no problem
  • Usually a Smaller footprint than recorded music
  • Can respond to game state!
    • tempo
    • tonal system
  • Check out Tone.js

Sytems Music Pros:

Sytems Music Cons:

  • Results may be unpredictable
  • Signifigantly harder to compose than through-composed music

Extrude Your

Tilemaps

npm install --global tile-extruder

tile-extruder --tileWidth 8 --tileHeight 16 --input ./in-tileset.png --output ./out-extruded.png

Use

Create Phaser App!!

Signal

Overgrowth

GRIFFIN

Webpack 4

Babel 7+

Es6 Support

Prettier Style

Bundle Analysis

Browsersync

Complexity Analysis

Tilemap Extrusion

Tiled Integration

Don't

Give Up

1. Start making a game.

2. Oh my this hard.

How to make a game : Real Feels™ Edition

3d-is-hard.gif

art_is_hard.jpg

Lets_upgrade_Phaser.gif

Hoot hoot.

Jesse Harlin
@5imian