24 hours Rust game: personal development experience

Original author: Olivia Ifrim
  • Transfer
image

In this article I will talk about my personal experience in developing a small game in Rust. It took about 24 hours to create a working version (I mainly worked in the evenings or on weekends). The game is far from over, but I think the experience will be useful. I will tell you what I learned and some of the observations made when building the game from scratch.

Skillbox recommends: Two-year practical course "I am a PRO Web Developer . "

We remind you: for all readers of “Habr” - a discount of 10,000 rubles when registering for any Skillbox course using the “Habr” promo code.

Why rust?


I chose this language because I heard a lot of good things about it and I see that it is becoming more and more popular in the field of game development. Prior to writing the game, I had little experience developing simple applications in Rust. This was just enough to feel a certain freedom while writing the game.

Why exactly the game and what kind of game?


Making games is fun! I would like for more reasons, but for “home” projects I choose topics that are not too closely related to my usual work. What game is this? I wanted to make something like a tennis simulator, which combines Cities Skylines, Zoo Tycoon, Prison Architect and tennis itself. In general, it turned out a game about a tennis academy, where people come to play.

Technical training


I wanted to use Rust, but I did not know exactly how “from scratch” I would need to get started. I didn’t want to write pixel shaders and use drag-n-drop, so I was looking for the most flexible solutions.

Found useful resources that I share with you:


I explored several Rust game engines, eventually choosing Piston and ggez. I came across them when working on a previous project. In the end, I chose ggez, because it seemed more suitable for implementing a small 2D game. The modular structure of Piston is too complex for a novice developer (or someone who works with Rust for the first time).

Game structure


I spent a little time thinking about the architecture of the project. The first step is to make the “land”, people and tennis courts. People must move around the courts and wait. Players must have skills that improve over time. Plus, there should be an editor that allows you to add new people and courts, but this is no longer free.

Thinking over everything, I set to work.

Game creation


Start: circles and abstractions

I took an example from ggez and got a circle on the screen. Amazing Now a few abstractions. It seemed to me that it’s nice to ignore the idea of ​​a game object. Each object must be rendered and updated as indicated 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 piece of code allowed me to get an excellent list of objects that I can update and render in an equally excellent loop.

mpl 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(())
    }
}

main.rs is needed because it contains all the lines of code. I spent a little time to separate the files and optimize the directory structure. Here's how it all began to look after this:

resources -> this is where all the assets are (images)
src
- entities
- game_object.rs
- circle.rs
- main.rs -> main loop


People, floors and images

The next step is to create a game Person object and loading images. Everything should be based on tiles 32 * 32 in size.



Tennis courts

Having studied how tennis courts look, I decided to make them out of 4 * 2 tiles. Initially, it was possible to make an image of this size or to make together 8 separate tiles. But then I realized that only two unique tiles are needed, and that's why.

In total we have two such tiles: 1 and 2.

Each section of the court consists of tile 1 or tile 2. They can be located as usual or be turned upside down by 180 degrees.



The main mode of construction (assembly)

After I managed to achieve the rendering of sites, people and maps, I realized that a basic assembly mode was also needed. It was implemented like this: when the button is pressed, the object is selected, and the click places it in the right place. So, button 1 allows you to select a court, and button 2 allows you to select a player.

But you still need to remember what we mean 1 and 2, so I added a wireframe so that it is clear which object is selected. Here is how it looks.


Questions on architecture and refactoring

Now I have several game objects: people, courts and floors. But in order for wireframes to work, you need to tell each entity of the object whether the objects themselves are in demonstration mode, or if a frame is simply drawn. This is not very convenient.

It seemed to me that I needed to rethink the architecture so that some limitations were revealed:

  • the presence of an entity that displays and updates itself is a problem, because this entity will not be able to “know” what it should render - an image and a wireframe;
  • lack of a tool for exchanging properties and behavior between individual entities (for example, the is_build_mode property or rendering behavior). Inheritance could be used, although there is no normal way to implement it in Rust. What I really needed was the layout;
  • a tool for the interaction of entities among themselves was needed to assign people to the courts;
  • the entities themselves were a mixture of data and logic, which quickly got out of hand.

I did some further research and discovered the ECS architecture - Entity Component System , which is commonly used in games. Here are the benefits of ECS:

  • data is separated from logic;
  • layout instead of inheritance;
  • data oriented architecture.

ECS is characterized by three basic concepts:

  • entities - the type of object that the identifier refers to (it can be a player, a ball, or something else);
  • components - entities consist of them. An example is a rendering component, layout, and others. It is a data warehouse;
  • systems - they use both objects and components, plus they contain behavior and logic that are based on this data. An example is a rendering system that iterates over all entities with components for rendering and is engaged in rendering.

After studying it became clear that ECS solves such problems:

  • use layout instead of inheritance for system organization of entities;
  • getting rid of a hash of code due to control systems;
  • using methods like is_build_mode to store the logic of the wireframe in the same place - in the rendering system.

Here's what happened after implementing ECS.

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


Assign people to the courts


ECS made life easier. Now I had a systematic way of adding data to entities and adding logic based on this data. And this, in turn, made it possible to organize the distribution of people by court.

What have I done:

  • added data about assigned courts to Person;
  • added data on distributed people to TennisCourt;
  • added CourtChoosingSystem, which allows you to analyze people and sites, find available courts and distribute players to them;
  • added the PersonMovementSystem system, which searches for people assigned to the courts, and if they are not there, it sends people where necessary.


To summarize


I really enjoyed working on this simple game. Moreover, I am pleased that I used Rust to write it, because:

  • Rust gives you what you need;
  • he has excellent documentation, Rust is very elegant;
  • constancy is cool;
  • You don’t have to resort to cloning, copying or other similar actions, which I often did in C ++;
  • Options are very convenient for work, they also handle errors perfectly;
  • if the project could be compiled, then in 99% it works, and exactly as it should. Compiler error messages, it seems to me, are the best of those that I have seen.

Game development on Rust is only just beginning. But there is already a stable and fairly large community working to open Rust to everyone. Therefore, I look at the future of the language with optimism, looking forward to the results of our common work.

Skillbox recommends:



Also popular now: