One of the things I did on my recent sabbatical was to start coding a video game in JavaScript. I learned a lot in the process, and I thought I’d write down some of my experiences. (For the record, while I wrote the game engine myself, I used the excellent Pixi library for graphics.)
Classes are useful after all
The more I’ve written code in JS the more I’ve eschewed classes. JavaScript’s primitive object type is pretty succinct and powerful already. Also, the prototype
object model has an awkward syntax when using new
. The class
keyword makes this better, but can increase confusion due to call site binding (class fields with arrow functions fix this, but that feature is still in stage 3). It doesn’t help that the new
keyword is misleading to those familiar with classical inheritance. Lastly, using class
makes it easier to use inheritance instead of composition.
I don’t actually think of any of these as problems with the language, since I find it easy to write clear code with just functions that pass around data. In my experience, web applications do best when they never mention the new
keyword at all. However, my first attempt to apply this methodology to game development ended in quite a mess.
It turns out that while most of the web application data I’ve dealt with is easily represented and moved around using primitive types, the entities in a video game have so much internal state and so much interactive functionality that a one-way flow approach (like the one popularized by React) is terribly inefficient. The way I did it, rendering each frame was a nightmare of indirection.
Keeping the state and functions together within each object and sharing those functions using inheritance ended up being a whole lot simpler. It also meant that I had real object types that could be validated using Typescript.
Math is useful after all
When I tell people I’m a programmer, they often think it means I’m great at math. I’m not great at math; that’s why I like computers so much! But it turns out that dealing with virtual objects in a virtual space requires quite a bit of algebra, trigonometry, and calculus.
It doesn’t help that most of the game development tutorials I came across assume that you’re using an existing game engine to do the work. The first time I tried to figure out how to calculate the angle between two sprites, all I could find was instructions for slerp
ing quaternions.
Do you know what a quaternion is, or spherical linear interpolation? Well, they’re totally unnecessary in this case. In 2d space, all I really needed was arctan2()
, which I probably learned about in high school and subsequently forgot. But it took me a while to figure that out. I think that if I had been taught math in school in the context of game development, I probably would have paid a lot more attention.
Making a scripting language is great
As I was developing events in the game, I wanted a way to easily centralize dialog such that I didn’t need to write code to create conversation trees (actually they’re graphs!). I did this by creating a JSON file that encoded dialog nodes, each with their own responses, and links between those responses and other nodes.
This worked really well, but I quickly realized that I needed to also encode functions into these nodes so that actions could be taken when certain choices were made. When a player insulted the captain of an enemy ship, for example, I wanted to decrease the happiness of that character and possibly have the enemy ship attack. Furthermore, I found that when a ship did attack, I wanted to encode the ship’s behavior itself. In short, I began to write the content of my game in a script.
At that time I was putting JavaScript functions into my JSON file to encode actions. That meant that it wasn’t really JSON anymore. If I wanted to serialize my nodes, I’d have to put those functions into strings. This immediately felt wrong because I’d have to let my game engine execute arbitrary JavaScript, which is about as big of an anti-pattern as you can get.
Professional game engines are not written in JavaScript, so they can use JS as a scripting language, but I would have to do something else if I wanted to allow executing arbitrary scripts. I began to wonder if I could write my own, very simple scripting language for the purpose. I was fortunate to discover Nearley which made that both possible and fast.
I first wrote a grammar that looked something like JavaScript, except without any variables or named function definitions (although there were anonymous functions). Statements were composed of expressions which were composed of function calls and primitives. The functions were all pre-defined; this was my game’s API. All of that worked pretty well, but I ran into a problem when I added if
statements for control flow.
Each statement had to end with a semicolon. Since the language’s programs had to be encoded as a JSON string, and JSON strings cannot have newlines, it was imperative that I could chain statements together on one line. However, if
statements in most languages do not end with a semicolon. I had a really hard time encoding this into the grammar, and so each if
statement had to end in a semicolon as well. With that requirement I found conditions really awkward to write.
if (distanceToPlayer() < 5) { if (getNpcHappiness('enemy1') > 2) { gotoNode('enemy1Greeting'); }; if (getNpcHappiness('enemy1') <= 2 { attackPlayer(); }; };
Eventually I realized: I had multi-argument functions and I had anonymous function definitions… so I could encode control flow as a function call! That is, instead of this procedural condition statement:
if (distanceToPlayer() > 5) { accelerate(); };
I could write this functional version:
if(distanceToPlayer() > 5, { accelerate(); });
The difference was subtle but powerful. I was able to remove nearly half of the grammar I had written, since the function call syntax was already supported.
My scripting language is not extremely powerful, but even with a simple grammar in place I was able to write most of the events of the game entirely in one JSON file. I can see how it would be possible to write a dedicated dialog and level editor for that file, which would totally separate the game and its engine.
Conclusion
I’d love to show you my game, but I was pulled into other adventures on my sabbatical before I was able to finish writing it, and it has a long way to go. Maybe I’ll pick it up some time in the future, but for now I’m happy about all the things I learned getting it as far as I did. If you want to look at my code, feel free to browse the repo!