This post is a Celestial Combat devlog and also be found here.
Writing code is a necessary evil to get things done on a project and often the best kind of code is no code at all. Good tooling helps us do things:
- FASTER: avoid writing code when the task least requires it
- CHEAPER: avoid writing code when results are not worth the effort
- EASIER: avoid barriers for contributors of all skill levels
Creating game art (EASIER)
CelCom exclusively uses vector graphics, which is one of the simplest and most space-efficient ways to represent art in a game. Using vector graphics is a trade off in favor of using more CPU for less disk space. Vectors also don’t lose resolution after scaling.
All art in CelCom is expressed as polygons: arrays of points in sequential order. For example, a simple triangle is
[ [0,0], [8,0], [0,8], [0,0] ]
.
As you might imagine, art that’s even a little bit more sophisticated would be very tedious to code by hand.
If we want to easily add to and iterate our game art we need an editor that the artist can draw inside and use to export
to our game-native vector format.
The geometry editor tool was created to do this. It can also be run locally:
yarn watch
- http://localhost:3000/?editor
The editor can create layers of polygons with an infinite undo history. See LETTERS
within src/client/constants.ts
for an example of the output. Further tweaks to the resulting coordinate arrays is possible with the simple helper
function transformPolygon()
inside src/client/Geometry.ts
.
Creating builds (FASTER + CHEAPER)
Previously the project used Webpack to create a single client-side JS file that would minimize the number of network requests as well as total load time. As of 2018, webpack is the standard bundler for many JS projects.
It has a vast collection of plugins for configuring and transforming code and assets. This is also its main downside: you need to configure these plugins individually and orchestrate them. The level of effort required for the initial setup and continued maintenance (if you want it to build more things / more types of things) is sizable.
webpack.config.js
files easily become sprawling messes of interdependent configs on large projects. Occasionally
drawing attention away from feature development simply to keep the bundler working acceptably as the codebase scales.
Eventually, some people got sick of doing that and proposed a better approach: avoiding as much as config as possible while achieving the same purpose of compiling client-side resources.
“Zero-config” bundler
ParcelJS is one such alternative to the config hell that many webpack projects become. It began a trend of thought for build tools in 2018 that quickly gained momentum. You install a single dependency for the bundler, point it to an entry point for each bundle / resource to produce and it takes care of the rest. Plugins are automatically installed when required, allowing the developer to focus more of their time on building features vs messing with the compilation toolchain.
Switching over to [Parcel] vs Webpack caused:
- 61 + 19 = 80 lines of dense config code to disappear; simply not needed!
- replaced by a handful of
package.json
script commands
- replaced by a handful of
- initial “watch” build time dropped from
12.18s
to10.85s
- “changed” build times dropped from order of tens of seconds to tens of milliseconds! (greatest benefit, time-wise)
- complete production build time dropped from
30.30s
to28.21s
The timings above were gathered from running the bundlers on my 2013 Macbook Air. Parcel is the clear winner here. It is not completely perfect however, some things that were straightforward in Webpack need clever workarounds / new ways of thinking with Parcel:
-
Including resources into the bundles was not obvious: you need to
require()
sound files for instance. Seescripts/generateSoundsList.js
andsrc/client/assets/sounds.ts
as examples. -
Some functionality was lost in regards to the Parcel plugin ecosystem not having equivalents to the Webpack counterparts: compiling markdown (
.md
) files had to be done via a short Node script instead. -
TypeScript support is good but the bundler does not do a typecheck during the transpilation. If you want to see build errors, you either need editor support (IntelliJ / VSCode will do this in the background) or you need to run the TS compiler in watch mode in a separate process.
Parcel can also be huge in terms of number of its child dependencies within node_modules/
, whereas this is
limited with Webpack (being explicitly defined). Not really a major concern for people not developing on limited spec
environments. Finally, the experimental scope hoisting functionality did not work well on this project owing to Parcel’s
lack of maturity. It created super-minified builds that did not work.
Not quite “zero-config” but much closer than before!
Releasing builds (CHEAPER + EASIER)
Hosting CelCom is provided by GitHub Pages: each commit into the master
branch of the git repository publishes the
codebase as a statically served website hosted on GitHub CDNs. This means publishing things is simple:
- no additional hosting is needed. same as version control provider
- publishing new builds is done by modifying files and then committing them
- general public receive updates immediately: cache time is < 1 hour
All we need to do to see which builds a client is receiving is to create a readily visible build tag that has the
timestamp of build and the commit it built from. We use the .env
approach here offered by Parcel and regenerate it
for each production build via scripts/generateDotEnv.js
Example:
BUILD_HASH=929e057
BUILD_DATE=2019-01-02T21:51:52.227Z