Banner Image

New York 2121

Published February 5, 2021

In early 2021, the "My First Game Jam" was hosted on, welcoming all first-time game developers. As with many, I have toyed with the idea of making a video game many times over the years, but had never actually gotten around to it. This game jam, hosted over the course of 2 weeks in late January 2021 seemed like a perfect opportunity.

The Engine: Bevy

Months prior, I had been dabbling with the very new (Only at v0.4.0 at the time) rust game engine: Bevy. The engine didn't have many of the quality of life features one would expect from a full-featured game engine like Unreal or Unity, but that wasn't a problem for me. The part of game development (at the time) that most interested me was more the nitty gritty of the various systems. How did graphics work? How do you handle assets? How do you implement dialog systems and quest lines? While a more mature engine would have let me make this game in likely half or less of the time, I wasn't really interested in what I saw as a shortcut.

Bevy uses the "ECS" or Entity, Component, System model of game development. The benefit of that system is that it strongly encourages modularity by making each element of the game world a simple object (Component), group of objects (Entity), or function (System). In Bevy, a Component could be as simple as this plain rust struct for tracking player data:

pub struct Player {
pub moving: bool,
pub impulse: Vec3,
A simple Player component that keeps track of data relevant to a player.

Systems as well are very simple. Their whole purpose is to perform operations on the data stored in the Entities, and since they are just plain rust functions, there continues to be a strong incentive to modularize. In New York 2121 there were some Systems that were as short as 3 lines long, like this one:

fn update_moving_state(mut player_query: Query<&mut Player>) {
for mut player in player_query.iter_mut() {
player.moving = player.impulse.x != 0. || player.impulse.y != 0.;
A very simple system to keep track of whether a player is in motion.

Asset Creation

All those nice features aside, Bevy didn't really have any sort of good interface for designing levels or assets. To supplement the engine, I chose to use Aseprite for editing pixel art and sprites, and Tiled for map creation.

Sprites: Aseprite

I started with a sprite sheet from, and modified it to support the visual style I was going for. I'm not an artist by any stretch, and I got a lot of help from my collaborator and story writer Molly Raven, but I think the sprite sheet turned out surprisingly cohesive:

The Game's Tilemap
I started with a sprite sheet and added to it

Map Creation: Tiled

Then came the assembling of the tilemap I'd created into a level. Bevy at this point did not have any sort of level editor, so I turned to Tiled, which seemed to be a pretty good option for a 2d top-down game. A few hours in tiled were all it took to have a rudimentary map for my game. The next step, then, was to integrate the map that Tiled generated into Bevy. Unfortunately, I did not like any of the existing Tiled integrations for bevy, so I ended up building my own, which involved cobbling together some custom shaders to support rendering a the tilemap, and custom parsing logic to handle the XML file that Tiled produced.

void main() {
int sprite_number = Tilemap[Vertex_Tile_Index];
if (sprite_number == 0) {
Rect sprite_rect = Textures[sprite_number - 1];
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
vec3 vertex_position = vec3(
Vertex_Position.xy * sprite_dimensions,
vec2 atlas_positions[4] = vec2[](
vec2(sprite_rect.begin.x, sprite_rect.end.y),
vec2(sprite_rect.end.x, sprite_rect.begin.y),
v_Uv = floor(atlas_positions[gl_VertexIndex % 4] + vec2(0.01, 0.01)) / AtlasSize;
v_Color = Color;
gl_Position = ViewProj * ChunkTransform * vec4(ceil(vertex_position), 1.0);
Excerpt of the custom shader for rendering tilemaps

Once I had all of that in place, I (and my collaborator) could easily edit the map and sprites. I used Tiled to encode event triggers and collision zones, and from there I was able to have a little character walking around in the city I had designed. Then I ran into the final big hurdle.

The Dialog System

You see, my plan for this game was to make it a sort of RPG-lite, so I needed dialog, quest lines, and items. The problem was that my story writer was not a programmer, so embedding the dialog in the rust code was not an option (and, in retrospect, would have made development much slower).

I looked into several dialog systems including Yarn Spinner, but found them to both be more complicated than I needed, and none of them had rust libraries. So I once again implemented my own! I took some of the features I liked from Yarn Spinner (like their method of linking parts of a conversation together), and added the Handlebars templating system, and produced a sort of proto-language that My writer could easily edit and add their content to.

# Quest 1: Robert Ruelas -> Rat Whistle
quest :: ruelas_jacket = Get Robert's jacket
item :: ruelas_jacket = Robert's Puffy jacket
item :: rat_whistle = Rat Whistle
char :: robert = Robert Ruelas
:: see_ruelas_car while quest.ruelas_jacket
{{ #unless visited.ruelas_car }}
There is a car over there, maybe it's !!Robert's!!.
{{ /unless }}
:: ruelas_car
{{ #if quest.ruelas_jacket }}
{{ #unless items.ruelas_jacket }}
You find a !!jacket!! in the back seat.
It's a waterproof, blue down jacket with a New York City Government badge on it.
The beginning of the dialog for the first quest in the game.

Finally, I had all of the elements needed, to build my game, and only a few days left to complete it. I had thankfully chosen a game jam that was 2 weeks long, rather than the 2-3 days of your Ludum Dare or other such jams. Since I ended up implementing so much of the needed scaffolding on my own during the jam, I really needed the extra time. In the end, I created a game that I am very happy with despite its short runtime.

New York 2121 Screenshot

If you'd like, the game is available for download on windows at at:

I built this project with Molly Raven.

All content © 2011-2022 Zachary Bush

Opinions expressed here are solely my own and do not express the views or opinions of my employer.