GameDev with Phaser

Intro to GameDev and the Phaser Framework

Who we are

 

 

 

DevLeague is a FullStack coding boot camp
focusing on Web Application Development

We love games too!

And we love to apply our skills towards creating awesome things

10% Game
90% Dev

Things to consider when making a game

Playing the game

Play : exploring the limits of a system

Players want to achieve the highest scores and win

GameDevs create the platform to make this happen in fun and interesting ways

all Games have

  • Rules
  • Players
  • Victory Conditions

 

When you start do design your game, define these

Things to consider for videogames

The 3 "C"s

  • Camera
  • Control
  • Characters
     

When you start do design your game, decide these

Perspective

How many players?

Art, Animation, Motivation

Game Mechanic

What interactions will be provided for gameplay?

  • Turn based?
    • What does the player do or decide each turn?
  • Real Time?
    • What can players do, what is the goal?
  • Skill, Strategy, Chance, World

More things to consider...

Do's and don'ts

In GameDev

Do

  • Prototype your game before building
  • Playtest and have others playtest for you
  • Sprint to a playable game asap, validate if it's fun

Don't

  • Plan to build the biggest greatest MMORPG
    if you haven't built frogger yet
  • Be the "idea guy"
    actually contribute something

In Development

Do

  • Use SCM!!! (git is free)
  • Define an attainable scope
  • Work in tiny iterations
  • Manage team tasks
  • Communicate with your team
  • Contribute some code
  • Stretch a little beyond what you think you are capable of

Don't

  • Blame anyone on your team,
    instead, team up against the problem
  • Plan to build the most advanced deep learning AI algorithm if you haven't solved Tic-Tac-Toe yet

In hackathons

Do

  • Work in new teams
  • Be open to new recruits from external sources
  • Cut your scope in half
  • Cut your scope in half
  • Get playable version asap
  • Keep track of time
  • Learn a lot and improve code ability

Don't

  • Pre-define your entire team
  • Deprive yourself of sleep
    or healthy nutrients, else
    you'll become a "bug factory"
  • Get stuck on one thing for too long, pivot, move on 
  • Be afraid of challenges and struggles... this is why we do it

Let's build a thing

playable shmup (Shoot ‘Em Up)

Using javascript

Shoot'em up

in the browser

Will actually look more like this

Game Design

  • Camera: Top Down, Shoot Up
  • Control: Keyboard
  • Character: "Rogue Five"
  • Rules: Shoot baddies, dodge to survive
  • Players: Single player
  • Victory Condition: Make it to the end
    stretch goal: beat the final boss
  • What makes it interesting or challenging:
    • MVP: just get the core mechanics working
    • Stretch goals: multiple levels span across the galaxy
    • Stretch goals: unique powerups, pugs and narwhales
    • Stretch goals: Bosses have patterns and weakpoints

graphical assets

there are free game art assets available

http://opengameart.org/content/vertical-shmup-set-2-m484-games

Things you'll need

  1. Internets
  2. Google Chrome
  3. A Code Editor
    (like VSCode, Sublime Text, Atom)
  4. A Terminal Emulator
    (like iTerm, or Terminal.app)
  5. An SCM
    (like Git)

HTML5 game project setup

import and initialize the phaser framework

https://phaser.io/tutorials/making-your-first-phaser-game/index

Initialize your project

git init

mkdir Phaser-shmup
cd Phaser-shmup
git init

File & Directory structure

Create js/ and assets/ directory

mkdir js assets
touch index.html js/game.js
.
├── assets
├── index.html
└── js
    └── game.js

project directory should look like this

Index.html

html5 boilerplate

use emmet if you have it

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Phaser SHMUP</title>
  </head>
  <body>
    <h1>Phaser SHMUP</h1>


  </body>
</html>

Index.html

import phaser from cdnjs

go to https://cdnjs.com/libraries/phaser-ce

to get the latest version of phaser.min.js

  <body>
    <h1>Phaser SHMUP</h1>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/phaser-ce/2.10.0/phaser.min.js"></script>

  </body>

Index.html

import js/game.js

  <body>
    <h1>Phaser SHMUP</h1>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/phaser-ce/2.10.0/phaser.min.js"></script>
    <script src="js/game.js"></script>
  </body>

phaser and game.js

sanity check

(Phaser => {

  console.log(Phaser);

})(window.Phaser);

Test

run a local webserver

if you have live-server

 

 

 

or use python

live-server
python -m SimpleHTTPServer

test in chrome browser

Open chrome developer tools console

Object {VERSION: "2.6.2", GAMES: Array[0], AUTO: 0, CANVAS: 1, WEBGL: 2…}

sanity confirmed!

you should see this in your Chrome developer console

Setup Phaser Framework

Instantiate a new Phaser.game instance

(Phaser => {
  const GAME_WIDTH = 460;
  const GAME_HEIGHT = 600;
  const GAME_CONTAINER_ID = 'game';

  const game = new Phaser.Game(GAME_WIDTH, GAME_HEIGHT, Phaser.AUTO, GAME_CONTAINER_ID, { preload, create, update });

  function preload() {

  };

  function create() {

  };

  function update() {

  };

})(window.Phaser);

https://photonstorm.github.io/phaser-ce/Phaser.Game.html

Create the game container

index.html

    <h1>Phaser SHMUP</h1>

    <div id="game"></div>

             Add this ----------^

Test the game

index.html

   Phaser v2.6.2 | Pixi.js | WebGL | WebAudio     http://phaser.io ♥♥♥

commit!

 

First iteration

display a graphic

Download the spritesheet

save  to assets/

