From 0 to 60 FPS

A look into Game Development for the Web

Game Development

Web

A look into

for the Web

for the

JavaScript

Single-Threaded

Non-Blocking

Asynchronous

Concurrent

Language

Game Development 101

The Game

Graphic Design

"Artificial Intelligence"

Physics

Sound Design

Input Events

Code

What can we use on the Web?

Canvas

WebGL

WebAssembly

Easy to understand and use, can develop 2D simple games

Requires some JS and Computer Graphics Skills, can develop 2D and 3D games

Simply hardcore if written by hand, fastest execution ever in the browser, can develop 2D and 3D games

Canvas

WebGL

WebAssembly

Canvas, mate, what are you?

<canvas>

Hey, I'm an HTML5 element

With my help, the browser can draw graphs, create fancy animations, make photo compositions and real-time video rendering.

Basically I can do anything that involves Computer Graphics.

I can't handle 3D rendering, shaders or real-time lighting of scenes by my own though.

Anybody can paint on a canvas

<html>
<head></head>
<body>
    <canvas id="JSPub" width="400" height="400"></canvas>
    <script>
        const canvas = document.getElementById('JSPub');
        const context = canvas.getContext('2d');
        .
        .
        .
    </script>
</body>
</html>
<script>
        .
        .
        .
        let x = 175;
        let y = 0;

        function draw() {
            context.fillStyle = '#000';
            context.fillRect(0, 0, 400, 400);
            
            context.fillStyle = '#fff';
            context.fillRect(x, y, 40, 40);
        }

        function update () {
           y < 400 ? y += 5 : y -= 400
        }
        .
        .
        .
</script>
<script>
        .
        .
        .
        function main() {
            (function loop() {
                update();
                draw();
    
                window.requestAnimationFrame(loop, canvas);
            })();
        }

        main();
        .
        .
        .
</script>
<html>
<head></head>
<body>
    <canvas id="JSPub" width="400" height="400"></canvas>
    <script>
        const canvas = document.getElementById('JSPub');
        const context = canvas.getContext('2d');
       
        let x = 175;
        let y = 0;
    
        function main() {
            (function loop() {
                update();
                draw();
    
                window.requestAnimationFrame(loop, canvas);
            })();
        }
    
        function update () {
           y < 400 ? y += 5 : y -= 400
        }
    
        function draw() {
            context.fillStyle = '#000';
            context.fillRect(0, 0, 400, 400);
            
            context.fillStyle = '#fff';
            context.fillRect(x, y, 40, 40);
        }
    
        main();
    </script>
</body>
</html>

Demo time

Canvas

WebGL

WebAssembly

I'm a JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins.

I am rather performant because I conform closely to the OpenGL standard

I have two personalities: WebGL 1.0 and WebGL 2.0. The first uses OpenGL ES 2.0 while the latter uses OpenGL ES 3.0

Canvas is my BFF. You'll always see us together at better and at worst!

WebGL? Identify yourself!

Is this real life?

Or is this just fantasy?

Or is this just fantasy?

Lending a helping hand

You're a wizard, WebGL

<html>
<head></head>
<body>
    <script src="https://cdnjs.cloudflare.com
    /ajax/libs/three.js/98
    /three.min.js"></script>
    <script>
        .
        .
        .
    </script>
</body>
</html>
const scene = new THREE.Scene();

const camera 
    = new THREE.PerspectiveCamera( 
        75, 
        window.innerWidth/window.innerHeight, 
        0.1, 
        1000 );

const renderer = new THREE.WebGLRenderer();

renderer.setSize( 
    window.innerWidth, 
    window.innerHeight );
document.body.appendChild( renderer.domElement );
const geometry = 
    new THREE.BoxGeometry( 20, 20, 20);

const material = 
    new THREE.MeshLambertMaterial(
        {color: 0xdddddd});

const light = 
    new THREE.HemisphereLight( 
        0xffffff, 
        0x000000, 
        1 );

const mesh = 
    new THREE.Mesh( geometry, material );
scene.add( mesh );
scene.add( light );

const animate = function () {
  requestAnimationFrame( animate );

  mesh.rotation.x += 0.01;
  mesh.rotation.y += 0.01;

  renderer.render( scene, camera );
};

