June 18, 2021
Rust vs. C++ for game development

Rust isn’t here to replace C++, even though they are syntactically similar. Rust provides memory safety as its core feature, which is why those who love Rust really love it. Rust is versatile too: it can be used for web development, game development, file systems, operating systems, and much more.

C++ is an object-oriented programming language that can be used for game development, operating systems, web browsers, cloud/distributed systems, etc. It’s particularly popular for game development because of its high performance, strong abstraction, and availability of libraries and tools.

In this guide, we’ll compare Rust vs. C++ in the context of game development. We’ll cover similarities and differences between Rust and C and the pros and cons of using each programming language for game development. We’ll also introduce you to some tools for game development in Rust and C++.

Here’s what we’ll cover:

Why C++ is popular for game development
Game engines for C++ developers
C++ tools for game development
Rust in game development: Are we game yet?
Why use Rust for game development?
Object-oriented vs. data-oriented programming
Game engines for Rust developers
Rust tools for game development
Where Rust falls short
C++ vs. Rust: Which is best for your game development project?

Why C++ is popular for game development

C++ has been around for quite some time in the game development industry. A lot of game developers choose to complement it with other languages in the C family or even assembly languages.

C++ hangs its hat on high performance and strong abstraction. Developers also choose C++ for its inheritance feature and other features provided by its object-oriented model. Anyone who has been in the game industry for some time can attest to the wide availability of tools to build games in C++. For developers who have a deadline to meet or who a are new to the gaming industry, C++ is always the go-to because of the vast availability of tools and resources.

Game engines for C++ developers

Because C++ has been a staple among the game development community for a long time, there is a wide variety of game engines built with C++. Let’s compare some of the most popular tools for C++ game developers.

Blender

Blender is a free and open-source software (FOSS) 3D production suite. It’s entirely built with C++ and offers support for OpenAL 3D sound and Python scripting. Given that it’s cross-platform blender supports most major operating systems. Game development isn’t all Blender is good for; you can also create short films and other cinematic elements.

Unity

Unity is a cross-platform game engine that enables you to create 2D, 3D, and virtual reality games. Although it was mainly built as a MAC OS X-exclusive game engine, Unity has since been adopted in many cinematography, engineering, and construction applications.

Panda3D

Some game engines require you to use external libraries for collision detection, I/O, audio, etc. Panda3D offers all this and more in one package. This game engine written in C++ allows you to write games in Python, although there is a workaround for writing games in C++.

Godot

Godot is an open-source, cross-platform game engine packed with tools that allows you to focus on your game development. This game engine is built with C++ and is quite popular for game development with C++ because of the flexibility it supports.

C++ tools for game development

Unlike in Rust, most game engines in C++ come packed with all the tools you need to develop games.

When building games in C++, it’s important to carefully consider which engine is most appropriate for your project. It’s also important to understand the general concept of object-oriented programming since you’ll be dealing with the object-oriented model.

Rust in game development: Are we game yet?

To appreciate what Rust offers and why it is a useful language for game development, let’s take a little ride on the history train to understand why it was created in the first place.

Rust started out as a side project by one of Mozilla’s employees, Graydon Hoare, who tackled most of the vulnerabilities in C++. Over time, Mozilla users became frustrated with the memory leaks and other vulnerabilities from C++, which is the core language of its web browser, Firefox.

This is why Graydon Hoare suggests Rust, a language he had been working on since 2006. It wasn’t until 2010 that Mozilla started supporting Rust, after it had shown great strides in memory safety.

Why use Rust for game development?

Why would anyone want to use a new language for game development instead of the C family, which been around for ages? It’s because memory-safe languages such as Rust eliminate many ofd the bugs your users will face when they use your product. Memory-safe languages won’t allow code with memory leaks run. To achieve memory safety, Rust uses the data-oriented model. It treats game elements as data instead of objects as in object-oriented programming.

Object-oriented vs. data-oriented programming

There are some problems with object-oriented programming in game development — especially with one of the core feature of object-oriented programming, encapsulation. Encapsulation helps developers hide data to maintain a safe environment. However, in game development, this feature is a footgun because it defeats the very purpose it was created for.

For example, you need to follow inheritance to be able to access data because you can’t make it public due to encapsulation. Therefore, for every new feature added to the game, you may need to access data from another field, which could be encapsulated, so that they can inherit the feature.

To understand the downside of encapsulation/inheritance associated with OOP in game development, let’s look at this quick example from Catherine West’s closing keynote at RustConf 2018:

typedef uint32_t EntityId;

// Declare World to pass it to Entity
struct World;

struct InputState { … };
struct RenderState { … };

// Pure virtual interface!
class Entity {
public:
virtual Vec2F position() const = 0;
void update(World* world) = 0;
void input(InputState const& input_state) = 0;
void render(RenderState& render_state) = 0;

private:
};

