dotrix_core 0.3.0

Dotrix 3D game engine core
Documentation

Dotrix core crate crate provides generic features. Component usually is just a data structure with or without associated methods. In ECS pattern components represent properties of entities: velocity, weight, model, rigid body, color etc.

Set of components in the entity defines an Archetype. Entities of the same Archetype are being grouped together in the [crate::services::World] storage, that makes search fast and linear.

When planning the architecture of your game, developer should think about components not only as of properties, but also as of search tags. For example, if you are making physics for your game, and you have a Car and a SpringBoard, you may want to have the same named components, so you can easily query all Cars or all SpringBoards. But as soon as you will need to calculate physics for entities of the both types, you should add some component like RigidBody to the entities, so you can calculate physics for all entities who have that component.

Usefull references

  • To learn how to spawn and query entities, continue reading with [crate::services::World]
  • To learn how to implement systems [crate::systems] Services are very important part of Dotrix. Technicaly the service is a standard Rust structure with methods. Logically, services are providers of interfaces to various features.

Developer should explicitly create an instance of a service and add it to a game using the [crate::Dotrix] application builder:

use dotrix_core::{
Dotrix,
services::{ World },
};

fn main() {
Dotrix::application("My Game")
.with_service(World::default())
.run();
}

After adding a service to your game you can access it inside of [crate::systems].

Systems implement logic of your game. A system take a set of [crate::services] as parameters and implements a feature. For example, here is a system that moves camera by X axis if Right mouse button is pressed:

use dotrix_core::{
Dotrix,
input::{ ActionMapper, Button, State as InputState, Mapper }, 
ecs::{ Const, Mut, System },
services::{ Camera, Frame, Input }, 
};

// camera moving system
pub fn camera_move_x(mut camera: Mut<Camera>, input: Const<Input>, frame: Const<Frame>) {
let time_delta = frame.delta().as_secs_f32();

if input.button_state(Button::MouseRight) == Some(InputState::Hold) {
camera.target.x += 1.0 * time_delta;
}
}

fn main() {
Dotrix::application("My Game")
// add system to yout application
.with_system(System::from(camera_move_x))
// services should also be there
.with_service(Camera::default())
.with_service(Frame::default())
.with_service(Input::new(Box::new(Mapper::<Action>::new())))
.run();
}

// Mapping is required for the Input service
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
enum Action {}

impl ActionMapper<Action> for Input {
fn action_mapped(&self, action: Action) -> Option<&Button> {
let mapper = self.mapper::<Mapper<Action>>();
mapper.get_button(action)
}
}

[crate::ecs::Mut] and [crate::ecs::Const] are just the accessors for services to keep Rust mutability controls. The set of services that system takes as arguments is up to developer. It can be effortlessly extended at any time. The order of [crate::services] in arguments list does not matter.

What is also important to mention here, is that all services used in systems must be added to your game using the application builder.

Systems with context

Sometimes it is usefull to preserve some data between system executions. It can be done using a [crate::Service], but it has sense only when the data has to be shared between different systems. Otherwise it is better to use the [crate::ecs::Context].

System can have only one context and if it does, the context must be always passed as a first argument. Another requirement is that [crate::ecs::Context] structure must implement the Default trait.

Example

use dotrix_core::{
ecs::Context,
};

#[derive(Default)]
struct Counter {
value: usize,
}

fn count_up(mut counter: Context<Counter>) {
counter.value += 1;
println!("The `counter_up` system has been called {} times", counter.value);
}

System run levels

Developer can affect the execution of systems by assigning them specific run levels. If [crate::ecs::RunLevel] is not set explicitly, the RunLevel::Standard will be used.

use dotrix_core::{
Dotrix,
services::Assets,
ecs::{ Mut, System, RunLevel },
};

fn init_game(mut assets: Mut<Assets>) {
println!("Starting my super game");
assets.import("/path/to/my/asset.png");
}

fn main() {
Dotrix::application("My Game")
.with_system(System::from(init_game).with(RunLevel::Startup))
.with_service(Assets::new())
.run();
}