
How I made a 3D game in 2 KB of JavaScript - pjmlp
http://frankforce.com/?p=7427
======
rsiqueira
The first ideas of this 3D road game was created in just 140 characters (or
less) and posted here (Dwitter) by him (Frank Force's nickname is
"KilledByAPixel"):
[https://www.dwitter.net/h/road](https://www.dwitter.net/h/road) And many
other tiny 3D effects and engines in JavaScript were created by Dwitter's
users, like these demos:
[https://www.dwitter.net/h/d3](https://www.dwitter.net/h/d3)

~~~
tomxor
Hi Rodrigo!

Just wanted to add... Dwitter is awesome for learning stuff like this, since
everything is so absurdly small you know the ceiling of time required for
understanding a dweet is pretty low - yet at the same time it's surprising how
advanced techniques can get in this tiny space.

This makes them so tempting. I sometimes struggle to pick up or return to a
technical book when work/life etc gets busy, but when someone posts an
interesting new dweet I often can't stop myself from pulling it to pieces.

------
midgetjones
Seems to be struggling a bit. Alternate link:
[https://web.archive.org/web/20200309074327/http://frankforce...](https://web.archive.org/web/20200309074327/http://frankforce.com/?p=7427)

~~~
itronitron
here is the codepen link from same article >>
[https://codepen.io/KilledByAPixel/pen/poJdLwB](https://codepen.io/KilledByAPixel/pen/poJdLwB)

~~~
TruthSHIFT
FYI, there is no jump button. Also, those are rocks, not ramps. Jumping is not
a gameplay mechanic in this game.

~~~
KilledByAPixel
Double click to jump.

~~~
krossitalk
Thanks. Wish the controls were more obvious. After learning this the game is
actually quite playable.

------
sbr464
You could get the minified code size lower with a few tricks below. This
allows the minifier to stop using the longer method names. It also has the
benefit of eliminating an object property lookup which is significantly slower
than accessing a variable (under a microscope of course, if called many
times). Probably 5-10% savings by doing this for the Math methods.

1\. Create variables for any global/builtins (setTimeout/clearTimeout,
requestAnimationFrame, etc) at the top of the file.

2\. Create variables for global object properties, especially if used multiple
times or they have longer names (Object.assign, Object.hasOwnProperty,
Math.cos, Math.sin , JSON.stringify).

3\. For non globals, within functions, create variables for any object
properties or methods that are called multiple times to be worth it; like in
formulas where obj.width/height is called 4-5 times, the minifier can't
optimize the property name.

4\. For Class instance methods that don't allow creating variables for methods
(You have to call the method with the class, since they operate on "this"
context) but don't have anything unique about "this", you can create a
variable once within a function, then just bind itself. If you are using it
multiple times to be worth it.

    
    
      const classMethod = ClassInstance.classMethod.bind(ClassInstance)
    

would be minified to

    
    
      const Z = X.classMethod.bind(X)
    

and (in the minified code) used as Z() instead of X.classMethod()

5\. In longer functions that use "this.whatever" many times, you can create a
variable once for "this" (t or _this etc) and the minifier will be able to
eliminate those bytes.

------
jsgamedev
This is very impressive!

Lots of counterintuitive observations when you realize the objective is for
compressed 2k size, which means that duplicate code is actually a good thing!

It reminds me of this 3d maze in 1k of javascript from many years ago:
[https://js1k.com/2010-first/demo/459](https://js1k.com/2010-first/demo/459)

~~~
KilledByAPixel
Thanks, I also referenced this jk1k racing game in the post...

[https://js1k.com/2019-x/demo/4006](https://js1k.com/2019-x/demo/4006)

------
throwaway77384
I absolutely love projects like this. It's so interesting working within these
constraints.

So, js1k is not continuing? On the js1k site I can't see anything stating
this. Nothing on the subreddit either. Where is that information coming from?

It's a real shame that this great tradition may not continue, though, perhaps
the js2k+ will be the next big thing ;)

~~~
woogley
Only thing I can find is this tweet from the organizer.
[https://twitter.com/kuvos/status/1213546333707083778](https://twitter.com/kuvos/status/1213546333707083778)

~~~
pjmlp
Love the WASM remark on his tweet.

Think WASM coding like the original Demoscene demos.

~~~
tracker1
Not sure about the 2k+zip, but I do think that there will be a _LOT_ of
progress in wasm+canvas for interactive programming... of course this leaves
out accessibility, so hopefully it's done with some constraint depending on
the space.

------
jfkebwjsbx
I love these projects, but writing statements like "full HD" and "Realistic
driving physics and collisions" is a bit too much unless it is tongue-in-cheek
:)

~~~
KilledByAPixel
HD because many tiny games like this do not stretch to fill the whole window.
It requires extra code to do this and have everything scale properly.

~~~
buzzerbetrayed
I've never heard "HD" be used to mean "full width"

~~~
KilledByAPixel
Do you have an HD monitor? If so the game is in HD. Many tiny games and demos
like this are rendered to a smaller canvas and will either not stretch to fill
your monitor or are rendered at a lower then HD resolution.

~~~
jfkebwjsbx
When people use that term for games, it usually means the assets have been
reworked to look better.

In this case, I guess that would be the procedural generation of the
background and trees, for instance, to give it better quality than simple
triangles.

~~~
path411
No, it means the assets have been reworked to support a higher resolution.

~~~
jfkebwjsbx
That's what I said.

------
nathell
Also in the genre of tiny racing games, this 2004 entry from IOCCC comes to
mind:

[https://www.ioccc.org/2004/vik1.c](https://www.ioccc.org/2004/vik1.c)

------
sccxy
Controls:

Mouse = Steer

Click = Brake

Double Click = Jump

[https://github.com/KilledByAPixel/HueJumper2k](https://github.com/KilledByAPixel/HueJumper2k)

------
agys
In the minified code there are still a lot of Math.PI, Math.sin, Math.cos…
could this not be optimised further with something like s=Math.sin…?

~~~
vanderZwan
I was about to say _" well, it's probably a lot of work to verify that s isn't
already a mangled variable name"_ but then I realized that doing what you
suggested in the pre-minified code should work, yeah.

So not only are you right, it's actually super easy. Just do this at the top
level of your code:

    
    
        const {cos, sin, PI /* etc */} = Math;

~~~
KilledByAPixel
You could do that, but it would not compress as well. For example the word
const is not use anywhere in the 2k version, while Math. is used about 40
times so it becomes essentially free when compressed.

~~~
vanderZwan
> _For example the word const is not use anywhere in the 2k version, while
> Math. is used about 40 times so it becomes essentially free when
> compressed._

Except that the word const does seem to be used in the uncompressed version,
so we don't know what happens during compression in the Closure compiler, no?
Or is the 2k version pre-Closure compiler different from the uncompressed
version?

Either way the only way to know is to try because asymptotically free is all
well and good, but without measurement we don't know the actual behavior in
practice and we are just making assumptions about how well or how badly things
compress and how the mangling process works out.

~~~
KilledByAPixel
Ok, I just tried it. Removing every Math. and adding
let{cos,sin,PI,min,max,tan,atan2,abs,ceil}=Math; to the top. It worked better
then I expected, though it is still 1 byte larger then without. Could
potentially be a win in some situations.

~~~
vanderZwan
Nice! Thanks for trying!

> _it is still 1 byte larger then without_

That's actually pretty cool, because together with your earlier remark of ~40x
occurrences of Math.<whatever> that means we have a ballpark figure for where
the tipping point is!

------
notkaiho
And thanks to this article I've learned about the Google Closure compiler:
[https://developers.google.com/closure/compiler](https://developers.google.com/closure/compiler)

~~~
Cthulhu_
Is it still relevant and updated nowadays? IIRC that one's been around for at
least a decade already.

~~~
lvh
Absolutely. It took a long time for everyone to catch up on basic features
(Closure had modules figured out while the rest of the JS community still had
several competing standards), but it’s still tremendously effective.

The only downside is the compiler itself runs on the JVM, and most developers
ostensibly prefer a single platform. Given how much other software you need to
make a development environment work, this seems silly to me.

~~~
Scarbutt
A real downside and the reason it never took off, is that you have to annotate
all your Javascript code to really leverage the Closure compiler.

Google also doesn't really advertised the use of Closure, you have to go out
of your way to find it in their developer website or google it to find it.
It's really just an internal tool.

They break backwards compatibility in the Closure Library so much that
projects that adopted Closure (like Clojurescript), are stuck with a library
from 2017. The project is maintained only to serve Google.

~~~
nathell
> They break backwards compatibility in the Closure Library so much that
> projects that adopted Closure (like Clojurescript), are stuck with a library
> from 2017. The project is maintained only to serve Google.

Is that the case? The current version of ClojureScript depends on
org.clojure.google-closure-library 0.0-20191016 – I know it's a fork, but I'm
not sure to what extent it is being synced with upstream.

~~~
Scarbutt
Their repo says they are using version "0.0-20170809-b9c14c6b"

[https://github.com/clojure/clojurescript/blob/master/deps.ed...](https://github.com/clojure/clojurescript/blob/master/deps.edn)

[https://github.com/clojure/clojurescript/blob/master/project...](https://github.com/clojure/clojurescript/blob/master/project.clj)

------
tabtab
I made a 2D game in 3 KB of JavaScript. Not quite as impressive I suppose.

------
aylmao
In the final, minified code there's a lot of calls to Math methods (sin, cos,
tan). I wonder if there's potential to shrink this even more with some
assignments:

    
    
        M=Math,s=M.sin,c=M.cos,p=M.PI,a=M.abs; // etc.

~~~
carterehsmith
That's what minifiers/uglifiers do? Perhaps OP did not want to use them.

~~~
aylmao
As far as I know minifiers don't do this since it changes semantics. Take this
for example:

    
    
        // 1
        doSomething();
        console.log(Math.sin(2));
    
        // 2
        var s = Math.sin;
        doSomething();
        console.log(s(2));
    
    

Now, consider this definition for `doSomething()`:

    
    
        function doSomething() {
          Math.sin = x => x
        }
    
    

Now, the first prints 2 and the second snippet prints 0.9092.

Keeping track of globals to get around this is complicated. Even if you do,
you can't always be sure the behaviour of the code isn't changing since
`doSomething` could be defined in a module your minifier doesn't know about.

------
6510
It looks great, runs smoothly but I'm not having a gameplay experience.

~~~
notkaiho
What do you mean by a 'gameplay experience' in this case?

~~~
6510
I don't want to hate on the creation at all but its hard not to sound like
that in text.

On commodore 64 games tried to have nice graphics, nice music and interesting
mechanics. Lots of games failed at 1 of the 3. Sometimes they did really well
with the other challenges but with few exceptions it doesn't make up for it.

I think if 2 kb is the challenge the game mechanics are the best hope to make
up for the lack of sound and visuals.

Reminds me of burning rubber. A game with truly ugly graphics and terrible
music that is quite hilarious to play.

[https://www.youtube.com/watch?v=nKSbOyGy_Qo](https://www.youtube.com/watch?v=nKSbOyGy_Qo)

~~~
egypturnash
...which is a shameless clone of Data East’s Bump ‘n’ Jump/Burnin’ Rubber,
which had much better art and sound.
[https://m.youtube.com/watch?v=99gnxjpeL7s](https://m.youtube.com/watch?v=99gnxjpeL7s)

~~~
6510
haha, so they kept only the game play. It makes a lot more sense now.

------
iron4o
Why are there trees in them middle of the road? ;D

~~~
KilledByAPixel
Alien trees...

------
franblas
Nice ! :) Looks like a raycasting engine like this one
[https://www.playfuljs.com/a-first-person-engine-
in-265-lines...](https://www.playfuljs.com/a-first-person-engine-
in-265-lines/)

~~~
KilledByAPixel
Nope! It is not a raycasting engine at all, but instead built similar to old
school racing game tech.

~~~
winrid
Could you help us understand how it's not ray casting? OP doesn't mean ray
tracing by the way. Ray casting is the tech used in Wolf3D for example, or
maybe I'm preaching to the choir.

~~~
fenwick67
Since the car only goes forward, there's no reason to have any depth or
visibility testing, things are just rendered in order (painter's algorithm).

------
dlsso
Makes you wonder if the 70gb installs of modern games are really necessary.

Also, obligatory kkrieger link:
[https://en.wikipedia.org/wiki/.kkrieger](https://en.wikipedia.org/wiki/.kkrieger)

~~~
ssully
It's an impressive project, but it shouldn't make you question why games are
so large these days.

~~~
KilledByAPixel
I think there is a middle ground of many indie games I see on steam that are
much larger then they should really be.

As a dev, one problem I saw often is UE4 is massive, well over a hundred meg
for just the base engine.

~~~
meheleventyone
Binary size is a drop in the ocean most of the size of game installs is in
assets.

~~~
Sohcahtoa82
While true, you do have to ask how the hell there could possibly be 100 MB of
_just code_.

Consider Windows calculator. In Windows 10, it consumes 15+ MB of RAM. When
desktops have 8+ GB of RAM, 15 MB is essentially nothing. And yet, 15 MB is an
astronomical amount of RAM considering the functionality Calculator offers.
There's really no reason it should be more than a couple hundred kilobytes.

~~~
rafaelvasco
I didn't have a look at the code but the binaries aren't just code. Apart from
things like embedded icons and stuff, there can be lots of engine embedded
resources inside;

------
mosselman
The game is pretty fun and playable for its size. Nice work!

------
m00dy
damn,

I was reading the blog post and mistakenly refreshed the page. Now, BW is
exhausted. I didn't know...

------
pettycashstash2
resource limit reached.....

------
IvanK_net
It reminds me my 3D demo in 47 lines of JS (without WebGL) :)
[https://jsfiddle.net/06L845jx/86/](https://jsfiddle.net/06L845jx/86/)

And 54 lines of JS:
[https://jsfiddle.net/06L845jx/127/](https://jsfiddle.net/06L845jx/127/)

~~~
numlock86
> It reminds me my 3D demo in 47 lines of JS

How exactly?

