Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 3

Slide 3 text

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!

Slide 4

Slide 4 text

But Why Build a Game in Ember? 4 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Incredulous Croissant!

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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...

Slide 7

Slide 7 text

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?

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

The Fun Parts of Game Development 9 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • The game mechanics • Graphics • Audio

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

The Fun Parts 11 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • The game loop • Sprites • Audio

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

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(); }

Slide 14

Slide 14 text

draw() • Clear the 's context (more on 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(); }

Slide 15

Slide 15 text

Draw on the • 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);

Slide 16

Slide 16 text

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);

Slide 17

Slide 17 text

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 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Go, Croissant! 19 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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 . Next slide!

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Audio • Audio in Javascript is ughghghhhhhh. • You have a couple options: The HTML5 tag and the Web Audio API. • The 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 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

The Boring Parts 27 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • Menus • Testing • Deploying

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Pausing the Game 29 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > /#/play/1 Play Pause /#/play/1/menu

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

/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(); }, ...

Slide 32

Slide 32 text

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:

Slide 33

Slide 33 text

Testing the Game in Ember 33 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 34

Slide 34 text

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:

Slide 35

Slide 35 text

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!

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Deploying the Game with Ember 37 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Web Mobile Desktop

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Croissant on Mobile and Desktop 40 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > • iOS • Android • Mac OS X

Slide 41

Slide 41 text

Takeaway • Let Ember handle the boring parts so you can focus on the fun part: building the game! 41 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 42

Slide 42 text

Links • https://developer.mozilla.org/en-US/docs/Games • http://ludumdare.com/compo/tools/ 42 > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >

Slide 43

Slide 43 text

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