( cd assets; 
curl -O http://gomagames.com/assets/shmup-spritesheet-140x56-28x28-tile.png )

Load the spritesheet in phaser

const GFX = 'gfx';

function preload() {
 game.load.spritesheet(GFX, '../assets/shmup-spritesheet-140x56-28x28-tile.png', 28, 28);
};

Load the spritesheet in phaser

let player;

function create() {
  player = game.add.sprite(100, 100, GFX, 8);
};

Test

Commit

second iteration

move the graphic

Set inital movespeed

add the movespeed property to player object

const INITIAL_MOVESPEED = 4;

function create() {
  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
};

create a reference to keyboard controls

add the cursors variable

let cursors;

function create() {
  cursors = game.input.keyboard.createCursorKeys();
  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
};

handle player input

and react by translating object properties

a new function, above the update function

  function handlePlayerMovement() {
    switch( true ){
      case cursors.left.isDown:
        player.x -= player.moveSpeed;
        break;
      case cursors.right.isDown:
        player.x += player.moveSpeed;
        break;
    }
    switch( true ){
      case cursors.down.isDown:
        player.y += player.moveSpeed;
        break;
      case cursors.up.isDown:
        player.y -= player.moveSpeed;
        break;
    }
  };

handle player input on every frame

function update() {
  handlePlayerMovement();
};

Test

up down left right

commit

What is wrong with the movement?

fix it with math!

diagonal movement should be the same speed as orthogonal movement

function handlePlayerMovement() {
  let movingH = Math.sqrt(2);
  let movingV = Math.sqrt(2);
  if( cursors.up.isDown || cursors.down.isDown){
    movingH = 1; // slow down diagonal movement
  }
  if( cursors.left.isDown || cursors.right.isDown){
    movingV = 1; // slow down diagonal movement
  }
  switch( true ){
    case cursors.left.isDown:
      player.x -= player.moveSpeed * movingH;
      break;
    case cursors.right.isDown:
      player.x += player.moveSpeed * movingH;
      break;
  }
  switch( true ){
    case cursors.down.isDown:
      player.y += player.moveSpeed * movingV;
      break;
    case cursors.up.isDown:
      player.y -= player.moveSpeed * movingV;
      break;
  }
};

Test

commit

 

third iteration

SPAWN AND ANIMATE BULLETS

Add a bullets group

playerbullets is a Phaser Group

  let playerBullets;

  function create() {
    cursors = game.input.keyboard.createCursorKeys();

    player = game.add.sprite(100, 100, GFX, 8);
    player.moveSpeed = INITIAL_MOVESPEED;
    playerBullets = game.add.group();
  };

add keyboard control for fire

function create() {
  cursors = game.input.keyboard.createCursorKeys();
  cursors.fire = game.input.keyboard.addKey(Phaser.KeyCode.SPACEBAR);
  cursors.fire.onUp.add( handlePlayerFire );

  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
  playerBullets = game.add.group();
};
function handlePlayerFire() {
  console.log("fire");
};

sanity check first

Test

confirm sanity and commit

spawn bullets

function handlePlayerFire() {
  playerBullets.add( game.add.sprite(player.x, player.y, GFX, 7) );
};

when player triggers fire signal

test

commit

animate bullets

function update() {
  handlePlayerMovement();
  handleBulletAnimations();
};
const PLAYER_BULLET_SPEED = 6;

function handleBulletAnimations() {
  playerBullets.children.forEach( bullet => bullet.y -= PLAYER_BULLET_SPEED );
};

iterate over playerbullets, translate .y

in a new function above update

on every frame

Test

commit

Memory Management

this is standard best PRACTICE, always do this

  function cleanup() {
    playerBullets.children
      .filter( bullet => bullet.y < 0 )
      .forEach( bullet => bullet.destroy() );
  };

  function update() {
    handlePlayerMovement();
    handleBulletAnimations();

    cleanup();
  };

Test

commit

fourth iteration

SPAWN AND ANIMATE BADDIES

create an enemies phaser group

let enemies;

function create() {
  cursors = game.input.keyboard.createCursorKeys();
  cursors.fire = game.input.keyboard.addKey(Phaser.KeyCode.SPACEBAR);
  cursors.fire.onUp.add( handlePlayerFire );

  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
  playerBullets = game.add.group();
  enemies = game.add.group();
};

randomly spawn enemies

  const ENEMY_SPAWN_FREQ = 100; // higher is less frequent

  const randomGenerator = new Phaser.RandomDataGenerator();

  function randomlySpawnEnemy() {
    if(randomGenerator.between(0, ENEMY_SPAWN_FREQ) === 0) {
      let randomX = randomGenerator.between(0, GAME_WIDTH);
      enemies.add( game.add.sprite(randomX, -24, GFX, 0));
    }
  }
  const update = _ => {
    handlePlayerMovement();
    handleBulletAnimations();
    randomlySpawnEnemy();

    cleanup();
  };

run this every frame

translate enemy .y property

  const ENEMY_SPEED = 4.5;

  function handleEnemyActions() {
    enemies.children.forEach( enemy => enemy.y += ENEMY_SPEED );
  };
  function update() {
    handlePlayerMovement();
    handleBulletAnimations();
    handleEnemyActions();
    randomlySpawnEnemy();

    cleanup();
  };

run this every frame

Test

commit

fifth iteration

COLLISION DETECTION - destroy them with lasers

handle collisions

  function removeBullet(bullet) {
    bullet.destroy();
  }

  function destroyEnemy(enemy) {
    enemy.kill();
  }

  function handleCollisions() {
    // check if any bullets touch any enemies
    let enemiesHit = enemies.children
      .filter( enemy => enemy.alive )
      .filter( enemy => 
        playerBullets.children.some( 
          bullet => enemy.overlap(bullet) 
        ) 
      );

    if( enemiesHit.length ){
      // clean up bullets that land
      playerBullets.children
        .filter( bullet => bullet.overlap(enemies) )
        .forEach( removeBullet );

      enemiesHit.forEach( destroyEnemy );
    }
  };

handle collisions every frame

  function update() {
    handlePlayerMovement();
    handleBulletAnimations();
    handleEnemyActions();
    handleCollisions();
    randomlySpawnEnemy();

    cleanup();
  };

Test

Commit

sixth iteration

enemies can destroy Player

check for player collisions

  functions handleCollisions() {
    // check if any bullets touch any enemies
    let enemiesHit = enemies.children
      .filter( enemy => enemy.alive )
      .filter( enemy => enemy.overlap(playerBullets) );

    if( enemiesHit.length){
      // clean up bullets that land
      playerBullets.children
        .filter( bullet => bullet.overlap(enemies) )
        .forEach( removeBullet );

      enemiesHit.forEach( destroyEnemy );
    }

    // check if enemies hit the player
    enemiesHit = enemies.children
      .filter( enemy => enemy.overlap(player) );

    if( enemiesHit.length){
      handlePlayerHit();

      enemiesHit.forEach( destroyEnemy );
    }
  };

handle player hit

  function handlePlayerHit() {
    gameOver();
  };

just gameover for now

implement gameover()

  function gameOver() {
    game.state.destroy();
    game.add.text(90, 200, 'YOUR HEAD ASPLODE', { fill: '#FFFFFF' });
  };

test, get hit on purpose

commit

seventh iteration

play again

add a play again button

function gameOver() {
  game.state.destroy();
  game.add.text(90, 200, 'YOUR HEAD ASPLODE', { fill: '#FFFFFF' });
  let playAgain = game.add.text(120, 300, 'Play Again', { fill: '#FFFFFF' });
  playAgain.inputEnabled = true;
  playAgain.events.onInputUp.add(() => window.location.reload());
};

it's just text that we made clickable

test, get hit on purpose, play again

commit

stretch goal one

baddies fire back

create a phaser group to track enemy bullets

let enemyBullets;


function create() {
  cursors = game.input.keyboard.createCursorKeys();
  cursors.fire = game.input.keyboard.addKey(Phaser.KeyCode.SPACEBAR);
  cursors.fire.onUp.add( handlePlayerFire );

  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
  playerBullets = game.add.group();
  enemies = game.add.group();
  enemyBullets = game.add.group();
};

enable arcade physics

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);

  cursors = game.input.keyboard.createCursorKeys();
  cursors.fire = game.input.keyboard.addKey(Phaser.KeyCode.SPACEBAR);
  cursors.fire.onUp.add( handlePlayerFire );

  player = game.add.sprite(100, 100, GFX, 8);
  player.moveSpeed = INITIAL_MOVESPEED;
  playerBullets = game.add.group();
  enemies = game.add.group();
  enemyBullets = game.add.group();
  enemyBullets.enableBody = true;
};

implement random enemy fire

  const ENEMY_FIRE_FREQ = 30; // higher is less frequent

  function randomEnemyFire(enemy) {
    if( randomGenerator.between(0, ENEMY_FIRE_FREQ) === 0 ){
      let enemyBullet = game.add.sprite(enemy.x, enemy.y, GFX, 9);
      enemyBullet.checkWorldBounds = true;
      enemyBullet.outOfBoundsKill = true;
      enemyBullets.add( enemyBullet );
    }
  };

  function handleEnemyActions() {
    enemies.children.forEach( enemy => enemy.y += ENEMY_SPEED );
    enemies.children.forEach( enemy => randomEnemyFire(enemy) );
  };

enemy bullets fire towards player

const ENEMY_BULLET_ACCEL = 100;

function handleBulletAnimations() {
  playerBullets.children.forEach( bullet => bullet.y -= PLAYER_BULLET_SPEED );
  enemyBullets.children.forEach( bullet =>  {
    game.physics.arcade.accelerateToObject(bullet, player, ENEMY_BULLET_ACCEL);
  });
};

detect collisions with player

  function handleCollisions() {
    // check if any bullets touch any enemies
    // ...

    // check if enemies hit the player
    // ...

    // check if enemy bullets hit the player
    let enemyBulletsLanded = enemyBullets.children
      .filter( bullet => bullet.overlap(player) );

    if( enemyBulletsLanded.length){
      handlePlayerHit(); // count as one hit
      enemyBulletsLanded.forEach( removeBullet );
    }
  };

Clean up enemy bullets

  function cleanup() {
    playerBullets.children
      .filter( bullet => bullet.y < 0 )
      .forEach( bullet => bullet.destroy() );
    enemies.children
      .filter( enemy => enemy.y > GAME_HEIGHT || !enemy.alive )
      .forEach( enemy => enemy.destroy() );
    enemyBullets.children
      .filter( bullet => !bullet.alive )
      .forEach( bullet => bullet.destroy() );
  };
function randomEnemyFire(enemy) {
  if(randomGenerator.between(0, ENEMY_FIRE_FREQ) === 0){
    let enemyBullet = game.add.sprite(enemy.x, enemy.y, GFX, 9);
    enemyBullet.checkWorldBounds = true;
    enemyBullet.outOfBoundsKill = true;
    enemyBullets.add( enemyBullet );
  }
};

remember this?

Test

commit

More Stretch goals

hold to charge fire

powerups

final boss

Note how the MVP iterations vs. stretch goals are defined

award points, increment score

GameDev with Phaser - Intro

By DevLeague Coding Bootcamp

GameDev with Phaser - Intro

workshop in preparation for game jams

  • 1,810