In this post I'll talk about a small game I've been developing in about 24 hours in total (mostly in a few hour blocks during evenings or weekends). The game is far from finished, but I thought I'd write up about my experience so far, what I've learnt and some interesting observations about building a game from scratch-ish and doing it in Rust.
I picked Rust because I've been hearing great things about it and I see it's been getting some traction in the game development space. I have to say at the point which I started this game I had written a few small programs in Rust, just enough to not feel like my hands were tied when I started writing the game.
Making games is fun! I wish there was a more elaborate reason than that, but for side projects I normally prefer things that are quite far from what I do daily at my job. Now, what game? I wanted to make a simulation game with a tennis theme, think like Cities Skylines meets Zoo Tycoon meets Prison Architect with tennis and pixel art. I haven't got it all figured out yet, but it's basically a tennis academy where people come and play tennis.
I knew I wanted to use Rust, but I didn't know exactly how much "from-scratch" I wanted to make it. I didn't want to write pixel shaders, but I didn't want to use drag and drop either, so I had to pick something that gave me enough flexibility but also keep it interesting from an engineering perspective without going too low level.
I found a few useful resources which I'll link here in case you want to have a look:
I did a little research on Rust game engines and was left with two main contenders: Piston and ggez. I tried both in a previous small project and I ended up going with ggez because it seemed easier to use for a small 2D game. Piston's modularity seemed a bit unapproachable from a beginner's point a few.
I then spent a bit of time thinking about the game. The first step would be to have some floor on the screen, then some people, then some courts. Eventually, we'll need to assign people to courts, have them move there and waaay in the future, the people would have skills which would improve the more they play. There would need to be a sort of build mode so you can add people and courts and eventually all this would cost money.
At this point I felt like I had enough ideas to start coding. (being PM and dev is quite fun! I'd still rather have a PM though ..)
I pretty much ripped off a ggez sample and got a window with a circle in it up on the screen. Amazing! Next, some abstraction. I thought it would be a good idea to abstract the idea of a game object. Every game object can be rendered and updated, something like this (if you're rolling your eyes at this point, this was before I found out about ECS, so bear with me here).
// the game object trait
trait GameObject {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()>;
fn draw(&mut self, ctx: &mut Context) -> GameResult<()>;
}
// a specific game object - Circle
struct Circle {
position: Point2,
}
impl Circle {
fn new(position: Point2) -> Circle {
Circle { position }
}
}
impl GameObject for Circle {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
let circle =
graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?;
graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?;
Ok(())
}
}
This allowed me to have a nice list of objects which I could update and render in a nice loop.
impl event::EventHandler for MainState {
fn update(&mut self, context: &mut Context) -> GameResult<()> {
// Update all objects
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult<()> {
graphics::clear(context);
// Draw all objects
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
}
At this point main.rs
was the place to be, because every single line of code was there, so I spent some time breaking that up into separate files and rationalizing the directory structure a bit, so it looked like this now.
resources -> this is where all the assets are (images)
src
-- entities
---- game_object.rs
---- circle.rs
-- main.rs -> main loop
The next big milestone was creating a Person
game object and loading images. I decided everything will be tile based (currently 32x32 tiles).
I spent some time looking at images of tennis courts online and I figured I'd want my tennis court to be 4*2 tiles. I would make an image that wide, or I could have 8 separate tiles. On further inspection I actually realized I only need 2 unique tiles to build the whole court. Let me explain.
2 unique tiles: 1 and 2.
Each section of the court is actually made up of either tile 1 or tile 2, either as is or rotated 180 degrees.
Now that I could render floors, people and courts I figured I needed a basic build mode. I made it so that when a key was pressed, an object was selected and click would then place that type of object. For example, pressing 1 would give you a court and pressing 2 would give you a person.
That wasn't super useful as you needed to remember what 1 or 2 were, so I added a build-mode wireframe so you could at least know what object you had. Here it is in action.
I now had a few game objects: people, courts and floors. But in order to make the wireframes work I had to let every entity know if it was in wireframe mode and then in every draw call the entity would have to check if it was in wireframe mode and if it was draw a bounding box instead of an image. This didn't really feel right.
I started questioning the architecture and I could see some clear limitations:
is_build_mode
property and draw behaviour. I could use inheritance (but Rust didn't have a very good way of doing this), but what I really want is composition.I did a bit more research and I found something called ECS - Entity Component System architecture which is mostly used in games. The gist of ECS is this:
In ECS terminology you have these 3 basic concepts:
The more I read about this the more I realized it would solve my current problems:
is_build_mode
and have that wireframe logic in one place (in the rendering system).Here is what I ended up with after implementing ECS (which in fairness, was pretty much a rewrite).
resources -> this is where all the assets are (images)
src
-- components
---- position.rs
---- person.rs
---- tennis_court.rs
---- floor.rs
---- wireframe.rs
---- mouse_tracked.rs
-- resources
---- mouse.rs
-- systems
---- rendering.rs
-- constants.rs
-- utils.rs
-- world_factory.rs -> world factory functions
-- main.rs -> main loop
After moving to ECS everything became relatively easy. I now had a systematic way of adding data to my entities and adding logic based on that data. This allowed me to very easily get the behaviour of assigning people to courts.
What I did:
Person
TennisCourt
CourtChoosingSystem
which iterates through people and courts, finds available courts and assigns them to people. Easy!PersonMovementSystem
which iterates through people which have courts assigned to them and makes them move to the position of the courts (if they are not already there)See it in action here.
I had a really great time making this small game. But I especially had a great time writing so much Rust because:
In terms of Rust game development, I think it's still early days, but I see a great community actively working towards making Rust more accessible to game developers. So I am optimistic about the next few years and I cannot wait to see how it evolves.
Did you enjoy this post? Let me know, so I can write more!
PS: If you are interested in learning more about ECS, check out ECS explained with tennis courts and people, my follow-up post.
Share something about this post.