As part of my work on Agave, I needed to find a way to render a virtual office in a web application. I also needed the ability to maintain a high engineering velocity and get high leverage out of the libraries we chose.
So far, the biggest learning experience for me has been isometric rendering. You know isometric rendering. It’s that sort of 2.5-dimension look that some video games have, where you’re sort of looking down from the top right on the map. Some examples:
Perhaps the most appropriate modern example is the intro scene to HBO’s Silicon Valley:
From the start, we had a vision of a virtual office in this style. It would be significantly more immersive than a flat (two-dimensional) map, and afforded the opportunity for some seriously cool experiences. (Plus, Jared and I both loved playing these games when we were kids.)
This was a daunting task, as I had no experience in video game design or programming. I ended up watching a ton of different YouTube videos on the topic in an effort to find my way. I didn’t really even know what I was looking for – it was sort of an immersive, random process.
I have been a longtime fan of Id software, Doom in particular, and have followed John Carmack for a long time. So, naturally I ordered a copy of The Game Engine Black Book: Doom and read it cover to cover in three days. I learned a lot!
I was most interested in how the engine worked and, in particular, rendering:
I finally figured out that what we were looking for was called isometric rendering. Here’s what I learned.
Cartesian vs. Isometric
On a 2d game, there is simply a cartesian map. It has x and y coordinates. If you were to lay out the old-school board game Risk onto its cartesian map, it would look something like this:
Conventionally the top left cell is 0,0 (the origin). So, where I live in South Lake Tahoe would be somewhere inside 5,5, or maybe 6,5.
Cartesian grids are trivial to store in memory as two dimensional arrays. And, even in games like Sim City, all of the units, characters, items, really anything on the map has a coordinate in the Cartesian plane. So to store the data for our virtual office, we’d need a simple two dimensional array that indicates where each avatar, furniture, etc all exist in the office.
Next, we needed artwork for everything. We started with floor tiles. Here’s an example of the sprite we used:
As you can see, this is not a square. It’s a square tile as seen from the top-right. It’s also not technically a perspective sprite because the lines do not converge to the horizon – they remain parallel. It’s an isometric sprite. To make things more confusing, the image file itself is a rectangular, transparent PNG. Here’s a closer look:
And, it turns out, the tile itself has a height of 5 pixels, meaning the “top” of the tile is 50×25. It’s half as tall as it is wide.
After some fiddling I had produced the following map by rendering each tile individually (in a loop) in order from “top left” to “bottom right”:
It got confusing quickly. The “top left” when looked at isometrically is actually in the top of the screen, slightly to the right of center!
So I needed to figure out how to place avatars and other items onto the map from my cartesian grid. The brown-haired avatar on the left is “at” location 5,12. I had to figure out how to convert from cartesian to isometric so that I could tell the game engine precisely where to render the avatar.
If we were just rendering on a Cartesian plane, I could simply tell the engine to render an item at 2,2 by multiplying by the width of one tile in the grid:
screen_x_coord = cartesian_x * tile_width screen_y_coord = cartesian_y * tile_height
However, this only works with a square grid. Our isometric grid is not exactly a square. This illustrates the problem better than I can explain:
Thinking this through: for every 1 cell “right” we want to go, we’re actually going “right” by 1/2 a tile width and “down” by 1/2 a tile height. Like this:
So, we have a new formula:
screen_x_coord = cartesian_x * (tile_width / 2) - cartesian_y * (tile_width / 2) screen_y_coord = cartesian_y * (tile_height / 2) + cartesian_x * (tile_height / 2)
Plugging these values in gives the right map location and everything looked great.
The other thing I wanted was the ability to click somewhere on the map and move your avatar to that location. This meant I had to detect the x,y coordinates on the screen that the mouse clicked, reverse the above conversion from isometric to cartesian, move the avatar in the 2d arrays and then re-render them isometrically.
Whew! Lots of steps to simply render an item at a specific set of coordinates.
Another wrinkle that I ran into was that rendering a sprite “at” a location is not very well defined. If I’m trying to render a square image at a particular cell, by default many game engines place the top left of the image at the defined location. That won’t work for us since we need our characters to be “standing” in a particular cell. So much of our rendering code actually does even more arithmetic by adjusting the coordinates according to the width and height of the sprite such that the bottom-center of the image (where the character’s feet are) become the “anchor” point and we render the item “at” a coordinate by place it’s bottom-center in the defined coordinates:
There were lots of other challenges along the way, including representing the whole thing as a grid, determining which sections of the map are “walkable” so that we can use a pathfinding algorithm, finding the right “pace” a character walks across the map, and more. And I’m just scratching the surface on the types of challenges – I haven’t even mentioned our proximity video conversations that enable magical serendipitous moments, the interactive relic system we’ve devised, native operating system components and much more.
To sum it up: I learned a lot in this process and continue to learn more every day. Agave is truly a cross-disciplinary project – it requires more than simply building features to a spec. Blending the engineering of game engines with the art of sprite design (and even interior design!) has been a super cool thing to be a part of. I can’t wait to show it to you.
Like this post? Email email@example.com, I’d love to hear from you.