#Recreate game with new settings

4 messages · Page 1 of 1 (latest)

wide wyvern
#

My game exposes some user-configurable settings. If these settings change then I need to recreate it. The game is the majority of the code in my app, but things like "main menu" aren't part of the game itself.

Prior to this, I was writing code like:

pub struct MyPlugin;

impl Plugin for MyPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<MyResource>();
    }
}

I felt this was fine because there was a 1:1 relationship between App and Game.

Now that I wish to recreate the game with new settings, and having introduced a main menu to allow the user to define settings before creating a game, I have broken that expectation. There is now a 1:(0 or 1) relationship between App and Game.

I am left with a couple of options to manage my app state:

  1. Query for MyResource and manually reset it to its default value. The resource continues to exist in the app.
  2. Remove MyResource from the app in a teardown step. Add MyResource to the app in a setup step.

I dislike the first approach. It seems counter-intuitive because resetting to default, rather than destroy/recreate, feels like it isn't reflective of the fact a game is being destroyed and recreated within the app.

I dislike the second approach because it requires me to call init_resource and remove_resource using systems rather than making use of Plugin architecture.

Additionally, I dislike both approaches because I have to do a lot manually. I feel like there's a hierarchy here of "App" containing "Instance of Game" which is created with "Settings." I wish to destroy "Instance of Game" and recreate with new "Settings." It's effortful and error prone for me to iterate through all entities/resources that are part of "Game" not "App" and manage their lifecycles. I wish to destroy "Game" and have all of its related entities cleaned up.

If this were just components then I would be make use of parent/child + despawn_recursive, but I've already done that. This is all the related resources.

I've been going with the second approach for now. I am wondering if this seems correct? I don't see many examples of this being done when I look at other codebases.

pub fn setup_game_time(world: &mut World) {
    world.init_resource::<GameTime>();
    world.init_resource::<IsFastForwarding>();
    world.init_resource::<PendingTicks>();
}

pub fn teardown_game_time(world: &mut World) {
    world.remove_resource::<GameTime>();
    world.remove_resource::<IsFastForwarding>();
    world.remove_resource::<PendingTicks>();
}
app.add_systems(
    OnEnter(StoryState::Initializing),
    setup_game_time
);

app.add_systems(
    OnEnter(StoryState::Cleanup),
    teardown_game_time
);

I also want to confirm that this isn't where the notion of a "SubApp" would come into play, right? I assume not.

heavy fable
#

Would using insert_resource vs init_resource where appropriate help? The latter will do nothing if the resource already exists, while the former will replace the resource's value (thus you can use insert and push the default values). Then you could also use conditionals like resource_added or resource_changed with run_if to run any systems that will operate over the game when a change is detected (if necessary). This won't solve the fact that you need to do something about the resources because they are "global" to your app indeed.

wide wyvern
#

Yeah, as you mention it, I guess the cleanup step isn't strictly necessary if I change to insert or overwrite. I was feeling more confident in having a cleanup step so that I could "see" my app in a clean state before recreating because I thought it might be hard to tell if something was awry with the state after overwriting.

#

still, I would be adding all my resources in a system rather than inserting as part of plugin instantiation. Maybe that's normal enough though