•  

    A Web Game From Scratch For A Game Jam

    ▶
  •  

    Why a web game?

    ◀ ▶
  •  

    What is a Game Jam?

    ◀ ▶
  •  
    • Ludum Dare
    • js13kGames
    • 0h Game Jam
    • One Game A Month
    ◀ ▶
  •  

    The Theme

    ◀ ▶
  •  

    Are there HTML5 game frameworks?

    ◀ ▶
  •  
    • Phaser
    • PixiJs
    ◀ ▶
  •  
    • html5gameengine.com
    ◀ ▶
  •  

    But we'll keep it simple

    ◀ ▶
  •  

    How to start?

    ◀ ▶
  •  

    First, make a decision what game you want to make.

    ◀ ▶
  •  

    Make a prototype.

    ◀ ▶
  •  

    Then make graphics, music and levels.

    ◀ ▶
  •  

    Pixel art? Vector graphics? 3D?

    ◀ ▶
  •  

    Upsampling versus Pixel Art

    ◀ ▶
  •  
    image-rendering: pixelated;
    		
    ◀ ▶
  •  
    context.imageSmoothingEnabled = false;
    		
    ◀ ▶
  •  

    High DPI

    ◀ ▶
  •  

    Mobile

    ◀ ▶
  •  

    SVG scales great

    ◀ ▶
  •  

    gl.NEAREST

    ◀ ▶
  •  
    gl.texParameteri(
        gl.TEXTURE_2D,
        gl.TEXTURE_MIN_FILTER,
        gl.NEAREST)
    gl.texParameteri(
        gl.TEXTURE_2D,
        gl.TEXTURE_MAG_FILTER,
        gl.NEAREST)
    		
    ◀ ▶
  •  

    Canvas, DOM, SVG oder WebGL?

    ◀ ▶
  •  
    • Keep Out!
    • HexGL
    • BMW i8
    ◀ ▶
  •  

    Canvas is okay

    ◀ ▶
  •  
    • Biolab
    • Galex
    • Gingerman
    ◀ ▶
  •  

    DOM only for a few elements

    ◀ ▶
  •  
    • Striker
    • Santa Tracker
    ◀ ▶
  •  

    What about SVG?

    ◀ ▶
  •  
    • Simple SVG Game (Canvas implementation)
    • Animation Benchmark (Canvas implementation)
    ◀ ▶
  •  

    Anatomy of a 2D Canvas Game

    ◀ ▶
  •  

    HTML

    ◀ ▶
  •  
    <!doctype html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, user-scalable=0"/>
    <style>
    ...
    </style>
    </head>
    <body>
    <canvas id="Game">Sorry, no game for you.</canvas>
    <script>
    ...
    </script>
    </body>
    </html>
    		
    ◀ ▶
  •  

    Stylesheet

    ◀ ▶
  •  
    html, body {
        margin: 0; padding: 0;
        overflow: hidden;
        -ms-touch-action: none;
    }
    
    canvas {
        position: fixed;
        width: 100%;
        height: 100%;
    }
    		
    ◀ ▶
  •  

    JavaScript

    ◀ ▶
  •  
    'use strict'
    
    var canvas,
        ctx,
        width,
        height,
        now,
        factor,
        last,
        pointersLength = 0,
        pointersX = [],
        pointersY = [],
        keysDown = []
    
    function draw() {}
    function run() {}
    function init() {}
    		
    ◀ ▶
  •  

    Init

    ◀ ▶
  •  
    function init() {
        if (!(canvas = document.getElementById('Game')) ||
                !(ctx = canvas.getContext('2d'))) {
            return
        }
    
        window.onresize = resize
        resize()
    
        ...
    
        last = Date.now() - 16
        run()
    }
    		
    ◀ ▶
  •  

    Resize

    ◀ ▶
  •  
    function resize() {
        canvas.width = width = window.innerWidth
        canvas.height = height = window.innerHeight
    
        ...
    }
    		
    ◀ ▶
  •  

    The main loop

    ◀ ▶
  •  
    function run() {
        requestAnimationFrame(run)
    
        now = Date.now()
        factor = (last - now) / 16
        last = now
    
        input()
        draw()
    }
    		
    ◀ ▶
  •  

    Input

    ◀ ▶
  •  
    function input() {
        if (pointersLength > 0) {
            move(pointersX[0] > playerX ? step : -step)
        } else if (keysDown[37]) {
            move(-step)
        } else if (keysDown[39]) {
            move(step)
        }
    }
    		
    ◀ ▶
  •  

    Draw

    ◀ ▶
  •  
    function draw() {
        ctx.fillRect(0, 0, width, height)
    
        ...
    }
    		
    ◀ ▶
  •  

    Touch and Mouse Input

    ◀ ▶
  •  
    var D = document
    
    D.onmousedown = pointerDown
    D.onmousemove = pointerMove
    D.onmouseup = pointerUp
    D.onmouseout = pointerUp
    
    if ('ontouchstart' in D) {
        D.ontouchstart = pointerDown
        D.ontouchmove = pointerMove
        D.ontouchend = pointerUp
        D.ontouchleave = pointerUp
        D.ontouchcancel = pointerUp
    }
    		
    ◀ ▶
  •  

    Pointer down/move/up

    ◀ ▶
  •  
    function pointerUp(event) {
        setPointer(event, false)
    }
    
    function pointerMove(event) {
        setPointer(event, pointerLength)
    }
    
    function pointerDown(event) {
        setPointer(event, true)
    }
    		
    ◀ ▶
  •  

    Set pointers

    ◀ ▶
  •  
    function setPointer(event, down) {
        if (!down) {
            pointersLength = event.touches ? event.touches.length : 0
        } else if (event.touches) {
            var touches = event.touches
            pointersLength = touches.length
    
            for (var i = pointersLength; n--;) {
                var t = touches[n];
                pointersX[i] = t.pageX
                pointersY[i] = t.pageY
            }
        } else {
            pointersLength = 1
            pointersX[0] = event.pageX
            pointersY[0] = event.pageY
        }
    
        event.preventDefault()
    }
    		
    ◀ ▶
  •  

    Keyboard Input

    ◀ ▶
  •  
    var D = document
    
    D.onkeydown = keyDown
    D.onkeyup = keyUp
    		
    ◀ ▶
  •  

    Set pressed keys

    ◀ ▶
  •  
    function setKey(event, down) {
        keysDown[event.keyCode] = down
        event.preventDefault()
    }
    
    function keyUp(event) {
        setKey(event, false)
    }
    
    function keyDown(event) {
        setKey(event, true)
    }
    		
    ◀ ▶
  •  

    Demo

    ◀ ▶
  •  
    • Canvas
    • WebGL 2D
    ◀ ▶
  •  

    Tips

    ◀ ▶
  •  

    Keep it simple!

    ◀ ▶
  •  

    All numbers are floats!

    ◀ ▶
  •  
    ctx.drawImage(sprite, x | 0, y | 0)
    		
    ◀ ▶
  •  

    Object Properties and Array Indicies are slow

    ◀ ▶
  •  
    object.a.b.c()
    
    array[0][0][0]
    		
    ◀ ▶
  •  
    var a = object.a,
        b = a.b
    
    b.c()
    		
    ◀ ▶
  •  

    Avoid allocations

    ◀ ▶
  •  
    object = []
    
    object = {}
    
    new Object()
    		
    ◀ ▶
  •  

    Prerender

    ◀ ▶
  •  
    ctx.drawImage(sprite, x | 0, y | 0)
    		
    ◀ ▶
  •  

    Don't scale or rotate in the hot path

    ◀ ▶
  •  

    Schedule onresize events

    ◀ ▶
  •  
    function scheduleResize() {
        if (resizeTimer) {
            clearTimeout(resizeTimer)
        }
    
        resizeTimer = setTimeout(resize, 200)
    }
    
    document.onresize = scheduleResize
    		
    ◀ ▶
  •  

    Run in Full Screen Mode on Mobile Devices

    ◀ ▶
  •  

    Meta Tags on iOS

    ◀ ▶
  •  
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
    		
    ◀ ▶
  •  

    manifest.webapp for Chrome on Android

    ◀ ▶
  •  
    {
        "name": "...",
        "installs_allowed_from": ["*"],
        "fullscreen": "true"
        ...
    }
    		
    ◀ ▶
  •  

    manifest.json for Firefox

    ◀ ▶
  •  
    {
        "start_url": "index.html",
        "display": "standalone"
        ...
    }
    		
    ◀ ▶
  •  

    Texture atlas

    ◀ ▶
  •  
    ctx.drawImage(
        src, sx, sy, sw, sh,
        dst, dx, dy, dw, dh)
    		
    ◀ ▶
  •  

    devicePixelRatio / backingStorePixelRatio

    ◀ ▶
  •  
    ratio =
        (window.devicePixelRatio || 1) /
        (ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio ||
            1)
    		
    ◀ ▶
  •  
    width = window.innerWidth * ratio | 0
    height = window.innerHeight * ratio | 0
    
    for (var i = 0; i < pointerLength; ++i) {
        pointersX[i] = pointersX[i] * ratio | 0
        pointersY[i] = pointersY[i] * ratio | 0
    }
    		
    ◀ ▶
  •  

    ctx.fillRect() versus ctx.clearRect()

    ◀ ▶
  •  
    ctx.getContext("2d", {alpha: false})
    		
    ◀ ▶
  •  

    Use two canvas on top of each other

    ◀ ▶
  •  

    Avoid State-Changes

    ◀ ▶
  •  
    // avoid state changes
    ctx.save()
    ...
    ctx.restore()
    
    // group by path
    ctx.beginPath()
    ...
    ctx.closePath()
    
    // group by fillStyle
    ctx.fillStyle = “red”;
    		
    ◀ ▶
  •  

    WebAudio

    ◀ ▶
  •  
    • Web Audio API
    • Using Web Audio
    ◀ ▶
  •  

    Profile

    ◀ ▶
  •  

    Just do it!

    ◀ ▶
  •  

    Ressources

    ◀ ▶
  •  
    • Getting started with HTML5 game development
    • Guides for Game Development
    • Optimizing HTML5 Canvas Games
    • Optimizing for Firefox OS
    • Canvas Cheat Sheet
    • HTML5 Game Devs
    • SVG guide
    • Guide to HTML5 Canvas Libraries
    ◀ ▶
  •  

    Questions?

    ◀ ▶
  •  

    Thanks!

    ◀