Boxworld.js
A 2.5D CSS/DOM Game engine
10 Jan 2022
When I started learning development I remember how amazed I was that you could go onto any website, right click and see how it was built.
The canvas
element always seemed like a black box to me. Sure, you can dig into the source code, but the inspector just shows a single element.
Boxworld.js is my attempt at making a rudimentary game engine using just CSS and divs.
How it works
Well, it’s boxes. A grid of divs, animated when the player moves.
I started out with react, but couldn’t make the setState play ball with my game loop and requestAnimationFrame, so I ended up using vanilla JavaScript.
The game map is defined as an arrays of tiles. For example, here’s the “ground” layer:
const g1 = "grass";
const g2 = "grass-b";
const field = [
[g1, g1, g1, g1, g1, g1, g1, g1, g1, g1, g1],
[g1, g1, g1, g1, g1, g1, g1, g1, g1, g1, g1],
[g1, g1, g1, g1, g1, g1, g1, g1, g1, g1, g1],
[g1, g1, g2, g1, g1, g1, g2, g1, g1, g1, g1],
[g1, g1, g1, g2, g1, g1, g1, g1, g1, g1, g1],
[g1, g1, g1, g1, g1, g1, g1, g1, g1, g1, g1],
[g2, g2, g2, g1, g1, g1, g1, g1, g1, g1, g1],
[g2, g2, g2, g1, g1, g2, g1, g1, g1, g2, g1],
[g2, g2, g2, g1, g1, g2, g2, g2, g1, g1, g1],
[g1, g1, g2, g2, g2, g2, g2, g2, g2, g1, g1],
[g1, g1, g1, g1, g1, g1, g1, g1, g1, g1, g1],
];
I combine three of these layers, so that each “tile” has an image for the ground, one for what’s above the ground, for example, a tree trunk, and the last one for the tree itself.
Example showing the grid of tiles:
The tiles, or divs are rotated and translated based on their layer. The biggest bonus of using this approach is that adding effects to the game are really easy. It’s just CSS, so you could blur, zoom and rotate any element.
When interacting with things in the game I change the scale
and rotate
transform values on the outer frame with a CSS class. The animation comes for free since I’ve added transition: transform 0.5s ease;
to the frame.
The javaScript part
There’s honestly not that much code, so if you’re interested I’d skim through the source code.
I split the library logic into the following files:
engine.ts
- Wrap the below together and provide a simple API for the user.loop.ts
- The main game loop, runs update/render at (hopefully) 60fps.update.ts
- Update the game state each frame.render.ts
- Render the game each frame.input.ts
- Handle user input.
Most of the logic is very basic and standard. The biggest challenge was getting the map animation to run smoothly, especially at the point when the tiles at the edge are swapped.
I found that using createDocumentFragment()
and appendChild()
instead of setting the innerHTML shaved off a few milliseconds per frame.
Assets and game logic
As it turns out, making a game is a lot of work. I probably spent around 30% of the time on the engine, and 70% on the game I made to test the logic.
Below is a screenshot from the figma file I used to create the assets.
Summary
As usual, I learned a lot. The library works fairly okay, but I’m not sure how well it’d perform on a mobile device, or an old laptop. The DOM is slow they say. I quite like it though. It’s not a black box!
If nothing else, the next time I want to make a game, at least the library will be done!