Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building HTML5 games with Pixi

Building HTML5 games with Pixi

Reginald Tan

June 12, 2019
Tweet

Other Decks in Technology

Transcript

  1. Why use it? • Browser First. (Unity WebGL export -

    slow, unreliable) • Lightweight. Not a game-engine. No sounds, physics. Just rendering. (Phaser is more fully featured) • Javascript. I already know JS
  2. Why not use it? • Poor performance on iOS/Android (WebView

    sucks) • WebGL debugging tools are bad • Javascript Exception Reporting (cant see stack locals) • You need an all-in-one package (rendering + physics + sounds + tilemaps + editors)
  3. Circles vs Squares • A game where the world is

    filled with squares and your goal is to convert them into circles and make the world a better place
  4. Outline • Display Circle • Movement Control • Displaying Squares

    • Z-order and Containers • Convert Squares->Circle • Effects and Animations • Score
  5. Circle Add Canvas DOM Element <html> <head> <title>Circles vs Squares</title>

    <script src="./pixi.5.0.3.js" type="text/javascript" charset=“utf-8”> </script> </head> <body> <canvas id='game_canvas'> </body> </html>
  6. Circle Initialize PIXI const app = new PIXI.Application({ view: document.getElementById(‘game_canvas'),

    forceCanvas: false, antialias: true, width: window.innerWidth, height: window.innerHeight })
  7. Circle Create Circle + Add to Stage function main() {

    let texture = PIXI.utils.TextureCache["circle.png"] let circle = new PIXI.Sprite(texture) circle.anchor.set(0.5) circle.position.set(window.innerWidth / 2, window.innerHeight / 2) app.stage.addChild(circle) }
  8. Movement Controls The Wrong Way document.addEventListener("keydown", keyDownHandler, true) function keyDownHandler()

    { let step = 10 if (e.key === "w") circle.position.y -= step if (e.key === "a") circle.position.x -= step if (e.key === "s") circle.position.y += step if (e.key === "d") circle.position.x += step }
  9. Movement Controls The Correct Way app.ticker.add(renderLoop) // uses requestAnimationFrame function

    renderLoop(delta) { let step = 5 if (heldKeys["w"]) circle.position.y -= step if (heldKeys["a"]) circle.position.x -= step if (heldKeys["s"]) circle.position.y += step if (heldKeys["d"]) circle.position.x += step }
  10. Movement Controls Input Handler window.heldKeys = {} document.addEventListener("keydown", keyDownHandler, true)

    document.addEventListener("keyup", keyUpHandler, true) function keyDownHandler(e) { heldKeys[e.key] = true } function keyUpHandler(e) { delete heldKeys[e.key] }
  11. Displaying Squares Constructor function createSquare() { const graphics = new

    PIXI.Graphics() let width = height = 50 graphics.beginFill(0x0000ff) graphics.drawRect(0, 0, width, height) graphics.endFill() return graphics }
  12. Displaying Squares Add to Stage for (var i = 0;

    i < 10; i++) { let square = createSquare() let randomX = Math.floor(Math.random() * window.innerWidth) let randomY = Math.floor(Math.random() * window.innerHeight) square.position.set(randomX, randomY) app.stage.addChild(square) }
  13. Z-Order and Containers Concepts • Containers group Display Objects together

    • Stuff added later has higher Z-order • Squares were added later to Stage so it was rendered on top of circle
  14. Z-Order and Containers Add Containers to Stage function setupLayers() {

    const layers = {} layers.squaresContainer = new PIXI.Container() app.stage.addChild(layers.squaresContainer) layers.circlesContainer = new PIXI.Container() app.stage.addChild(layers.circlesContainer) return layers }
  15. Z-Order and Containers Add Graphics to Containers let layers =

    setupLayers() circle = createCircle() layers.circlesContainer.addChild(circle) for (var i = 0; i < 10; i++) { let square = createSquare() let randomX = Math.floor(Math.random() * window.innerWidth) let randomY = Math.floor(Math.random() * window.innerHeight) square.position.set(randomX, randomY) layers.squaresContainer.addChild(square) }
  16. Collision Handling Test if Box overlaps Circle // https://github.com/davidfig/intersects/blob/master/box-circle.js /**

    * box-circle collision * @param {number} xb top-left corner of box * @param {number} yb top-left corner of box * @param {number} wb width of box * @param {number} hb height of box * @param {number} xc center of circle * @param {number} yc center of circle * @param {number} rc radius of circle */ function testBoxCircle(xb, yb, wb, hb, xc, yc, rc) { var hw = wb / 2 var hh = hb / 2 var distX = Math.abs(xc - (xb + wb / 2)) var distY = Math.abs(yc - (yb + hb / 2)) if (distX > hw + rc || distY > hh + rc) { return false } if (distX <= hw || distY <= hh) { return true } var x = distX - hw var y = distY - hh return x * x + y * y <= rc * rc }
  17. Collision Handling Hold references to Squares let squares = {}

    for (var i = 0; i < 10; i++) { let square = createSquare() square.id = i squares[square.id] = square }
  18. Collision Handling Get collisions function getCollisions(circle, squares, callback) { for

    (var id in squares) { let square = squares[id] let isOverlapping = testBoxCircle(square.position.x , square.position.y, square.width, square.height, circle.position.x, circle.position.y, circle.width/2) if (isOverlapping) { callback(square) } } }
  19. Collision Handling Converting Squares into Circles function handleCollision(circle, squares) {

    getCollisions(circle, squares, (square) => { // remove square square.parent.removeChild(square) delete squares[square.id] // create circle let newCircle = createCircle() newCircle.position.set(square.position.x, square.position.y) layers.circlesContainer.addChild(newCircle) }) }
  20. Collision Handling Executing the handler function renderLoop(delta) { if (heldKeys["w"]

    || heldKeys["a"] || heldKeys["s"] || heldKeys["d"]) { onCirclePositionChanged(circle) } // other stuff (not included for brevity) } function onCirclePositionChanged(circle) { handleCollision(circle, squares) }
  21. Effects and Animations Destroy Square Animation function animateSquareDestroy(square, onComplete) {

    const attrs = { alpha: 1, rotation: 0 } const desired = { alpha: 0, rotation: Math.PI * 2 } const duration = 2000 const tween = new TWEEN.Tween(attrs) .to(desired, duration) .onUpdate(() => { square.alpha = attrs.alpha square.rotation = attrs.rotation }) .onComplete(onComplete) tween.start() }
  22. Effects and Animations Circle Enter Animation function animateCircleEnter(circle) { const

    attrs = { scale: 0 } const desired = { scale: 1 } const duration = 1000 const tween = new TWEEN.Tween(attrs) .to(desired, duration) .onUpdate(() => { circle.scale.set(attrs.scale) }) tween.start() }
  23. Effects and Animations Tying Things together function handleCollision(circle, squares) {

    getCollisions(circle, squares, (square) => { animateSquareDestroy(square, () => { // remove square square.parent.removeChild(square) delete squares[square.id] // create circle let position = square.position let newCircle = createCircleAtPosition(square.id, position.x, position.y) animateCircleEnter(newCircle) }) }) }
  24. Effects and Animations Showing score using PIXI.Text let style =

    { fontFamily : 'Arial', fontSize: 24, fill : 0xffffff } score = new PIXI.Text('0', style) score.position.set(20,20) app.stage.addChild(score) function renderLoop(delta) { // more code above if (Object.keys(squares).length > 0) { let seconds = (app.ticker.lastTime / 1000).toFixed(2) score.text = seconds + " s" } // more code below }
  25. Tools for Debugging • PixiJS Devtools • Chrome Devtools •

    WebGL-Inspector • Nvidia Nsight (Windows)