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