Playing Rust in 24 hours: personal development experience

Playing Rust in 24 hours: personal development experience

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 (mostly I worked in the evenings or on weekends). The game is far from complete, but I think the experience will be useful. I'll share what I've learned and some observations I've made while 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 rubles when enrolling in any Skillbox course using the "Habr" promotional code.

Why Rust?

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

Why the game and what kind of game?

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

Technical training

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

I found useful resources that I share with you:

I explored several Rust game engines, eventually choosing Piston and ggez. I came across them while working on a previous project. I ended up choosing ggez as it seemed more suitable for a small 2D game. The modular structure of Piston is too complex for a novice developer (or someone who is new to Rust).

Structure of the game

I spent some time thinking about the architecture of the project. The first step is to make "land", people and tennis courts. People have to 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.

After thinking everything through, I got to work.

Creating a game

Beginning: circles and abstractions

I took the example from ggez and got a circle on the screen. Marvelous! Now some abstractions. I thought it was a good idea to abstract away from the idea of ​​a game object. Each object must be rendered and updated as specified 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 gave me a great list of objects that I can update and render in an equally great cycle.

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 took some time to separate the files and optimize the directory structure. Here's what it looked like after that:
resources -> this is where all the assets are (images)
src
- entities
β€”game_object.rs
β€” circle.rs
- main.rs -> mainloop

People, floors and images

The next step is to create the Person game object and load the images. Everything should be based on 32*32 tiles.

Playing Rust in 24 hours: personal development experience

Tennis courts

After studying what tennis courts look like, I decided to make them from 4x2 tiles. Initially, it was possible to make an image of this size, or else put together 8 separate tiles. But then I realized that only two unique tiles are needed, and here'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 placed as usual or be flipped 180 degrees.

Playing Rust in 24 hours: personal development experience

Basic construction (assembly) mode

After I managed to achieve the rendering of sites, people and maps, I realized that a basic assembly mode was also needed. I implemented it 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 1 and 2 mean for us, so I added a wireframe in order to make it clear which object is selected. Here's what it looks like.

Playing Rust in 24 hours: personal development experience

Questions about architecture and refactoring

Now I have several game objects: people, courts and floors. But in order for wireframes to work, each object entity needs to be told whether the objects themselves are in demo mode, or whether a frame is simply drawn. This is not very convenient.

It seemed to me that it was necessary to rethink the architecture so that some limitations were revealed:

  • having an entity that renders and updates itself is a problem, as that entity will not be able to "know" what it should render - an image and a wireframe;
  • the absence of a tool for exchanging properties and behavior between individual entities (for example, the is_build_mode property or behavior rendering). It would be possible to use inheritance, although there is no normal way in Rust to implement it. What I really needed was the layout;
  • a tool for interaction between entities was needed to assign people to courts;
  • the entities themselves were a mix of data and logic that got out of control very quickly.

I did more research and discovered the architecture ECS - 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-centric 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 are made up of them. An example is a rendering component, layouts, and others. These are data stores;
  • 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 to render and takes care of rendering.

After studying, it became clear that ECS solves such problems:

  • the use of layout instead of inheritance for the systemic organization of entities;
  • getting rid of the mess of code at the expense of control systems;
  • using methods like is_build_mode to keep the wireframe logic in the same place - in the rendering system.

Here's what happened after the introduction of 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 -> mainloop

Assign people to the courts

ECS has made life easier. Now I had a system path of adding data to entities and adding logic based on that data. And this, in turn, made it possible to organize the distribution of people on the courts.

What have I done:

  • added data about assigned courts to Person;
  • added distributed people data to TennisCourt;
  • added the CourtChoosingSystem, which allows you to analyze people and courts, detect available courts and distribute players to them;
  • added the PersonMovementSystem, which looks for people assigned to the courts, and if they are not there, then sends people to the right place.

Playing Rust in 24 hours: personal development experience

Summing up

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

  • Rust gives you what you need;
  • it has excellent documentation, Rust is very elegant;
  • persistence is cool;
  • no need to resort to cloning, copying, or other similar actions, which I often did in C ++;
  • Options are very easy to work with, they also handle errors very well;
  • if the project was successfully compiled, then in 99% it works, and exactly as it should. Compiler error messages are the best I've ever seen.

Rust game development is just getting started. But there is already a stable and fairly large community working to make Rust open to everyone. Therefore, I am optimistic about the future of the language, looking forward to the results of our common work.

Skillbox recommends:

Source: habr.com

Add a comment