Matthew Wilber
Full-Stack Web Developer. Building web sites & mobile apps for fun and profit! Free-time freelancer and UI/UX Engineer @ChartIQ
greenzeta.com
Combat 2077
Flash 4
Visionary Media 2000
Acme Agent Hunter
Flash 5
Warner Bros. 2003
Go Turtle Go!
Flash 8
Little Tikes 2011
Peanut Butter Art
EaselJS
Hormel 2015
Smashion
Phaser
VFiles 2016
Puppy Club
Phaser / Cordova
Mini Trading Cards Co. 2018
...the short version
First, there was Flash. And it was good...
But it required a plug-in.
Then HTML5 gave us <canvas>,
which had everything...
Except an easy way to use it.
This led to the creation of, many,
specialized frameworks.
Open Source HTML <canvas> game development framework that's "fast fun and free"
ECMA Script 2015 (ES6)
Webpack & Babel
Node Package Manager (npm)
Start with the GreenZeta Webpack Boilerplate
import 'phaser';
src/main.js
npm install --save phaser
CLI
github.com/mwilber/zeta-bros > Step1
import { GameScene } from './scenes/GameScene';
const gameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-game',
dom: {
createContainer: true
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 900 },
debug: false
}
},
scene: [
GameScene
]
};
new Phaser.Game(gameConfig);
src/main.js
github.com/mwilber/zeta-bros > Step2
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
}
update() {
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step3
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.player = this.createPlayer();
}
update() {
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step4
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.cursors = this.input.keyboard.createCursorKeys();
this.player = this.createPlayer();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
}else{
this.player.setVelocityX(0);
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step5
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.player = this.createPlayer();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 30,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 30
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 30,
repeat: -1
});
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step6
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.image('ground', 'assets/images/ground.png');
this.load.image('wall', 'assets/images/wall.png');
this.load.image('platform', 'assets/images/platform.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.platforms = this.createPlatforms();
this.player = this.createPlayer();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlatforms() {
let platforms = this.physics.add.staticGroup();
platforms.create(400, 270, 'platform');
platforms.create(400, 572, 'ground');
return platforms;
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 30,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 30
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 30,
repeat: -1
});
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step7
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.image('ground', 'assets/images/ground.png');
this.load.image('wall', 'assets/images/wall.png');
this.load.image('platform', 'assets/images/platform.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.platforms = this.createPlatforms();
this.player = this.createPlayer();
this.physics.add.collider(this.player, this.platforms);
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlatforms() {
let platforms = this.physics.add.staticGroup();
platforms.create(400, 270, 'platform');
platforms.create(400, 572, 'ground');
return platforms;
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 30,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 30
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 30,
repeat: -1
});
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step8
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.image('ground', 'assets/images/ground.png');
this.load.image('wall', 'assets/images/wall.png');
this.load.image('platform', 'assets/images/platform.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
this.load.image('bot', 'assets/images/security_bot.png')
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.platforms = this.createPlatforms();
this.player = this.createPlayer();
this.bots = this.physics.add.group();
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.bots, this.platforms);
this.spawnBot();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlatforms() {
let platforms = this.physics.add.staticGroup();
platforms.create(400, 270, 'platform');
platforms.create(400, 572, 'ground');
return platforms;
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 30,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 30
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 30,
repeat: -1
});
}
spawnBot(){
this.bots.create(100, 75, 'bot').setVelocityX(100);
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step9
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.image('ground', 'assets/images/ground.png');
this.load.image('wall', 'assets/images/wall.png');
this.load.image('platform', 'assets/images/platform.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
this.load.image('bot', 'assets/images/security_bot.png')
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.platforms = this.createPlatforms();
this.walls = this.createWalls();
this.player = this.createPlayer();
this.bots = this.physics.add.group();
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.bots, this.platforms);
this.physics.add.collider(this.bots, this.walls, this.handleCollisionWall.bind(this));
this.physics.add.collider(this.player, this.bots, this.handleCollisionEnemy.bind(this));
this.spawnBot();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createPlatforms() {
let platforms = this.physics.add.staticGroup();
platforms.create(400, 270, 'platform');
platforms.create(400, 572, 'ground');
return platforms;
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
createWalls() {
let walls = this.physics.add.staticGroup();
walls.create(0, 300, 'wall').setActive(true);
walls.create(800, 300, 'wall').setActive(true);
return walls;
}
handleCollisionWall(event, collider){
if(collider.body.touching.left){
event.setVelocityX(-100);
}else if(collider.body.touching.right){
event.setVelocityX(100);
}
return true;
}
handleCollisionEnemy(event, collider) {
this.scene.start('EndScene');
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 30,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 30
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 30,
repeat: -1
});
}
spawnBot(){
this.bots.create(100, 75, 'bot').setVelocityX(100);
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step10
github.com/mwilber/zeta-bros > Step11
import 'phaser';
import { GameScene } from './scenes/GameScene';
import { EndScene } from './scenes/EndScene';
const gameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-game',
dom: {
createContainer: true
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 900 },
debug: false
}
},
scene: [
GameScene,
EndScene
]
};
new Phaser.Game(gameConfig);
src/main.js
import Phaser from 'phaser';
export class EndScene extends Phaser.Scene {
constructor() {
super({
key: 'EndScene'
});
}
preload() {
this.load.image('end_splash', 'assets/images/end_splash.png');
}
create() {
// Add the background image
this.splash = this.add.image(400, 300, 'end_splash');
this.splash.setInteractive().on('pointerdown', () => {
this.scene.start('IntroScene');
});
}
}
src/scenes/EndScene.js
import Phaser from 'phaser';
export class GameScene extends Phaser.Scene {
constructor() {
super({
key: 'GameScene'
});
this.levelCt = 1;
}
preload() {
this.load.image('background', 'assets/images/background.png');
this.load.image('ground', 'assets/images/ground.png');
this.load.image('wall', 'assets/images/wall.png');
this.load.image('platform', 'assets/images/platform.png');
this.load.spritesheet('zeta',
'assets/images/zeta_spritesheet.png',
{ frameWidth: 40, frameHeight: 60 }
);
this.load.spritesheet('door',
'assets/images/door.png',
{ frameWidth: 64, frameHeight: 64 }
);
this.load.image('bot', 'assets/images/security_bot.png')
}
create() {
// Add the background image
this.add.image(400, 300, 'background');
this.initAnimation();
this.cursors = this.input.keyboard.createCursorKeys();
this.door = this.createDoors();
this.doorsign = this.createDoorSign();
this.platforms = this.createPlatforms();
this.walls = this.createWalls();
this.player = this.createPlayer();
this.bots = this.physics.add.group();
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.bots, this.platforms);
this.physics.add.collider(this.bots, this.walls, this.handleCollisionWall.bind(this));
this.physics.add.collider(this.player, this.bots, this.handleCollisionEnemy.bind(this));
this.physics.add.overlap(this.player, this.door, this.handleOverlapDoor.bind(this));
this.doorsign.setText(this.levelCt);
this.spawnBot();
}
update() {
if (this.cursors.left.isDown){
this.player.setVelocityX(-200);
this.player.anims.play('left', true);
}else if (this.cursors.right.isDown){
this.player.setVelocityX(200);
this.player.anims.play('right', true);
}else{
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down){
this.player.setVelocityY(-750);
}
}
createDoors(){
let door = this.physics.add.staticGroup();
// Entry door is inactive
door.create(400, 508, 'door', 0, true, false);
// Add an exit door
door.create(700, 508, 'door', 3);
return door;
}
createDoorSign(){
this.add.text(380, 410, 'LEVEL', { fontSize: '12px', fill: '#ff0000', align: 'center', fontFamily: 'sans-serif' });
return this.add.text(385, 420, '0', { fontSize: '48px', fill: '#ff0000', align: 'center', fontFamily: 'sans-serif' });
}
createPlatforms() {
let platforms = this.physics.add.staticGroup();
platforms.create(400, 270, 'platform');
platforms.create(400, 572, 'ground');
return platforms;
}
createPlayer() {
let player = this.physics.add.sprite(100, 450, 'zeta');
player.setBounce(0.2);
player.setCollideWorldBounds(true);
return player;
}
createWalls() {
let walls = this.physics.add.staticGroup();
walls.create(0, 300, 'wall').setActive(true);
walls.create(800, 300, 'wall').setActive(true);
return walls;
}
handleCollisionWall(event, collider){
if(collider.body.touching.left){
event.setVelocityX(-100);
}else if(collider.body.touching.right){
event.setVelocityX(100);
}
return true;
}
handleCollisionEnemy(event, collider) {
this.scene.start('EndScene');
}
handleOverlapDoor(event, collider) {
if(collider.active){
// Load the next scene
this.scene.start(this.scene.manager.getAt(this.scene.getIndex()+1));
}
}
initAnimation() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('zeta', { start: 0, end: 3 }),
frameRate: 20,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'zeta', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('zeta', { start: 5, end: 8 }),
frameRate: 20,
repeat: -1
});
this.anims.create({
key: 'doorOpen',
frames: this.anims.generateFrameNumbers('door', { start: 1, end: 3 }),
frameRate: 10
});
}
spawnBot(){
this.bots.create(100, 75, 'bot').setVelocityX(100);
}
}
src/scenes/GameScene.js
github.com/mwilber/zeta-bros > Step12
import 'phaser';
import { Level1 } from './levels/level1';
import { Level2 } from './levels/level2';
import { EndScene } from './scenes/EndScene';
const gameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-game',
dom: {
createContainer: true
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 900 },
debug: false
}
},
scene: [
Level1,
Level2,
EndScene
]
};
new Phaser.Game(gameConfig);
src/main.js
import { GameScene } from '../scenes/GameScene';
export class Level1 extends GameScene{
constructor() {
super({
key: 'Level1'
});
this.levelCt = 1;
}
createPlatforms() {
let platforms = super.createPlatforms();
platforms.create(100, 180, 'platform');
platforms.create(500, 180, 'platform');
platforms.create(700, 360, 'platform');
platforms.create(300, 360, 'platform');
return platforms;
}
createDoors(){
let door = super.createDoors();
// Add an exit door
door.create(100, 132, 'door', 3);
return door;
}
create() {
super.create();
this.time.addEvent({
delay: 5000,
callback: (event)=>{
this.spawnBot('left');
},
callbackScope: this,
repeat: 1
});
}
}
src/levels/Level1.js
github.com/mwilber/zeta-bros > Step13
import 'phaser';
import { IntroScene } from './scenes/IntroScene';
import { Level1 } from './levels/level1';
import { Level2 } from './levels/level2';
import { EndScene } from './scenes/EndScene';
const gameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'phaser-game',
dom: {
createContainer: true
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 900 },
debug: false
}
},
scene: [
IntroScene,
Level1,
Level2,
EndScene
]
};
new Phaser.Game(gameConfig);
src/main.js
import Phaser from 'phaser';
export class IntroScene extends Phaser.Scene {
constructor() {
super({
key: 'IntroScene'
});
}
preload() {
this.load.image('splash', 'assets/images/intro_splash.png');
}
create() {
// Add the background image
this.splash = this.add.image(400, 300, 'splash');
this.splash.setInteractive().on('pointerdown', () => {
this.scene.start('Level1');
});
}
}
src/scenes/IntroScene.js
Source Code
github.com/mwilber/zeta-bros
Follow @greenzeta on Twitter
Slides
slides.com/greenzeta/phaser
greenzeta.com
Phaser Website
phaser.io
By Matthew Wilber
From the 9/10/2019 Fredericksburg Developer Group meeting.
Full-Stack Web Developer. Building web sites & mobile apps for fun and profit! Free-time freelancer and UI/UX Engineer @ChartIQ