logo
Published on

Making a Game Engine in Rust 02: First Steps

Authors

In the first post, I introduced the idea of creating a 2D game engine in Rust and shared my goals for this project. Now, it’s time to dive into the first steps of actually building it. In this post, I’ll walk you through the basic project structure, setting up the renderer, handling the coordinate system, and implementing an input system using SDL2. Plus, I’ll reveal the name I’ve chosen for the engine!

Basic Project Structure

To get started, I’ve created a simple project structure. This is just a basic setup for now, and I plan to make it more modular and manageable as the engine evolves, likely incorporating Cargo workspaces. But before diving into advanced organization, I want to ensure that the core ideas—such as rendering, input handling, and other foundational features—are working as expected.

Here’s what the initial project structure looks like:

├───src
│   ├───ecs
│   ├───engine
│   ├───physics
│   ├───utils
│   └───view

As you can see, it’s fairly straightforward. I’ll refine and expand this structure as the project grows, but for now, this setup will help me focus on getting the basics right.

Renderer

For rendering graphics, I’ve decided to use SDL2. Since I’m focusing on 2D graphics, SDL2 is a great choice—it’s simple, powerful, and well-suited for the task. While I may consider using another library or even switching to OpenGL in the future, SDL2 offers everything I need to get started.

To keep things organized, I’ve split the rendering process into its own file. The idea is to create a function that will be called each frame from the main loop to handle rendering.

Here’s a snippet of the render function:

renderer.rs
impl Renderer {
    /// Renders the game objects on the canvas.
    ///
    /// # Arguments
    ///
    /// * `objects` - The vector of game objects to render.
    pub fn render(&mut self, objects: &mut Vec<Box<dyn GameObject>>) {
        // Background
        self.canvas.set_draw_color(self.background_color);
        self.canvas.clear();

        // Game objects
        for object in objects {
            // Render objects

            // ...snip...
        }

        // Present the render
        self.canvas.present();
    }
}

Thoughts on ECS and Future Plans

Right now, I’m directly rendering game objects from a vector of structs. While this works for testing the basic functionality, I know it’s not the best long-term solution. Eventually, I plan to implement a proper Entity-Component-System (ECS) architecture to handle game objects more efficiently. For now, though, I’m focusing on making sure the basics work.

Coordinate System

One thing I noticed while working with SDL2 is that its coordinate system is a bit different from what I’m used to. In SDL2, the top-left corner of the window is the coordinate (0,0), and the Y-axis increases as you move downwards, which is opposite to what I’m used to.

Coming from other engines where the Y-axis increases upwards, this took some getting used to. To simplify things, I created a couple of helper functions to convert coordinates from a more familiar system (where the origin is at the bottom-left) to SDL2’s system.

Here’s how the conversion functions look:

use crate::engine::Vector2;

pub fn convert_point_y(y: f32) -> f32 {
    // !Arbitrary window height just to make sure the coordinate system is correct
    -y + 576.0
}

pub fn convert_vector_y(v: &Vector2) -> Vector2 {
    Vector2::new(v.x, convert_point_y(v.y))
}

Vector2 Struct

You might have noticed the Vector2 struct in the code above. I created this custom struct to represent a vector in two dimensions. The Vector2 struct is designed to make common vector operations—such as dot products, perpendicular vectors, angles, normalization, distance between points, projections, etc.—easier to implement.

For example, you can easily call Vector2::reflect(in_direction, in_normal) to get the reflected vector off a surface. This will save time down the road, especially when working on the physics system.

Input System

SDL2 also includes a simple and powerful input system, which I plan to use for this engine. My goal is to create a static class (or something similar) to manage input, accessible throughout the program. However, since Rust doesn’t have classes in the traditional sense, I’ll need to explore different approaches using references and other Rust features.

Name

After some thought, I’ve decided to name the engine Axle. I think it’s a good fit, though I’m not the best at naming things, so it might change later if something better comes to mind. For now, though, Axle it is!

Conclusion

These first steps lay the foundation for the Axle game engine. I’ve set up a basic project structure, started work on the renderer, handled coordinate system conversion, and begun thinking about the input system. There’s still a lot to do, but I’m excited about the progress so far.