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

Building HTML5 games with Pixi

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Building HTML5 games with Pixi

Avatar for Reginald Tan

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)