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

Build a Game in JavaScript and Ember Starring Y...

Build a Game in JavaScript and Ember Starring Your Cat

Ember is generally used to build ambitious web applications, but what about ambitious web games starring your cat? In this talk, I'll go over how to use Ember to build, organize, and deploy HTML5-canvas based games.

Using Ember to develop games shares the same benefits of using Ember to develop applications—both the toolchain and the conventions alleviate decision fatigue. For example, route-driven paths are great at handling levels, Liquid Fire provides lovely transitions for menus, and adapters provide a standard way to connect to backends for saving high scores.

Matt McKenna

April 09, 2016
Tweet

More Decks by Matt McKenna

Other Decks in Programming

Transcript

  1. Build a Game in JavaScript and Ember Starring Your Cat

    Global Ember Meetup April 9, 2016 1 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > https://speakerdeck.com/mtmckenna/build-a-game-in-javascript-and-ember-starring-your-cat [email protected] @mattmckenna
  2. Previous Talks on Games/Cats • Build a Game in JavaScript

    Starring Your Cat • https://speakerdeck.com/mtmckenna/build-a-game-in-javascript- starring-your-cat • Build a Game in Ember Starring Your Cat • https://speakerdeck.com/mtmckenna/build-a-game-in-ember- starring-your-cat 2 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  3. Why Build a Game in JS? • Haven't you always

    wanted to make a game? • You probably already know JS, so you can get started right away. • JS apps are relatively easy to deploy. • JS apps work on all devices (hahahahahaha). 3 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Croissant!
  4. But Why Build a Game in Ember? 4 > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Incredulous Croissant!
  5. You Are Special You are my friend You are special

    You are my friend You're special to me. You are the only one like you. Like you, my friend, I like you. 5 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > https://commons.wikimedia.org/wiki/File:Fred_Rogers,_late_1960s.jpg
  6. Your JS App Is Probably Less Special 6 > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Thing Your JS App Needs Ember's Way of Handling that Need An asset pipeline ember-cli Remotely persisted data adapters / emberfire App state maintained between routes services tests ember-qunit, ember-cli-mirage a deploy and rollback strategy ember-cli-deploy • Even if your JS app seems unique, it probably still needs...
  7. Let Ember Handle the Boring Parts 7 > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • It may feel like your app requires freedom from Ember and its conventions, but... • "With freedom comes responsibility maintenance."- Eleanor Roosevelt • How bad do you want to maintain that glue code?
  8. The Boring Parts of Game Development 8 > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • The non-game UI (e.g. menus, scores, etc.) • Testing • Deploying to the web, mobile, and desktop environments.
  9. The Fun Parts of Game Development 9 > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • The game mechanics • Graphics • Audio
  10. Croissant -- this is you! Pizzas -- touch these for

    points! Cat beds -- jump to avoid these! 10 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Croissant the Pizza Cat • https://www.CroissantThePizzaCat.com • https://github.com/mtmckenna/croissant-runner-ember
  11. The Fun Parts 11 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • The game loop • Sprites • Audio
  12. The Game Loop 12 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > setInterval(function() { this.update(); this.draw(); }, 1000.0/60.0); We'll do a better game loop in 4 slides. • The game loop... • ... calculates the position of sprites (Croissant, pizzas, etc.). • ... draws the sprites to the screen.
  13. update() • Update the position of the sprites (e.g. Croissant,

    the pizzas, cat beds, etc.). • Check if anything is touching anything (e.g. if Croissant has touched a pizza/cat bed) and then do something (e.g. increase the score/end the game). 13 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > update() { updateSpritePositions(); handleCollisions(); }
  14. draw() • Clear the <canvas>'s context (more on <canvas> in

    like 5 seconds). • Draw the randomly generated sprites (cat beds, pizzas, flowers, etc.). • Draw Croissant. 14 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > draw() { context.clearRect(0, 0, 320, 240); drawGround(); spriteEmitter.draw(); drawScore(); croissant.draw(); }
  15. Draw on the <canvas> • <canvas> is an HTML element

    on which you can programmatically draw and animate whatever you want free of the DOM. • https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/ Tutorial 15 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > var canvas = document.createElement('canvas'); document.body.appendChild(canvas); var context = canvas.getContext('2d'); context.fillStyle = '#4f8f00'; context.font = '15px Monaco'; context.fillText('Hi Score: 19', 10, 25);
  16. A Better Game Loop 16 > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > gameLoop() { this.animReq = window.requestAnimationFrame(this.gameLoop.bind(this)); this.update(); this.draw(); } What's this thing? Find out on the next slide! setInterval(function() { this.update(); this.draw(); }, 1000.0/60.0);
  17. requestAnimationFrame • Supported in modern browsers. • Calls the gameLoop

    function when the browser is ready to repaint the screen. • The browser will attempt to call gameLoop 60 times a second (i.e. 60 FPS). No promises though. • requestAnimationFrame is better than setInterval. 17 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  18. Sprites • Nowadays, a sprite is generally a single bitmap

    graphic, usually with multiple frames of animation. • Animation is done by flipping through the images' frames like a flipbook or filmstrip. 18 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Frame 0 Frame 1 0px, 0px 30px, 0px 60px, 0px 60px, 32px Both frames are in one image: croissant.png
  19. Go, Croissant! 19 > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  20. Go, Croissant! 20 > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  21. Go, Croissant! 21 > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  22. Go, Croissant! 22 > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  23. A Sprite ES6 Class 23 > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > export default class { constructor(type, context, startingPosition, size, velocity) { ... } cacheImage(type) { ... } createImage(type) { ... } get image() { ... } get currentFrame() { ... } intersects(anotherSprite) { ... } updatePosition() { ... } draw() { ... } } The draw method animates the sprite and places it on the <canvas>. Next slide!
  24. Sprite's Draw Method 24 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > draw() { this.context.drawImage(this.image, this.currentFrame * this.size.width, 0, this.size.width, this.size.height, this.pos.x, this.pos.y, this.size.width, this.size.height); }This is the part that flips between the two frames in croissant.png
  25. Audio • Audio in Javascript is ughghghhhhhh. • You have

    a couple options: The HTML5 <audio> tag and the Web Audio API. • The <audio> tag is simple to use, but nerfed on mobile. • So use Web Audio API. • https://developer.mozilla.org/en-US/docs/Web/API/ Web_Audio_API 25 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  26. A SoundEffect ES6 Class 26 > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > export default class { constructor(type, context, loop = false) { ... } loadAudio(path) { ... } decodeAudio(data) { ... } play() { ... } stop() { ... } } Web Audio API rigamarole How we'll actually play our audio
  27. The Boring Parts 27 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • Menus • Testing • Deploying
  28. The Boring Parts 28 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • Menus • Testing • Deploying
  29. Pausing the Game 29 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > /#/play/1 Play Pause /#/play/1/menu
  30. The Game Service 30 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • Services are long-lived JS objects that can maintain state between routes. • The game loop and logic can live in this service so the game will maintain its state even when transitioning into the menu route. • The game service can be used to play and pause the game. $ ember generate service game ... installing service create app/game/service.js installing service-test create tests/unit/game/service-test.js
  31. /game/service.js 31 > > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > ... gameLoop() { this.animReq = window.requestAnimationFrame(this.gameLoop.bind(this)); this.update(); this.draw(); }, play() { this.paused = false; this.gameLoop(); this.addEventListeners(); }, pause() { this.paused = true; window.cancelAnimationFrame(this.animReq); this.animReq = null; this.removeEventListeners(); }, ...
  32. Playing and Pausing the Game 32 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > export default Ember.Route.extend({ game: Ember.inject.service(), pauseGame: function(){ this.get('game').pause(); }.on('activate'), playGame: function(){ this.get('game').play(); }.on('deactivate') }); play/menu/route.js:
  33. Testing the Game in Ember 33 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  34. UI/Menu Component Tests 34 > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > test('toggle audio', function(assert) { this.render(hbs`{{game-menu}}`); $('div:contains("AUDIO OFF")').click(); assert.notEqual(this.$().text().indexOf('AUDIO ON'), -1); $('div:contains("AUDIO ON")').click(); assert.notEqual(this.$().text().indexOf('AUDIO OFF'), -1); }); • Test UI components as you would any Ember component. tests/integration/components/game-menu/component-test.js:
  35. Acceptance Testing the Game Mechanics 35 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > test('touching pizzas means points', function(assert) { visit('/play/1'); andThen(function() { assert.equal(currentURL(), '/play/1'); var pizzaCount = parseInt($('.js-pizza-count').text()); assert.equal(pizzaCount, 0); spewPizzasLikeCrazy(); var done = assert.async(); Ember.run.later(function() { pizzaCount = parseInt($('.js-pizza-count').text()); assert.ok(pizzaCount > 0); done(); }, 1000); }); }); What does this test helper do? Find out on the next slide!
  36. 36 > > > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > export default Ember.Test.registerHelper('spewPizzasLikeCrazy', function(app) { var game = app.__container__.lookup('service:game'); game.spriteEmitter.emitSprites = function() { if (!game.spriteEmitter.shouldCreateSprite(5)) { return; } var pizza = new Pizza(this.context, 200, -10.0); this.sprites.push(pizza); }; }); spewPizzasLikeCrazy Test Helper Pizzas all lined up No cat beds Croissant moves fast
  37. Deploying the Game with Ember 37 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Web Mobile Desktop
  38. Deploying to the Web 38 > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • ember-cli-deploy provides a supported way to deploy and rollback revisions. $ ember deploy production ... - ✔ index.html:d8608b8abdc7dd4f7f622a058e3ecd97 => index.html
  39. Deploying to Mobile and Desktop Apps 39 > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • Building Mobile Applications with Ember
 by Alex Blom (@alexblom) • ember-cli-cordova • Building Desktop Apps with Ember and Electron 
 by Felix Rieseberg (@felixrieseberg) • ember-electron
  40. Croissant on Mobile and Desktop 40 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • iOS • Android • Mac OS X
  41. Takeaway • Let Ember handle the boring parts so you

    can focus on the fun part: building the game! 41 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  42. Links • https://developer.mozilla.org/en-US/docs/Games • http://ludumdare.com/compo/tools/ 42 > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >
  43. Thank You! 43 > > > > > > >

    > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >