Code Weaving (in JavaScript)
In the previous post I showed a demo named “code weaving”, in which
points randomly float around, with connected lines forming a fascinating,
lively web of drifting triangles. The animated GIF showing the end result
barely succeeded in doing it justice. At the end of the post, I mused about
clicking the mouse to create waves in the pool.
So, for a
vacation workacation side project I rewrote the thing
in JavaScript, and so for the first time ever we now have an interactive demo
included in a DevCry post.
JavaScript is by no means ‘my thing’, and a lot of seasoned programmers absolutely diss it. Do I think it’s a good programming language? NO! But I had an immensely good time toying with it nevertheless.
The neat thing about JavaScript in the browser is that it comes with “batteries included”: graphics, mouse and keyboard input handling, audio API, what more do you need to get a little gamedev going.
Click and drag the mouse to make the points scare away from the mouse pointer.
While the browser gives us a lot to get started with gamedev, there are some issues to deal with, that even your favorite AI isn’t able to answer properly.
Spaces
Browsers scale content because displays have differing resolutions. This scaling gives issues when it comes to mouse clicks, as mouse events are reported in “client” coordinates.
You can create a nice retro style pixelated look by specifying a canvas with fixed size (e.g. 320x240) and then using CSS styling to scale it to 100% percent page width. Mouse clicks need to be transformed back to canvas space:
canvas.addEventListener('mousedown', (event: MouseEvent) => {
let r = canvas.getBoundingClientRect();
const relative_x = event.clientX - r.left;
const relative_y = event.clientY - r.top;
const scale_x = canvas.width / r.width;
const scale_y = canvas.height / r.height;
mouse_down = true;
mouse_x = Math.trunc(relative_x * scale_x);
mouse_y = Math.trunc(relative_y * scale_y);
});
The weave demo shown above does not have a fixed size canvas; I wanted to have sharp lines (no pixelated look), so there the canvas resolution changes when the window is resized. The rendering is resolution independent; each point is transformed from world to screen (canvas) space, and mouse clicks are transformed back to world space.
[Note: It is possible to do a hack version in which the points live in screen space rather than world space. This saves us from having to do transforms upon every frame. It is hacky however, and the cleaner solution is to have a proper world space].
Fix Your Timestep (JavaScript version)
The performance of JavaScript engines varies across different browsers, resulting in different frame rates. The best thing you can do is fix your timestep.
The idea is that the tick rate that drives the game is a constant,
hand-picked by you (the developer), for example 60 Hz. We’re just going to
keep time and call update()
and draw()
at our own rate.
// fixed timestep at 60 Hz
const timestep = 1.0 / 60;
const freq = Math.trunc(1000 * timestep);
let gameticks = 0;
let time0 = Date.now();
function gameloop(_ticks: number): void {
let time1 = Date.now();
let dt = time1 - time0;
time0 = time1;
if (dt < 0) {
dt = 0;
}
gameticks += dt;
if (gameticks >= freq) {
gameticks %= freq;
update(timestep);
draw();
}
requestAnimationFrame(gameloop);
}
// main
requestAnimationFrame(gameloop);
Ta-da, now we have consistent frame rates across browsers.
Number types (or lack thereof)
JavaScript lacks typing, which is not great. Typing tends to weed out small,
hard to spot bugs. Luckily there is TypeScript that has type annotations.
TypeScript compiles translates to JavaScript, which then in turn gets
executed by the browser.
My biggest gripe with JavaScript however is that it makes no distinction
between integer and float whatsoever—everything is just a number
, which
internally is a double precision floating point number.
There is no integer type in the language at all. Consequently code
is littered with Math.trunc()
because I want to ensure we have straight
integers most of the time. Sadly enough, TypeScript doesn’t help us here;
again, there is only the number
type that secretly is a float.
Anyway, this was a fun little code experiment, and it sure was a refreshing break from the serious stuff.