class Player : Entity {
public:
Vec2F position() const override;

void input(InputState const& input_state) override;
void update(World* world) override;
void render(RenderState& render_state) override;

private:
Physics m_physics;
HumanoidState m_humanoid;


};

class Monster : Entity {
public:
Vec2F position() const override;

void input(InputState const& input_state) override;
void update(World* world) override;
void render(RenderState& render_state) override;

private:
Physics m_physics;


};

class NPC : Entity {
public:
Vec2F position() const override;

void input(InputState const& input_state) override;
void update(World* world) override;
void render(RenderState& render_state) override;

private:
Physics m_physics;
HumanoidState m_humanoid;


};

struct WorldTile { … };

struct World {
List<EntityId> player_ids;
HashMap<EntityId, shared_ptr<Entity>> entities;

MultiArray2D<WorldTile> tiles;


};

As your project grows, many of the child, parent, and ancestor relationships in your app will become too difficult to handle and may create a loophole in your project. For example, if our game gets a new feature in future release, we’ll need to apply inheritance.

Let’s say, for instance, we need a monster to track players with bad health. To do this, we have to create a public accessor for the player’s health because it’s private:

class Monster : Entity {
public:
Vec2F position() const override;

void input(InputState const& input_state) override;
void update(World* world) override;
void render(RenderState& render_state) override;

DamageRegion const& damage_region() const;

private:

};

If we want to add more features for states that are private, then we need to create more accessors. By doing this, we’ll keep poking holes in our application until it’s unsafe and can’t be managed.

Because Rust takes a data-oriented approach, game elements are treated as data. Rust uses the Entity Component System (ECS) pattern in game development where the entity consists of different components attached to it, the component, consists of chunks of data (data for game development), and the system manages the application’s logic. For instance, if we want to replicate the same example from C++ in Rust, we’ll the ECS approach with entities and components as structs.

type EntityIndex = usize;

struct Physics {
position: Vector2<f32>,
velocity: Vector2<f32>,
mass: f32,
}

struct HumanoidAnimationState { … }
struct HumanoidItem { … }

struct HumanoidState {
animation_state: HumanoidAnimationState,
left_hand_item: HumanoidItem,
right_hand_item: HumanoidItem,
aim_position: Vector2<f32>,
}

struct Player {
physics: Physics,
humanoid: HumanoidState,

health: f32,
focused_entity: EntityIndex,
food_level: f32,
admin: bool,


}

enum MonsterAnimationState { … }
struct DamageRegion { … }

struct Monster {
physics: Physics,
animation_state: MonsterAnimationState,

health: f32,
current_target: EntityIndex,
damage_region: DamageRegion,


}

struct NpcBehavior { … }

struct Npc {
physics: Physics,
humanoid: HumanoidState,

health: f32,
behavior: NpcBehavior,


}

enum Entity {
Player(Player),
Monster(Monster),
Npc(Npc),
}

struct Assets { … }

struct GameState {
assets: Assets,

entities: Vec<Option<Entity>>,
players: Vec<EntityIndex>,


}

fn main() {
let mut game_state = initial_game_state();

loop {
let input_state = capture_input_state();

player_control_system(&mut game_state, &input_state);
npc_behavior_system(&mut game_state);
monster_behavior_system(&mut game_state);

physics_system(&mut game_state);

// … lots more systems

render_system(&mut game);
audio_system(&mut game);

wait_vsync();
}
}

New features can easily be added into the structs. To avoid repetition, you can use the impl keyword. With this approach, it is easy to retrieve or pass data to a feature securely without inheritance because components can be called in whenever they’re required.

Game engines for Rust developers

Even though Rust is a relatively new language in the game development scene, there is already a solid selection of game engines built in Rust. Let’s look at some of the top Rust game engine crates and briefly explore how to use them in game development.

Amethyst

Amethyst is data-oriented, fast, and easy to configure. It has massively parallel architecture, uses the ECS model, and allows rapid prototyping with RON files.

Amethyst enables developers who’re new to game development to hit the ground running. The game engine offers examples that help you get acquainted easily. To run any of the examples, execute the command below in the command-line interface of your choice:

cargo run –example name_of_example

Caper

Caper has support for other systems including audio, rendering, input, and collision detection. It’s not a cross-platform game engine and it supports only Linux OS. Like Amethyst, Caper provides some examples to help you get oriented with the game engine. You can test these examples by running the command below in your command line interface.

cargo run –example transforms

Chariot

Chariot is a reimplementation of the “Age of Empires” game published by Microsoft, which uses the Genie Engine. Chariot is an open-source game engine that can be ported to any desired platform. The goal of this game engine is to make games like the aforementioned title.

Console

If you want an engine that provides tools for handling user input, Console is your best bet. With the Console engine, you can even create standalone screens easily if you don’t want terminal, mouse, or keyboard handling.