animate();
<html>
<head></head>
<body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
        camera.position.z = 40;
        
        const geometry = new THREE.BoxGeometry( 20, 20, 20);
        const material = new THREE.MeshLambertMaterial({color: 0xdddddd});
        
        const light = new THREE.HemisphereLight( 0xffffff, 0x000000, 1 );
        const mesh = new THREE.Mesh( geometry, material );
        
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );
        
        scene.add( mesh );
        scene.add( light );
        
        const animate = function () {
          requestAnimationFrame( animate );
        
          mesh.rotation.x += 0.01;
          mesh.rotation.y += 0.01;
        
          renderer.render( scene, camera );
        };
        
        animate();
    </script>
</body>
</html>
<!doctype html>
<html>
   <body>
      <canvas width = "570" height = "570" id = "my_Canvas"></canvas>

      <script>
         /*============= Creating a canvas =================*/
         var canvas = document.getElementById('my_Canvas');
         gl = canvas.getContext('webgl');

         /*============ Defining and storing the geometry =========*/

         var vertices = [
            -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1,
            -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1,
            -1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1,
            1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1,
            -1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1,
            -1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1, 
         ];

         var colors = [
            221,221,221, 221,221,221, 221,221,221, 221,221,221,
            221,221,221, 221,221,221, 221,221,221, 221,221,221,
            221,221,221, 221,221,221, 221,221,221, 221,221,221,
            221,221,221, 221,221,221, 221,221,221, 221,221,221,
            221,221,221, 221,221,221, 221,221,221, 221,221,221,
            221,221,221, 221,221,221, 221,221,221, 221,221,221
         ];

         var indices = [
            0,1,2, 0,2,3, 4,5,6, 4,6,7,
            8,9,10, 8,10,11, 12,13,14, 12,14,15,
            16,17,18, 16,18,19, 20,21,22, 20,22,23 
         ];

         // Create and store data into vertex buffer
         var vertex_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

         // Create and store data into color buffer
         var color_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
         gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

         // Create and store data into index buffer
         var index_buffer = gl.createBuffer ();
         gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
         gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

         /*=================== Shaders =========================*/

         var vertCode = 'attribute vec3 position;'+
            'uniform mat4 Pmatrix;'+
            'uniform mat4 Vmatrix;'+
            'uniform mat4 Mmatrix;'+
            'attribute vec3 color;'+//the color of the point
            'varying vec3 vColor;'+

            'void main(void) { '+//pre-built function
               'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);'+
               'vColor = color;'+
            '}';

         var fragCode = 'precision mediump float;'+
            'varying vec3 vColor;'+
            'void main(void) {'+
               'gl_FragColor = vec4(vColor, 1.);'+
            '}';

         var vertShader = gl.createShader(gl.VERTEX_SHADER);
         gl.shaderSource(vertShader, vertCode);
         gl.compileShader(vertShader);

         var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
         gl.shaderSource(fragShader, fragCode);
         gl.compileShader(fragShader);

         var shaderProgram = gl.createProgram();
         gl.attachShader(shaderProgram, vertShader);
         gl.attachShader(shaderProgram, fragShader);
         gl.linkProgram(shaderProgram);

         /* ====== Associating attributes to vertex shader =====*/
         var Pmatrix = gl.getUniformLocation(shaderProgram, "Pmatrix");
         var Vmatrix = gl.getUniformLocation(shaderProgram, "Vmatrix");
         var Mmatrix = gl.getUniformLocation(shaderProgram, "Mmatrix");

         gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
         var position = gl.getAttribLocation(shaderProgram, "position");
         gl.vertexAttribPointer(position, 3, gl.FLOAT, false,0,0) ;

         // Position
         gl.enableVertexAttribArray(position);
         gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer);
         var color = gl.getAttribLocation(shaderProgram, "color");
         gl.vertexAttribPointer(color, 3, gl.FLOAT, false,0,0) ;

         // Color
         gl.enableVertexAttribArray(color);
         gl.useProgram(shaderProgram);

         /*==================== MATRIX =====================*/

         function get_projection(angle, a, zMin, zMax) {
            var ang = Math.tan((angle*.5)*Math.PI/180);//angle*.5
            return [
               0.5/ang, 0 , 0, 0,
               0, 0.5*a/ang, 0, 0,
               0, 0, -(zMax+zMin)/(zMax-zMin), -1,
               0, 0, (-2*zMax*zMin)/(zMax-zMin), 0 
            ];
         }

         var proj_matrix = get_projection(40, canvas.width/canvas.height, 1, 100);

         var mov_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
         var view_matrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];

         // translating z
         view_matrix[14] = view_matrix[14]-6;//zoom

         /*==================== Rotation ====================*/

         function rotateZ(m, angle) {
            var c = Math.cos(angle);
            var s = Math.sin(angle);
            var mv0 = m[0], mv4 = m[4], mv8 = m[8];

            m[0] = c*m[0]-s*m[1];
            m[4] = c*m[4]-s*m[5];
            m[8] = c*m[8]-s*m[9];

            m[1]=c*m[1]+s*mv0;
            m[5]=c*m[5]+s*mv4;
            m[9]=c*m[9]+s*mv8;
         }

         function rotateX(m, angle) {
            var c = Math.cos(angle);
            var s = Math.sin(angle);
            var mv1 = m[1], mv5 = m[5], mv9 = m[9];

            m[1] = m[1]*c-m[2]*s;
            m[5] = m[5]*c-m[6]*s;
            m[9] = m[9]*c-m[10]*s;

            m[2] = m[2]*c+mv1*s;
            m[6] = m[6]*c+mv5*s;
            m[10] = m[10]*c+mv9*s;
         }

         function rotateY(m, angle) {
            var c = Math.cos(angle);
            var s = Math.sin(angle);
            var mv0 = m[0], mv4 = m[4], mv8 = m[8];

            m[0] = c*m[0]+s*m[2];
            m[4] = c*m[4]+s*m[6];
            m[8] = c*m[8]+s*m[10];

            m[2] = c*m[2]-s*mv0;
            m[6] = c*m[6]-s*mv4;
            m[10] = c*m[10]-s*mv8;
         }

         /*================= Drawing ===========================*/
         var time_old = 0;

         var animate = function(time) {

            var dt = time-time_old;
            rotateZ(mov_matrix, dt*0.0005);//time
            rotateY(mov_matrix, dt*0.0005);
            rotateX(mov_matrix, dt*0.0005);
            time_old = time;

            gl.enable(gl.DEPTH_TEST);
            gl.depthFunc(gl.LEQUAL);
            gl.clearColor(0.5, 0.5, 0.5, 0.9);
            gl.clearDepth(1.0);

            gl.viewport(0.0, 0.0, canvas.width, canvas.height);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.uniformMatrix4fv(Pmatrix, false, proj_matrix);
            gl.uniformMatrix4fv(Vmatrix, false, view_matrix);
            gl.uniformMatrix4fv(Mmatrix, false, mov_matrix);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
            gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

            window.requestAnimationFrame(animate);
         }
         animate(0);
      </script>
   </body>
