An enclave for thoughts

Playing with Bevy Engine and ECS

Bevy is a Rust game engine that runs Entities, Components, and Systems in a update loop using functional paradigms. This system is commonly referred to as Bevy ECS

A month has passed since the beginning of this year, and a small “New Year’s resolution” of mine is to properly learn game programming. This turned out to be through the Bevy engine, giving a unique experience programming a graphical application in Rust, a language I am learning. This led to creating a 2D demo “game” where the player avoids a green enemy that constantly pursues them with a fixed velocity always pointing to the player.

The Setup

The first setback was setting up a development environment. As with everything in NixOS, development dependencies are not installed, or made available to the user session by default, so a Nix shell is needed to provide the relevant packages and libraries.

This is a sample shell.nix file to set up a Bevy environment. Here, note the buildInputs property that sets up all required libraries, where either X11 or Wayland dependencies are needed, as determined by your desktop environment.

{ pkgs ? import <nixpkgs> { } }:

with pkgs;

mkShell rec {
  nativeBuildInputs = [
    pkg-config
    cargo
    rustc
    rustfmt
    clippy
  ];
  buildInputs = [
    udev alsa-lib vulkan-loader
    xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature
    libxkbcommon wayland # To use the wayland feature
  ];
  LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
  RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}

Here, a shell environment is generated where a stable Rust tool-chain is exposed to the user, and the path to build inputs are exposed to LD_LIBRARY_PATH, ensuring that dynamically loading the resulting binary succeeds. This does mean that, when exposed to another NixOS environment, it is very likely that the binary cannot be run. It is possible to solve this by using the musl C compiler, which statically compiles libraries into the binary, sacrificing file size for compatibility.

Direnv automation

Quite conveniently, the nix-direnv package, installed by home.packages = [ pkgs.nix-direnv ] , allows auto-loading approved directory .envrc files. But, to let direnv know that the current directory uses a nix shell, use nix must be added as a directive to let it know of the shell.nix configuration.

# File: /PROJECT_ROOT/.envrc
use nix

In summary, the declarative nature of NixOS allows an environment to be defined in a reliable and stable manner, with the trade-off being the rapid iteration velocity achievable in a highly mutable conventional system environment.

Writing Bevy systems

Getting resources

Bevy systems are just ordinary functions that take in inputs and produce side-effects in the game world. Bevy uses a special struct Res<T> to get an available resource with the type T. For example, to get the current time counter:

fn process_time(time: Res<Time>) {
	...
}

Getting entities with Query

Procuring entities, such as the player and enemy, is a bit more complicated. Here, a reference can be taken of the entity that you want, mutable or immutable, in a set as the first type parameter of the Query<T,F> type, where T is the entity reference, and F are some filter options that are available.

Below you can find an example of getting a Player entity reference from the world, and getting its properties:

use bevy::prelude::*;
#[derive(Debug)]
fn main() {
 App::new()
	 .add_systems(Startup , setup_world)
	 .add_systems(Update, get_player)
	 ...
}

pub struct Player {
	name: String,
	...
}
fn setup_world(mut commands: Commands) {
	let s = Sprite::from_color(Color::srgb(1., 1., 1.), Vec2 { x: 10., y: 50. });
    commands.spawn(Camera2d);
    commands.spawn((
        Player {
            name: "Player Joe".to_string(),
        },
        s,
        Transform::from_xyz(0., 0., 0.),
    ));
    ...
}
fn get_player(players: Query<(&Player, &Sprite, &Transform)>) {
	for (player, sprite, transform) in &players {
		println!("Discovered player with name: {}", player.name);
	}
}

, , — Feb 7, 2025