Compare commits
2 Commits
b756819088
...
5f955c1a53
Author | SHA1 | Date |
---|---|---|
kaosat.dev | 5f955c1a53 | |
kaosat.dev | ec7dc2cb48 |
|
@ -32,7 +32,7 @@ It also allows you to setup 'blueprints' in Blender by using collections (the re
|
||||||
|
|
||||||
One crate to rule them all !
|
One crate to rule them all !
|
||||||
|
|
||||||
- [blenvy](./crates/blenvu/) This crate allows you to
|
- [blenvy](./crates/blenvy/) This crate allows you to
|
||||||
* define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
|
* define components direclty inside gltf files and instanciate/inject the components on the Bevy side.
|
||||||
* export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [blenvy](./tools/blenvy/README.md) add-on
|
* export your project's Bevy registry to json, in order to be able to generate custom component UIs on the Blender side in the Blender [blenvy](./tools/blenvy/README.md) add-on
|
||||||
* define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc
|
* define Blueprints/Prefabs for Bevy inside gltf files and spawn them in Bevy. With the ability to override and add components when spawning, efficient "level" loading etc
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -301,7 +301,8 @@ Bevy Side:
|
||||||
|
|
||||||
- [x] remove "Library" component & co
|
- [x] remove "Library" component & co
|
||||||
- [x] make "InBlueprint" non optional,
|
- [x] make "InBlueprint" non optional,
|
||||||
- [ ] and perhaps rename it to "FromBlueprint(BlueprintInfo)"
|
- [x] and perhaps rename it to "FromBlueprint"
|
||||||
|
- [ ] perhaps change it to FromBlueprint(BlueprintInfo)
|
||||||
|
|
||||||
- [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled
|
- [x] BlueprintInstanceDisabled => BlueprintInstanceDisabled
|
||||||
- [x] fix "remove component" operator from the rename/fix/update components panel
|
- [x] fix "remove component" operator from the rename/fix/update components panel
|
||||||
|
|
|
@ -199,7 +199,7 @@ There is also a ```BluePrintBundle``` for convenience , which just has
|
||||||
|
|
||||||
## Additional information
|
## Additional information
|
||||||
|
|
||||||
- When a blueprint is spawned, an ```InBlueprint``` component is inserted into all its children entities (and nested children etc)
|
- When a blueprint is spawned, an ```FromBlueprint``` component is inserted into all its children entities (and nested children etc)
|
||||||
- this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity
|
- this crate also provides a special optional ```GameWorldTag``` component: this is useful when you want to keep all your spawned entities inside a root entity
|
||||||
|
|
||||||
You can use it in your queries to add your entities as children of this "world"
|
You can use it in your queries to add your entities as children of this "world"
|
||||||
|
|
|
@ -17,6 +17,11 @@ pub struct BlueprintAnimations {
|
||||||
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
pub struct BlueprintAnimationPlayerLink(pub Entity);
|
pub struct BlueprintAnimationPlayerLink(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
/// Same as the above but for `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
|
||||||
|
/// which often is not the Blueprint or blueprint instance entity itself.
|
||||||
|
pub struct BlueprintAnimationInfosLink(pub Entity);
|
||||||
|
|
||||||
#[derive(Component, Reflect, Default, Debug)]
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
/// storage for scene level animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
/// storage for scene level animations for a given entity (hierarchy), essentially a clone of gltf's `named_animations`
|
||||||
|
@ -33,6 +38,11 @@ pub struct SceneAnimations {
|
||||||
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
pub struct SceneAnimationPlayerLink(pub Entity);
|
pub struct SceneAnimationPlayerLink(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
/// Same as the above but for scene's `AnimationInfos` components which get added (on the Blender side) to the entities that actually have the animations
|
||||||
|
/// which often is not the Blueprint or blueprint instance entity itself.
|
||||||
|
pub struct SceneAnimationInfosLink(pub Entity);
|
||||||
|
|
||||||
/// Stores Animation information: name, frame informations etc
|
/// Stores Animation information: name, frame informations etc
|
||||||
#[derive(Reflect, Default, Debug)]
|
#[derive(Reflect, Default, Debug)]
|
||||||
pub struct AnimationInfo {
|
pub struct AnimationInfo {
|
||||||
|
@ -77,7 +87,75 @@ pub struct AnimationMarkerReached {
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/*
|
|
||||||
|
/// triggers events when a given animation marker is reached for BLUEPRINT animations
|
||||||
|
pub fn trigger_blueprint_animation_markers_events(
|
||||||
|
animation_data: Query<(Entity, &BlueprintAnimationPlayerLink, &BlueprintAnimationInfosLink, &BlueprintAnimations)>,
|
||||||
|
// FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
|
||||||
|
animation_infos: Query<(&AnimationInfos, &AnimationMarkers)>,
|
||||||
|
animation_players: Query<&AnimationPlayer>,
|
||||||
|
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
|
||||||
|
|
||||||
|
animation_clips: Res<Assets<AnimationClip>>,
|
||||||
|
) {
|
||||||
|
for (entity, player_link, infos_link, animations) in animation_data.iter() {
|
||||||
|
|
||||||
|
for (animation_name, node_index) in animations.named_indices.iter() {
|
||||||
|
|
||||||
|
let animation_player = animation_players.get(player_link.0).unwrap();
|
||||||
|
let (animation_infos, animation_markers) = animation_infos.get(infos_link.0).unwrap();
|
||||||
|
|
||||||
|
if animation_player.animation_is_playing(*node_index) {
|
||||||
|
if let Some(animation) = animation_player.animation(*node_index) {
|
||||||
|
// animation.speed()
|
||||||
|
// animation.completions()
|
||||||
|
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
|
||||||
|
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
|
||||||
|
let animation_length_seconds = animation_clip.duration();
|
||||||
|
let animation_length_frames = animation_infos // FIXME: horribly inneficient
|
||||||
|
.animations
|
||||||
|
.iter()
|
||||||
|
.find(|anim| &anim.name == animation_name)
|
||||||
|
.unwrap()
|
||||||
|
.frames_length;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: we also need to take playback speed into account
|
||||||
|
let time_in_animation = animation.elapsed()
|
||||||
|
- (animation.completions() as f32) * animation_length_seconds;
|
||||||
|
let frame_seconds = (animation_length_frames / animation_length_seconds)
|
||||||
|
* time_in_animation;
|
||||||
|
// println!("frame seconds {}", frame_seconds);
|
||||||
|
let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
|
||||||
|
|
||||||
|
let matching_animation_marker = &animation_markers.0[animation_name];
|
||||||
|
|
||||||
|
if matching_animation_marker.contains_key(&frame) {
|
||||||
|
let matching_markers_per_frame =
|
||||||
|
matching_animation_marker.get(&frame).unwrap();
|
||||||
|
println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
|
||||||
|
// FIXME: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
|
||||||
|
let diff = frame as f32 - frame_seconds;
|
||||||
|
if diff < 0.1 {
|
||||||
|
for marker_name in matching_markers_per_frame {
|
||||||
|
animation_marker_events.send(AnimationMarkerReached {
|
||||||
|
entity,
|
||||||
|
animation_name: animation_name.clone(),
|
||||||
|
frame,
|
||||||
|
marker_name: marker_name.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// triggers events when a given animation marker is reached for INSTANCE animations
|
/// triggers events when a given animation marker is reached for INSTANCE animations
|
||||||
pub fn trigger_instance_animation_markers_events(
|
pub fn trigger_instance_animation_markers_events(
|
||||||
animation_infos: Query<(
|
animation_infos: Query<(
|
||||||
|
@ -87,14 +165,30 @@ pub fn trigger_instance_animation_markers_events(
|
||||||
&SceneAnimations,
|
&SceneAnimations,
|
||||||
&AnimationInfos,
|
&AnimationInfos,
|
||||||
)>,
|
)>,
|
||||||
animation_players: Query<(&AnimationPlayer)>,
|
animation_players: Query<&AnimationPlayer>,
|
||||||
animation_clips: Res<Assets<AnimationClip>>,
|
animation_clips: Res<Assets<AnimationClip>>,
|
||||||
animation_graphs: Res<Assets<AnimationGraph>>,
|
animation_graphs: Res<Assets<AnimationGraph>>,
|
||||||
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
|
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
|
||||||
) {
|
) {
|
||||||
for (entity, markers, link, animations, animation_infos) in animation_infos.iter() {
|
for (entity, markers, player_link, animations, animation_infos) in animation_infos.iter() {
|
||||||
let animation_player = animation_players.get(link.0).unwrap();
|
//let (animation_player, animation_transitions) = animation_players.get(player_link.0).unwrap();
|
||||||
let animation_clip = animation_clips.get(animation_player.animation_clip());
|
//let foo = animation_transitions.get_main_animation().unwrap();
|
||||||
|
|
||||||
|
for (animation_name, node_index) in animations.named_indices.iter() {
|
||||||
|
let animation_player = animation_players.get(player_link.0).unwrap();
|
||||||
|
if animation_player.animation_is_playing(*node_index) {
|
||||||
|
if let Some(animation) = animation_player.animation(*node_index) {
|
||||||
|
if let Some(animation_clip_handle) = animations.named_animations.get(animation_name) {
|
||||||
|
if let Some(animation_clip) = animation_clips.get(animation_clip_handle) {
|
||||||
|
|
||||||
|
println!("helooo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let animation_clip = animation_clips.get(animation_player.animation_clip());
|
||||||
// animation_player.play(animation)
|
// animation_player.play(animation)
|
||||||
|
|
||||||
if animation_clip.is_some() {
|
if animation_clip.is_some() {
|
||||||
|
@ -144,82 +238,6 @@ pub fn trigger_instance_animation_markers_events(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// triggers events when a given animation marker is reached for BLUEPRINT animations
|
|
||||||
pub fn trigger_blueprint_animation_markers_events(
|
|
||||||
animation_infos: Query<(Entity, &BlueprintAnimationPlayerLink, &BlueprintAnimations)>,
|
|
||||||
// FIXME: annoying hiearchy issue yet again: the Markers & AnimationInfos are stored INSIDE the blueprint, so we need to access them differently
|
|
||||||
all_animation_infos: Query<(Entity, &AnimationMarkers, &AnimationInfos, &Parent)>,
|
|
||||||
animation_players: Query<&AnimationPlayer>,
|
|
||||||
animation_clips: Res<Assets<AnimationClip>>,
|
|
||||||
mut animation_marker_events: EventWriter<AnimationMarkerReached>,
|
|
||||||
) {
|
|
||||||
for (entity, link, animations) in animation_infos.iter() {
|
|
||||||
let animation_player = animation_players.get(link.0).unwrap();
|
|
||||||
let animation_clip = animation_clips.get(animation_player.animation_clip());
|
|
||||||
|
|
||||||
// FIXME: horrible code
|
|
||||||
for (_, markers, animation_infos, parent) in all_animation_infos.iter() {
|
|
||||||
if parent.get() == entity {
|
|
||||||
if animation_clip.is_some() {
|
|
||||||
// println!("Entity {:?} markers {:?}", entity, markers);
|
|
||||||
// println!("Player {:?} {}", animation_player.elapsed(), animation_player.completions());
|
|
||||||
// FIMXE: yikes ! very inneficient ! perhaps add boilerplate to the "start playing animation" code so we know what is playing
|
|
||||||
let animation_name =
|
|
||||||
animations.named_animations.iter().find_map(|(key, value)| {
|
|
||||||
if value == animation_player.animation_clip() {
|
|
||||||
Some(key)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if animation_name.is_some() {
|
|
||||||
let animation_name = animation_name.unwrap();
|
|
||||||
let animation_length_seconds = animation_clip.unwrap().duration();
|
|
||||||
let animation_length_frames = animation_infos
|
|
||||||
.animations
|
|
||||||
.iter()
|
|
||||||
.find(|anim| &anim.name == animation_name)
|
|
||||||
.unwrap()
|
|
||||||
.frames_length;
|
|
||||||
// TODO: we also need to take playback speed into account
|
|
||||||
let time_in_animation = animation_player.elapsed()
|
|
||||||
- (animation_player.completions() as f32) * animation_length_seconds;
|
|
||||||
let frame_seconds = (animation_length_frames / animation_length_seconds)
|
|
||||||
* time_in_animation;
|
|
||||||
// println!("frame seconds {}", frame_seconds);
|
|
||||||
let frame = frame_seconds.ceil() as u32; // FIXME , bad hack
|
|
||||||
|
|
||||||
let matching_animation_marker = &markers.0[animation_name];
|
|
||||||
|
|
||||||
if matching_animation_marker.contains_key(&frame) {
|
|
||||||
let matching_markers_per_frame =
|
|
||||||
matching_animation_marker.get(&frame).unwrap();
|
|
||||||
// println!("FOUND A MARKER {:?} at frame {}", matching_markers_per_frame, frame);
|
|
||||||
// emit an event AnimationMarkerReached(entity, animation_name, frame, marker_name)
|
|
||||||
// FIXME: complete hack-ish solution , otherwise this can fire multiple times in a row, depending on animation length , speed , etc
|
|
||||||
let diff = frame as f32 - frame_seconds;
|
|
||||||
println!("diff {}", diff);
|
|
||||||
if diff < 0.1 {
|
|
||||||
for marker in matching_markers_per_frame {
|
|
||||||
animation_marker_events.send(AnimationMarkerReached {
|
|
||||||
entity,
|
|
||||||
animation_name: animation_name.clone(),
|
|
||||||
frame,
|
|
||||||
marker_name: marker.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, SpawnBlueprint, SubBlueprintsSpawnTracker};
|
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, FromBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
|
||||||
use bevy::asset::AssetEvent;
|
use bevy::asset::AssetEvent;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::scene::SceneInstance;
|
use bevy::scene::SceneInstance;
|
||||||
|
@ -21,7 +21,7 @@ pub(crate) fn react_to_asset_changes(
|
||||||
&BlueprintInfo,
|
&BlueprintInfo,
|
||||||
Option<&Children>,
|
Option<&Children>,
|
||||||
)>,
|
)>,
|
||||||
// blueprint_children_entities: Query<&InBlueprint>, => can only be used if the entites are tagged, right now that is optional...perhaps do not make it optional
|
blueprint_children_entities: Query<&FromBlueprint>, //=> can only be used if the entites are tagged
|
||||||
assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>,
|
assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>,
|
||||||
all_parents: Query<&Parent>,
|
all_parents: Query<&Parent>,
|
||||||
spawning_blueprints: Query<&BlueprintSpawning>,
|
spawning_blueprints: Query<&BlueprintSpawning>,
|
||||||
|
|
|
@ -136,13 +136,15 @@ impl Plugin for BlueprintsPlugin {
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(GltfBlueprintsSet::Spawn),
|
.in_set(GltfBlueprintsSet::Spawn),
|
||||||
)
|
)
|
||||||
/* .add_systems(
|
|
||||||
|
// animation
|
||||||
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
trigger_instance_animation_markers_events,
|
|
||||||
trigger_blueprint_animation_markers_events,
|
trigger_blueprint_animation_markers_events,
|
||||||
|
trigger_instance_animation_markers_events
|
||||||
),
|
),
|
||||||
)*/
|
)
|
||||||
// hot reload
|
// hot reload
|
||||||
.add_systems(Update, react_to_asset_changes.run_if(hot_reload));
|
.add_systems(Update, react_to_asset_changes.run_if(hot_reload));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashM
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations
|
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationInfosLink, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationInfosLink, SceneAnimationPlayerLink, SceneAnimations
|
||||||
};
|
};
|
||||||
|
|
||||||
/// this is a flag component for our levels/game world
|
/// this is a flag component for our levels/game world
|
||||||
|
@ -40,7 +40,7 @@ pub struct SpawnBlueprint;
|
||||||
#[derive(Component, Reflect, Default, Debug)]
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
/// flag component marking any spwaned child of blueprints
|
/// flag component marking any spwaned child of blueprints
|
||||||
pub struct InBlueprint;
|
pub struct FromBlueprint;
|
||||||
|
|
||||||
#[derive(Component, Reflect, Default, Debug)]
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
|
@ -570,10 +570,10 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we flag all children of the blueprint instance with 'InBlueprint'
|
// we flag all children of the blueprint instance with 'FromBlueprint'
|
||||||
// can be usefull to filter out anything that came from blueprints vs normal children
|
// can be usefull to filter out anything that came from blueprints vs normal children
|
||||||
for child in all_children.iter_descendants(blueprint_root_entity) {
|
for child in all_children.iter_descendants(blueprint_root_entity) {
|
||||||
commands.entity(child).insert(InBlueprint); // we do this here in order to avoid doing it to normal children
|
commands.entity(child).insert(FromBlueprint); // we do this here in order to avoid doing it to normal children
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -626,9 +626,14 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
|
||||||
all_names.get(child),
|
all_names.get(child),
|
||||||
all_names.get(original)
|
all_names.get(original)
|
||||||
);
|
);
|
||||||
/*commands
|
commands
|
||||||
.entity(original)
|
.entity(original)
|
||||||
.insert((BlueprintAnimationPlayerLink(bla),)); */
|
.insert((
|
||||||
|
//BlueprintAnimationPlayerLink(bla),
|
||||||
|
BlueprintAnimationInfosLink(child)
|
||||||
|
))
|
||||||
|
;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for parent in all_parents.iter_ancestors(child) {
|
for parent in all_parents.iter_ancestors(child) {
|
||||||
if animation_players.get(parent).is_ok() {
|
if animation_players.get(parent).is_ok() {
|
||||||
|
@ -651,6 +656,10 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if with_animation_infos.get(parent).is_ok() {
|
||||||
|
commands.entity(child).insert(SceneAnimationInfosLink(parent));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
({})
|
|
|
@ -1,12 +0,0 @@
|
||||||
({
|
|
||||||
"world":File (path: "models/StartLevel.glb"),
|
|
||||||
"level1":File (path: "models/Level1.glb"),
|
|
||||||
"level2":File (path: "models/Level2.glb"),
|
|
||||||
|
|
||||||
"models": Folder (
|
|
||||||
path: "models/library",
|
|
||||||
),
|
|
||||||
"materials": Folder (
|
|
||||||
path: "materials",
|
|
||||||
),
|
|
||||||
})
|
|
Loading…
Reference in New Issue