</html>

Demo time

Canvas

WebGL

WebAssembly

WebAssembly,

what's all the hype about?

I'm a new type of compiled binary code, and I can be used inside the browser.

With my help, developers can write apps in their preferred language, and target the browser for a performance boost. 

I am designed to work hand in hand with JavaScript, I aid wherever there's need for near-native execution time, and I can manage memory blocks like a pro

I am not meant to be used as a Front-End language, therefore I don't have direct access to the DOM, I only have 32 and 64 bit integer and float data types, and I don't have a garbage collector

What's the magic behind it?

Interpreted

Language

JIT Compiler

Monitor

Profiler

Baseline

Compiler

Optimizing

Compiler

De-Optimizing

What's the magic behind it?

Compiled

Format

Instantiate

Execute

Lower-Level

Compiled

Language

AOT Compiler

What's the magic behind it?

WebAssembly for the future?

That's cool, but how do you even?

#include <stdbool.h>

#define WIDTH 800
#define HEIGHT 600

typedef struct Rect {
  int x;
  int y;
  int width;
  int height;
} Rect;

typedef struct Player {
  Rect paddle;
  int score;  
} Player;

...
extern void fillRect(int x, int y, int width, int height);
extern void clearRect(int x, int y, int width, int height);

...

void movePlayer(Player *player, int direction, float delta) {
  int curY = (*player).paddle.y;
  float deltaY = (delta / 1000 * PADDLE_VELOCITY);
  int newY = curY + (int)deltaY * direction;
  (*player).paddle.y = clamp(newY, (*player).paddle.height, 
                            WALL_SIZE, HEIGHT - WALL_SIZE);
}

...

void drawObject(Rect o) {
  setFill(white);
  fillRect(o.x, o.y, o.width, o.height);
}

...

void tick(float delta, int player1dir, int player2dir) {
  drawCourt();
  drawScores();
 
  checkCollisions(&ball, player1dir, player2dir);

  movePlayer(&player1, player1dir, delta);
  movePlayer(&player2, player2dir, delta);
  moveBall(&ball, delta);

  drawObject(ball.sphere);
  drawObject(player1.paddle);
  drawObject(player2.paddle);
}

That's cool, but how do you even?

