Jesse Harlin PRO
I am a developer, musician and usergroup leader living and working in Oklahoma, US.
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
Sytems Music Pros:
Sytems Music Cons:
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
By Jesse Harlin
A 40 minute talk about Phaser 3 and Game design
I am a developer, musician and usergroup leader living and working in Oklahoma, US.