Add way to insert components to loaded scenes#4980
Add way to insert components to loaded scenes#4980nicopap wants to merge 3 commits intobevyengine:mainfrom
Conversation
|
The details of this were discussed in https://discord.com/channels/691052431525675048/983050018179121202 . Furthermore, there is debate on whether to store a |
|
Closes #4933.
My thoughts on it: #4933 (comment) |
| /// Marker Component for scenes that were hooked. | ||
| #[derive(Component)] | ||
| #[non_exhaustive] | ||
| pub struct SceneHooked; |
There was a problem hiding this comment.
Note that this component is automatically loaded into all scenes added with SceneBundle, since SceneHook is a component of the SceneBundle.
This adds the `hook` field to `SceneBundle`. The `hook` is a `SceneHook`, which accepts a closure, that is ran once per entity in the scene. The closure can use the `EntityRef` argument to check components the entity has, and use the `EntityCommands` to add components and children to the entity. This code is adapted from https://github.com/nicopap/bevy-scene-hook Co-authored-by: Hennadii Chernyshchyk <genaloner@gmail.com>
|
Note that this PR doesn't include re-running the hook on hot-reloading (which should happen) because I'd rather see #4552 merged first. The scene_spawner code is tricky and I'd rather clean it up before adding that functionality. |
|
We can also imagine passing special meta-data to the |
|
This feel to me like a workaround to the fact that both scenes and asset loading are not finished in Bevy yet. If Bevy scenes were ready to use, you could specify the components you want to add directly in the scene file. But as they are not ready to use to specify a scene with depending assets, we're falling back on gltf for that. Once the asset loader will be more feature complete, it should be able to specify actions to execute on the assets once they are loaded. I tend to work around this in two ways. The first one is cleaner and better when the same scene will be spawned multiple times, I tend to do the second one for scenes that are spawned only once as it's somewhat quicker and dirtier. Now that I've written both next to each other, I'm not even sure it's quicker anymore Modify the scene asset so that every scene spawned from it will have the components I wantload the scene -> modify the scene -> spawn the scene (#1665 would have provided a nicer api for that without needing an extra resource...) This will modify the scene asset, changing the inner world of the scene. All struct MyScene {
handle: Handle<Scene>,
modified: bool,
}
fn on_scene_loaded(
mut commands: Commands,
mut scenes: ResMut<Assets<Scene>>,
asset_server: Res<AssetServer>,
my_scene: Option<ResMut<MyScene>>,
) {
if let Some(mut my_scene) = my_scene {
if !my_scene.modified {
if let Some(mut scene) = scenes.get_mut(&my_scene.handle) {
// do whatever on scene.world
my_scene.modified = true;
}
}
} else {
commands.insert_resource(MyScene {
handle: asset_server.load("my_gltf_file.gltf#Scene0"),
modified: false,
});
}
}Modify spawned scenes after they are spawnedload the scene -> spawn the scene -> modify the scene This works more or less like this PR, but unlike it I need to add one system for each scene type I want to modify. This should handle hot-reloaded scenes though: #[derive(Component)]
struct NewScene;
fn on_scene_spawned(
new_scenes: Query<Entity, (Changed<SceneInstance>, With<MyTagForMyScene>)>,
ready_scenes: Query<(Entity, &SceneInstance), With<NewScene>>,
scene_spawner: Res<SceneSpawner>,
mut commands: Commands,
query: Query<()>,
) {
for entity in new_scenes.iter() {
commands.entity(entity).insert(NewScene);
}
for (entity, instance) in ready_scenes.iter() {
if !scene_spawner.instance_is_ready(**instance) {
continue;
}
commands.entity(entity).remove::<NewScene>();
for entity in scene_spawner.iter_instance_entities(**instance).unwrap() {
if let Ok(query_result) = query.get(entity) {
// do whatever I want with my entity that passed the query here
}
}
}
} |
|
Having to choose between |
@mockersf Agree.
Yes, but it's very boilerplate. Imagine creating a new system and component for each scene you want to spawn. I described it in #4933. But my suggestion was simpler - no trait, just a a component which calls user-defined function on each entity. The suggestion was inspired by similar Godot feature: https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_scenes.html#custom-script |
This lowers API surface and complexity.
|
I've updated the PR to remove the |
From the objective of the PR, I think it makes more sense to implement something on the scene asset that on the spawned scene. More generally, I think there are three things here that need improvements:
|
You are right, it's better to preprocess assets on loading once. I like the direction of #3972. But it will require a huge rework. Probably it's better to keep this as a third-party plugin until we improve the asset system. Will use the author's plugin https://github.com/nicopap/bevy-scene-hook. |
|
This is closed in favor of an eventual better integrated solution. Please review the previous discussion for pointers on how it could look like. Until an integrated solution is implemented, you can use https://github.com/nicopap/bevy-scene-hook, which is the exact same code as this PR, but as a third party plugin. |
Based on bevyengine/bevy#4980 feedback
Based on bevyengine/bevy#4980 feedback
This adds the
hookfield toSceneBundle. Thehookis aSceneHook, which accepts a closure, that is ran once per entity in thescene. The closure can use the
EntityRefargument to check componentsthe entity has, and use the
EntityCommandsto add components andchildren to the entity.
This code is adapted from https://github.com/nicopap/bevy-scene-hook
Objective
Simplify and streamline adding components to entities within scenes that were just loaded.
Often, when loading objects from a
glbfile or any scene format, we want to add custom ad-hoc components the scene format doesn't handle.For example, when loading a 3d model, we might be interested in adding components to handle physics interactions or basic animation. We could also want to add marker components so that it's easier to work with the entities that were added to the world through the scene.
A good example of this pattern can be seen in the warlock's gambit source: https://github.com/team-plover/warlocks-gambit/blob/3f8132fbbd6cce124a1cd102755dad228035b2dc/src/scene.rs
Closes #4933
Solution
The solution involves running a function once per entity added to the scene. This has 3 requirements:
Handles from asset resources as components). Here, this is by passing theselfparameter to theHook::hook_entitymethod. This also allows using closures with captured environment.EntityRefargument for that purpose.EntityCommandsargument as well.Ideally, we'd rather not want to have to add an individual system per scene. And this is why we use a dynamic object
Box<dyn Hook>stored in a component.This is added as a component in
SceneBundle(more precisely, as a field of theSceneHookcomponent), since we want to associate scenes with the hook that run for them.@Sheepyhead & @Shatur will most likely be interested in this PR.
Drawbacks & Side effects
SceneHookwill be added to allSceneBundlerun_hookwill run once per added scene, though if theSceneHookis not specified, the default value will causerun_hookto iterate over all entities added to the scene with a no-op, only once.SceneHookedmarker component added once they are fully loaded, which can have other benefits.Changelog
hookfield toSceneBundle, to make it easy to add components to entities loaded from any scene format.