emcc pong.c -o pong.wasm -s WASM=1 -Oz -g

That's cool, but how do you even?

0061 736d 0100 0000 000e 0664 796c 696e
6b90 81c0 0204 1000 014a 0d60 017f 0060
047f 7f7f 7f00 6003 7f7f 7f00 6002 7f7f
0060 047f 7f7f 7f01 7f60 027f 7f01 7f60
037f 7f7d 0060 027f 7d00 6000 0060 037d
7f7f 0060 027f 7c00 6003 7f7f 7c00 6003
7c7f 7f00 02b6 010a 0365 6e76 066d 656d
6f72 7902 0080 0203 656e 760a 6d65 6d6f
7279 4261 7365 037f 0003 656e 7609 5f64
7261 774c 696e 6500 0103 656e 7609 5f66
696c 6c52 6563 7400 0103 656e 7609 5f66
696c 6c54 6578 7400 0203 656e 760d 5f73
6574 4669 6c6c 5374 796c 6500 0203 656e
760c 5f73 6574 4c69 6e65 4461 7368 0003
0365 6e76 0d5f 7365 744c 696e 6557 6964
7468 0000 0365 6e76 0f5f 7365 7453 7472
6f6b 6553 7479 6c65 0002 0365 6e76 0c5f
7365 7454 6578 7453 697a 6500 0003 1312
0000 0405 0506 0700 0802 0802 0908 080a
...
(module
 (type $FUNCSIG$vii (func (param i32 i32)))
...
 (import "env" "memory" (memory $0 256))
 (import "env" "_fillRect" (func $_fillRect (param i32 i32 i32 i32)))
...
 (global $_player1 i32 (i32.const 60))
 (global $fp$_drawObject i32 (i32.const 8))
...
 (export "_drawObject" (func $_drawObject))
...
 (func $_setFill (; 8 ;) (param $0 i32)
  (call $_setFillStyle
   (i32.load
    (get_local $0)
   )
   (i32.load offset=4
    (get_local $0)
   )
   (i32.load offset=8
    (get_local $0)
   )
  )
 )
...
)

That's cool, but how do you even?

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d');

...

const _fillRect = (x, y, width, height) => ctx.fillRect(x, y, width, height);
const _clearRect = (x, y, width, height) => ctx.clearRect(x, y, width, height);

...

const memory = new WebAssembly.Memory({initial: 256, maximum: 256});

const emsdkEnvironment = {
  memory: memory,
  STACKTOP: 0,
  STACK_MAX: memory.buffer.byteLength
};

...
const init = async emsdkEnvironment => {
    const env = {
        ...emsdkEnvironment,
        _fillRect,
        _clearRect,
        _tick,

        ...
        
    };
    
    const {instance: {exports: pong}} = await WebAssembly.instantiateStreaming(
        fetch('pong.wasm'),
        {env}
    );
  
    const main = () => {
        requestAnimationFrame(main);
        pong._tick(delta, player1dir, player2dir);
        ...
    };
    
    requestAnimationFrame(main);
};

init(emsdkEnvironment);

What about game engines?

Demo time

Q & A

Thank You!

Examples from "Demo Times"

Canvas

Sinuous:   http://www.sinuousgame.com/

X-Type:   https://phoboslab.org/xtype/

WebGL

ThreeJS showcase:   https://threejs.org/

Xbox One Model:                                                                                             https://sketchfab.com/models/qsRPEw7hTKC4E02XMop9DUpu2wb

WebAssembly

Tanks!:   https://webassembly.org/demo/

Funky Karts:   https://www.funkykarts.rocks/demo.html

BunnyMark Benchmarks!

Canvas

WebGL

WebAssembly

http://demos.seb.ly/bunnybench/bunnies_canvas.html

https://www.goodboydigital.com/pixijs/bunnymark/

http://voidptr.io/dev/wasm-bunnies/html/#native

Useful links

Intro to WebAssembly by Lin Clark (@Mozilla - works on WA and Rust) https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/

Emscripten Documentation: http://kripken.github.io/emscripten-site/

A-Frame for WebVR: https://aframe.io/

Google's Tutorial for WebAR:                                                                              https://codelabs.developers.google.com/codelabs/ar-with-webxr/#0

Game Developer Roadmap (2018):                                                                         https://codeburst.io/the-2018-game-developer-roadmap-e07e45b3c423

Nice Visual Presentation about WebAssembly by Dan Callahan from Mozilla: https://www.youtube.com/watch?v=7mBf3Gig9io                                               

JSPub3

By alexgrigi

JSPub3

  • 668