-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add way to insert components to loaded scenes #4980
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
hook
field toSceneBundle
. Thehook
is aSceneHook
, which accepts a closure, that is ran once per entity in thescene. The closure can use the
EntityRef
argument to check componentsthe entity has, and use the
EntityCommands
to 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
glb
file 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:
Handle
s from asset resources as components). Here, this is by passing theself
parameter to theHook::hook_entity
method. This also allows using closures with captured environment.EntityRef
argument for that purpose.EntityCommands
argument 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 theSceneHook
component), 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
SceneHook
will be added to allSceneBundle
run_hook
will run once per added scene, though if theSceneHook
is not specified, the default value will causerun_hook
to iterate over all entities added to the scene with a no-op, only once.SceneHooked
marker component added once they are fully loaded, which can have other benefits.Changelog
hook
field toSceneBundle
, to make it easy to add components to entities loaded from any scene format.