Minimal Sample
This guide shows how to create a small but functional game module.
Create the project
See Quick Start.
Add a spawn system
First thing's first, let's spawn something.
Spawn a square
To do this, we can use a system_once, which is a function which runs only once at startup.
use engine::prelude::*;
#[system_once]
fn spawn_square() {
let color_render = &ColorRender {
size: Vec2::new(100., 100.),
visible: true,
};
Engine::spawn(bundle!(color_render));
}
mod ffi {
use super::*;
include!(env!("ECS_GENERATED_PATH"));
}
To spawn an entity, we call the engine's Engine::spawn() function. This function takes a slice of ComponentRef structs. These are a bit difficult to construct manually, so we use the bundle!() macro to help generate this slice from regular references.
bundle!() can take references to any type of component. Here, ColorRender is a component which tells the renderer to draw this entity as a solid-colored rectangle.
💡 Tip
To see the square, run the game with
./bloop -g <path_to_your_project_dir>
Make the square a different color
By default, ColorRender draws a white rectangle. This is because the Color component is being automatically added for us, with a default value of Color::WHITE.
Let's add the Color component ourselves, rather than relying on the default.
use engine::prelude::*;
#[system_once]
fn spawn_square() {
let color_render = &ColorRender {
size: Vec2::new(100., 100.),
visible: true,
};
let color = &Color::GREEN;
Engine::spawn(bundle!(color_render, color));
}
mod ffi {
use super::*;
include!(env!("ECS_GENERATED_PATH"));
}
Now we have a green square!
Move the square
💡 Tip
While hot-reloading
system_oncefunctions is currently unsupported, hot reloading regularsystemfunctions is! Try coding this section while the game is running.
We now have a square, but it doesn't do much. Let's try moving it with WASD.
Add a new function, this time with #[system]. This type of system runs once per frame.
use engine::prelude::*;
#[system_once]
fn spawn_square() { /** snip */ }
#[system]
fn move_square() {}
mod ffi {
use super::*;
include!(env!("ECS_GENERATED_PATH"));
}
User input is accessible in a resource called InputState. To get access to a resource, simply add it as a system parameter wrapped in a Ref<T> (for immutable access) or Mut<T> (for mutable access).
#[system]
fn move_square(input: Ref<InputState>) {}
We also need access to our square entity. Unlike resources, we access entities via queries. A query takes a list of components (also wrapped in Ref<T> or Mut<T>), which it uses as a filter to match all entities in the world.
#[system]
fn move_square(input: Ref<InputState>, mut squares: Query<Mut<Transform>>) {}
Just like the Color component, a Transform component is implicitly added to any entity which adds a ColorRender component. Because our square entity is the only entity in the world which has a Transform component, we can simply query over the Transform component.
Now that our system inputs are set up, let's make it move!
#[system]
fn move_sphere(input: Ref<InputState>, mut spheres: Query<Mut<Transform>>) {
spheres.for_each(|mut transform| {
if input.keys[KeyCode::KeyW].pressed() {
transform.position.y += 1.;
}
if input.keys[KeyCode::KeyS].pressed() {
transform.position.y -= 1.;
}
if input.keys[KeyCode::KeyD].pressed() {
transform.position.x += 1.;
}
if input.keys[KeyCode::KeyA].pressed() {
transform.position.x -= 1.;
}
});
}