MrGVSV / bevy_proto

Create config files for entities in Bevy

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Access to world in on_load_prototype?

zwazel opened this issue · comments

Hi, I have following scenario.
I'm working on adding mod support to my game, and I want to be able to define Mods via .ron files that then are interpreted by the game into entities. And with this crate that works great!
I encountered one problem though, I want the user to have control over when the mod will be spawned. For example, a UI Manager that will be spawned once the MainMenu is ready.

For this I've created an Enum that defines in what schedule it should be spawned. The only problem, for this to work, I need to register the name of the Prototype in a Bevy resource.
But in the on_load_prototype of a custom loader, as far as I can see, i don't have any way to do so?

   fn on_load_prototype(
        &self,
        mut prototype: Prototype,
        meta: &bevy_proto::backend::load::ProtoLoadMeta<Prototype>,
    ) -> Result<Prototype, Self::Error> {
        // Register for spawn on schedule, if any.
       // Getting the input, works great!
        if let Some(spawn_on_schedule) = prototype.schematics().get::<SpawnOnSchedule>() {
            let spawn_on_schedule = spawn_on_schedule
                .input()
                .downcast_ref::<SpawnOnSchedule>()
                .unwrap();

            // somehow get access to the world
            let mut world meta.world_mut();
            world.resource_scope(|world, mods_to_spawn_resource|{
                // add to the resource
            });
        }

        Ok(prototype)
    }

Something like that, is there maybe a possible workaround?

I want the user to have control over when the mod will be spawned. For example, a UI Manager that will be spawned once the MainMenu is ready.

Just another user here chiming in. Not sure if I understand you 100%, but couldn't this be addressed by having a separate "boolean if toggle" for the load functions?

pseudo code (haven't tested):

fn load(mut prototypes: PrototypesMut, is_mod_enabled: Res<IsMod>) {
    // you can make the paths array to be more intelligent by scanning specific mod folder and load them automatically
    let paths = ["examples/custom_loader/Player.mod.ron"];  
    if is_mod_enabled {
       for path in paths {
        prototypes.load(path);
    }}
}

One thing to be extra careful of, is that the prototype has some delay in loading the ron files, so need to ensure to use prototype.is_ready before using them. You might also have the toggle to be specific for each mod rather one big toggle. Hopefully this helps.

Thanks for the answer, but I think this is not what I'm trying to achieve!
But I think it goes into the correct direction.

My main goal is, that I can define a schedule (a state transition) on which the mod should be spawned on, not loaded.

Currently my prototype file looks something like this:

(
    name: "UiManager2",
    schematics: {
        "pgc::states::SpawnOnSchedule": (
            OnEnter(
                Loading(
                    PreMainMenu
                )
            ),
            false
        ),
        "pgc::states::DespawnOnSchedule": (
            OnExit(
                Menu(
                    MainMenu
                )
            ),
            true
        ),
    }
)

DespawnOnSchedule works perfectly, because it's a component inerted to the entity.
But I can't do the same thing with SpawnOnSchedule, as it can't be a component, because the entity hasn't been spawned yet, but this info tells me when that should happen!

SpawnOnSchedule implements Schematic simply empty, so it doesn't insert the component (because I don't need it)

pub struct SpawnOnSchedule(pub AllStatesScheduleHandler, pub bool);

impl Schematic for SpawnOnSchedule {
    type Input = Self;

    fn apply(_input: &Self::Input, _context: &mut SchematicContext) {}

    fn remove(_input: &Self::Input, _context: &mut SchematicContext) {}
}

So my current Idea is to use the on_load_prototype function of my custom Loader, because it seems to be the perfect place to do this.
I can easily access the SpawnOnSchedule in there:

if let Some(spawn_on_schedule) = prototype.schematics().get::<SpawnOnSchedule>() {
            let spawn_on_schedule = spawn_on_schedule
                .input()
                .downcast_ref::<SpawnOnSchedule>()
                .unwrap();
        }

And then all I need to do would be get a specific resource and insert the schedule and the Name of the prototype, so that the rest of my bevy backend can handle the spawning at said state transition.

That would be the easiest, probably.

But thanks to your comment I got another Idea, which will probably work, but seems like more work.

I now got following solution, that works:

fn load(
    mut prototypes: PrototypesMut,
    asset_server: Res<AssetServer>,
    prototype_assets: Res<Assets<Prototype>>,
    mut mods_loading: Local<Vec<Handle<Prototype>>>,
    mut mods_started_loading: Local<bool>,
    mut spawn_on_schedule: ResMut<ModsToSpawnOnSchedule>,
) {
    if !*mods_started_loading {
        let paths = vec!["examples/custom_loader/Player.mod.ron"];
        for path in paths {
            // keep a reference to all mods that are loading
            mods_loading.push(prototypes.load(path));
        }

        *mods_started_loading = true;
    } else {
        // go through all loaded mods, check which ones are ready
        mods_loading.retain(|mod_handle| match asset_server.get_load_state(mod_handle) {
            LoadState::Loading => {
                // keep it loading
                true
            }
            LoadState::Loaded => {
                // if they're loaded, get the prototype and check if it has a SpawnOnSchedule.
                let prototype = prototype_assets.get(mod_handle).unwrap();
                if let Some(spawn_schedule) = prototype.schematics().get::<SpawnOnSchedule>() {
                    let spawn_schedule = spawn_schedule
                        .input()
                        .downcast_ref::<SpawnOnSchedule>()
                        .unwrap();

                    // if it does, put it in the resource for later use
                    if let Some(mods) = spawn_on_schedule.mods.get_mut(&spawn_schedule.0) {
                        mods.push((prototype.id().clone(), spawn_schedule.1));
                    } else {
                        spawn_on_schedule.mods.insert(
                            spawn_schedule.0.clone(),
                            vec![(prototype.id().clone(), spawn_schedule.1)],
                        );
                    }
                }

                // remove it from the local list
                false
            }
            LoadState::Failed => {
                // remove the mod from the list (also notify about error somehow)
                false
            }
            LoadState::NotLoaded => todo!(),
            LoadState::Unloaded => todo!(),
        });
    }

    if mods_loading.is_empty() {
        // done loading (or there was nothing to load), next state
    }
}

Awesome work and thanks for sharing. Happy to trigger some ideas. I myself often stuck when doing solo dev.