Oxygengine

Oxygengine is a web game engine written in Rust with web-sys. It is an HTML5 and WebAssembly game engine based on the Specs crate used for its ECS framework.

Other noteworthy game engines written in Rust include bevy, coffee, corange, doryen, dotrix, muoxi, rusty_engine, turbine, and many more.

Rust tooling for game development

As we mentioned previously, tooling plays a major role in game development. In this section, like we did with C++, we’ll take a high-level look at some Rust tools for game development.

2D rendering

Rendering is an important part of game creation because gives users of your product an engaging user interface with two-dimensional, photorealistic images. Some of the top 2D rendering tools for Rust game development include:

beryllium
blit
crow
fermium
image

3D rendering

While 2D rendering offers two-dimensional photorealistic images, as you can probably guess, 3D rendering makes your game environment appear even more realistic with three-dimensional images. Below are some of the most useful 3D rendering tools for Rust game developers:

ash
gfx
gl
glow
glutin

Artificial intelligence (AI)

AI libraries enable you to use algorithms to implement predictive behaviors in your game. For example, there are AI libraries with prebuilt chess algorithms you can use to create such a game in Rust. Prominent examples of Rust AI libraries for game development include:

chess_ai
pathfinding
navmesh
steering
big_brain

Animation libraries

Most games require motion. Animation libraries in Rust enable you to manipulate images to behave like they’re moving. Because most Rust libraries are built by members of the community and Rust is a relatively new language on the scene, Pareen is the only widely used animation crate for game development in Rust at the time of writing.

Pareen allows you to create animations that are parameterized by time without needing to pass time variables around. This is useful for creating smooth transitions between multiple game states.

Audio wrappers

Sound is just as important as motion in game development. An action game, for example, would feel incomplete and boring without realistic booms, crashes, and other noises associated with wreckage and destruction.

The following list of Rust audio wrappers is a great place to start when looking to implement audio in your Rust game:

hound
kira
oddio
rodio
portmidi

Input libraries

For games that use pads and other input devices, you’ll need a crate to handle controllers in input devices. Rust has two input libraries:

gilrs
sdl2

Networking tools

Games are so much more fun when you play them with friends. The Rust ecosystem includes a range of networking tools to help foster collaboration among developers and facilitate multiplayer features in Rust games, including:

naia
laminar
message-io
enet
quinn

Collision detection libraries

In certain types of games, the user fails or earns points when they collide with something. Collision detection libraries do just what the name suggests: detect collisions within your game.

Useful collision detection libraries for Rust game developers include:

physme
physx
rapier
salva
mgf

UI libraries

The user interface is the first thing a player sees and judges about your game, even before engaging with and experiencing it. First impressions are everything in game development, and a bad user interface will often turn players off before they even start interacting with your game.

Some UI libraries for Rust game development are:

egui
fungui
imgui
iced
vitral

VR engines

In 2021, some segments of the game development community are trending toward virtual reality, creating stunningly realistic visual landscapes that surround and immerse players like never before.

Below are some of the best VR engines Rust has to offer:

openvr
openxr
rovr
libovr

Where Rust falls short

When building games in Rust, it’s important to understand that most Rust tools and engines are still in the development stage. And, to reiterate, Rust’s approach to game development is data-oriented. Therefore, if you’re coming from an object-oriented background like C++, you should spend some time familiarizing yourself with the data-oriented model before starting out with game development in Rust.

For further reading on the challenges posed by object-oriented programming in game development, check out Catherine West’s closing keynote from RustConf 2018.

C++ vs. Rust: Which is best for your game development project?

In my humble opinion, there’s no such thing as a perfect programming language or even an inherently programming language. The best language, framework, library, or tool for any job depends on your comfort level using it and the unique requirements and goals of your project.

Tool availability and support are also top considerations for game developers. Therefore, if you’re building a game where memory safety is a priority, Rust is likely your best choice. There’s community support and communication channels on Discord and elsewhere. You can stay up to date and track Rust’s production-readiness regarding game development by visiting Are We Game Yet?

On the other hand, C++ is a great choice for game development projects that don’t require memory safety. The C++ ecosystem includes a wider range of tried-and-true tools that have been around for years and are trusted among the game developer community. C++ is an especially strong choice for your project if you’re more comfortable with object-oriented programming than using data-oriented languages such as Rust.

Conclusion

In this guide, we explored the basics of game development in C++ and the Rust programming language. We compared the developer experience of using both Rust and C++ to build games; listed the most useful and widely adopted tools for implementing animations, sounds, collision detection, multiplayer features, and more; and defined some simple parameters for determining which language is most appropriate for your game development project.

The post Rust vs. C++ for game development appeared first on LogRocket Blog.

Leave a Reply

Your email address will not be published. Required fields are marked *

Send