Legion ECS refactor to real-time engine

Heya.

Some context:

I'm currently following along the Hands-on Rust book, and I want to convert the game from the turn-based structure in the book to a real-time one. I'm using the Legion ECS crate for all the ECS stuff, and Bracketlib for the rendering/console stuff.

The book implements a turn-based game structure with three Schedules (referring to the Legion ECS systems scheduler):

  1. input_systems
  2. player_systems
  3. monster_systems

These are tied to three game states:

  1. TurnState::AwaitingInput -> input_systems
  2. TurnState::PlayerTurn -> player_systems
  3. TurnState::MonsterTurn -> monster_systems

The Schedulers look like this:

  • build_input_scheduler
    • .add_system( player_input_system() )
    • .add_system( fov_system() )
    • .flush()
    • .add_system( map_render_system() )
    • .add_system( entity_render_system() )
    • .add_system( hud_system() )
    • .add_system( tooltips_system() )
    • .build()
  • build_player_scheduler
    • .add_system( combat_system() )
    • .add_system( flush() )
    • .add_system( movement_system() )
    • .add_system( flush() )
    • .add_system( fov_system() )
    • .add_system( flush() )
    • .add_system( map(), entity(), hud() )
    • .add_system( *end_turn_system()* )
    • .build()
  • build_monster_scheduler
    • .add_system( random_move_system() )
    • .add_system( chasing_system() )
    • .add_system( combat_system() )
    • .add_system( movement_system() )
    • .add_system( fov_system() )
    • .add_system( map(), entity(), hud() )
    • .add_system( *end_turn_system()* )
    • .build()

(abbreviated so as to not get too long-winded)

So right off the bat it seems like there is a lot of repetition, they seem to share a lot of the systems, but maybe legion takes care of that behind the scenes?

In the end_turn_system, there is this relevant state switching code:

let mut new_state = match current_state {
    TurnState::AwaitingInput => return,
    TurnState::PlayerTurn => TurnState::MonsterTurn,
    TurnState::MonsterTurn => TurnState::AwaitingInput
    _ => current_state
};

The TurnState enum has these variations:

  • AwaitingInput
  • PlayerTurn
  • MonsterTurn
  • GameOver
  • Victory

So far I've changed the TurnState to be GamePlay, GameOver, and Victory, and added changed the schedulers to be one "main"/realtime one, and one for rendering.

The current realtime version looks like this:

pub fn build_realtime_scheduler() -> Schedule {
    Schedule::builder()
        .add_system(player_input::player_input_system())
        .add_system(random_move::random_move_system())
        .add_system(chasing::chasing_system())
        .flush()
        .add_system(combat::combat_system())
        .flush()
        .add_system(movement::movement_system())
        .flush()
        .add_system(fov::fov_system())
        .flush()
        .add_system(end_turn::end_turn_system())
        .build()
}

With a separate schedule for rendering:

pub fn build_render_scheduler() -> Schedule {
    Schedule::builder()
        .flush()
        .add_system(map_render::map_render_system())
        .add_system(entity_render::entity_render_system())
        .add_system(hud::hud_system())
        .add_system(tooltips::tooltips_system())
        .build()
}

I think I've sat on this for too long and gotten myself confused, but I can't think of how to divide up the systems when it's a real-time simulation I want -- without affecting how responsive the game feels.

I have a resource in the world for frame_time_ms, which adds the elapsed time since the last update. If that is over my pub const TURN_TIME: f32 = 60.0, the game runs the gameplay systems.

I feel like I have the parts needed, just need some help on how to stitch it all together.

The entire code is on github.com/harofax/mt_septune I've probably forgotten some prudent info to include. (There's a branch called real-time-refactor where I've pushed how the code looks today)

Sorry for the absolutely massive post, and hello! This is my first time posting here! ^^

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.