Skip to content

Commit 8c5078a

Browse files
committed
Can now create skeletons from the editor
1 parent 3fb5a87 commit 8c5078a

6 files changed

Lines changed: 187 additions & 6 deletions

File tree

crates/bevy_animation_graph_core/src/skeleton/serial.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use bevy::reflect::Reflect;
1+
use bevy::{asset::AssetPath, reflect::Reflect};
22
use serde::{Deserialize, Serialize};
33

44
#[derive(Serialize, Deserialize)]
@@ -9,5 +9,8 @@ pub struct SkeletonSerial {
99

1010
#[derive(Clone, Reflect, Serialize, Deserialize)]
1111
pub enum SkeletonSource {
12-
Gltf { source: String, label: String },
12+
Gltf {
13+
source: AssetPath<'static>,
14+
label: String,
15+
},
1316
}

crates/bevy_animation_graph_editor/src/ui/core.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::ui::{
2222
native_windows::{NativeEditorWindow, NativeEditorWindowExtension},
2323
state_management::global::{
2424
GlobalState, clip::RequestCreateClip, fsm::RequestCreateFsm, ragdoll::RequestCreateRagdoll,
25+
ragdoll_bone_map::RequestCreateRagdollBoneMap, skeleton::RequestCreateSkeleton,
2526
},
2627
};
2728

@@ -145,18 +146,23 @@ fn menu_bar(ctx: &mut egui::Context, command_queue: &mut CommandQueue) {
145146
egui::MenuBar::new().ui(ui, |ui| {
146147
ui.menu_button("Assets", |ui| {
147148
ui.menu_button("Create", |ui| {
148-
if ui.button("Skeleton").clicked() {}
149+
if ui.button("Skeleton").clicked() {
150+
command_queue.push(trigger(RequestCreateSkeleton));
151+
}
149152
if ui.button("Animation").clicked() {
150153
command_queue.push(trigger(RequestCreateClip));
151154
}
152-
if ui.button("Animated scene").clicked() {}
153155
if ui.button("State machine").clicked() {
154156
command_queue.push(trigger(RequestCreateFsm));
155157
}
156158
if ui.button("Ragdoll").clicked() {
157159
command_queue.push(trigger(RequestCreateRagdoll));
158160
}
159-
if ui.button("Ragdoll bone map").clicked() {}
161+
if ui.button("Ragdoll bone map").clicked() {
162+
command_queue.push(trigger(RequestCreateRagdollBoneMap));
163+
}
164+
ui.disable();
165+
if ui.button("Animated scene").clicked() {}
160166
});
161167
})
162168
});

crates/bevy_animation_graph_editor/src/ui/native_windows/asset_creation/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod fsm;
33
pub mod generic_with_path;
44
pub mod ragdoll;
55
pub mod ragdoll_bone_map;
6+
pub mod skeleton;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use bevy::{
2+
asset::{AssetServer, Assets, Handle},
3+
gltf::Gltf,
4+
prelude::World,
5+
};
6+
use bevy_animation_graph::core::skeleton::serial::{SkeletonSerial, SkeletonSource};
7+
use egui_dock::egui;
8+
9+
use crate::ui::{
10+
generic_widgets::{popup_asset_picker::PopupAssetPicker, string_picker::StringPickerWidget},
11+
native_windows::{EditorWindowContext, NativeEditorWindowExtension},
12+
state_management::global::{CloseWindow, skeleton::CreateSkeleton},
13+
utils::asset_path,
14+
};
15+
16+
#[derive(Debug, Clone, Copy)]
17+
pub struct CreateSkeletonWindow;
18+
19+
impl NativeEditorWindowExtension for CreateSkeletonWindow {
20+
fn ui(&self, ui: &mut egui::Ui, world: &mut World, ctx: &mut EditorWindowContext) {
21+
ui.push_id("Skeleton asset creator", |ui| {
22+
let mut queue = ctx.make_queue();
23+
24+
let buffer_id = ui.id().with("buffer");
25+
let buffer: &mut SkeletonCreateBuffer = ctx.buffers.get_mut_or_default(buffer_id);
26+
27+
ui.label("asset path:");
28+
ui.text_edit_singleline(&mut buffer.path);
29+
30+
ui.label("GLTF file:");
31+
ui.add(PopupAssetPicker::new_salted(
32+
&mut buffer.gltf,
33+
world,
34+
"gtlf",
35+
));
36+
37+
// get gltf asset
38+
let gltf_assets = world.resource::<Assets<Gltf>>();
39+
let labels: Vec<String> = gltf_assets
40+
.get(&buffer.gltf)
41+
.map(|gltf| {
42+
(0..gltf.scenes.len())
43+
.map(|k| format!("Scene{}", k))
44+
.collect()
45+
})
46+
.unwrap_or_default();
47+
48+
ui.label("Scene label:");
49+
ui.add(
50+
StringPickerWidget::new(&mut buffer.label, &labels).salted("scene label picker"),
51+
);
52+
53+
let asset_server = world.resource::<AssetServer>();
54+
55+
ui.add_enabled_ui(validate_buffer(buffer), |ui| {
56+
ui.with_layout(egui::Layout::right_to_left(egui::Align::Max), |ui| {
57+
if ui.button("Create").clicked() {
58+
queue.trigger(CreateSkeleton {
59+
virtual_path: buffer.path.clone(),
60+
skeleton: SkeletonSerial {
61+
source: SkeletonSource::Gltf {
62+
source: asset_path(buffer.gltf.id().untyped(), asset_server),
63+
label: buffer.label.clone(),
64+
},
65+
},
66+
});
67+
queue.trigger_window(CloseWindow::default());
68+
}
69+
});
70+
});
71+
72+
ctx.consume_queue(queue);
73+
});
74+
}
75+
76+
fn display_name(&self) -> String {
77+
"Create skeleton".to_string()
78+
}
79+
}
80+
81+
fn validate_buffer(buffer: &SkeletonCreateBuffer) -> bool {
82+
buffer.gltf != Handle::default()
83+
&& !buffer.label.is_empty()
84+
&& buffer.path.ends_with(".skn.ron")
85+
}
86+
87+
#[derive(Default)]
88+
pub struct SkeletonCreateBuffer {
89+
path: String,
90+
gltf: Handle<Gltf>,
91+
label: String,
92+
}

crates/bevy_animation_graph_editor/src/ui/state_management/global/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod fsm;
1212
pub mod inspector_selection;
1313
pub mod ragdoll;
1414
pub mod ragdoll_bone_map;
15+
pub mod skeleton;
1516

1617
use std::marker::PhantomData;
1718

@@ -32,7 +33,7 @@ use crate::ui::state_management::global::{
3233
active_graph_context::ActiveContexts, active_graph_node::ActiveGraphNode,
3334
active_ragdoll::ActiveRagdoll, active_scene::ActiveScene, active_skeleton::ActiveSkeleton,
3435
clip::ClipManager, fsm::FsmManager, inspector_selection::InspectorSelection,
35-
ragdoll::RagdollManager, ragdoll_bone_map::RagdollBoneMapManager,
36+
ragdoll::RagdollManager, ragdoll_bone_map::RagdollBoneMapManager, skeleton::SkeletonManager,
3637
};
3738

3839
#[derive(Component)]
@@ -56,6 +57,7 @@ impl GlobalState {
5657
ClipManager::register(world, entity);
5758
RagdollManager::register(world, entity);
5859
RagdollBoneMapManager::register(world, entity);
60+
SkeletonManager::register(world, entity);
5961

6062
world.add_observer(CloseWindow::observe);
6163
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use bevy::ecs::{
2+
component::Component,
3+
entity::Entity,
4+
event::Event,
5+
observer::On,
6+
system::{Commands, Res, ResMut},
7+
world::World,
8+
};
9+
use bevy_animation_graph::core::skeleton::serial::SkeletonSerial;
10+
11+
use crate::{
12+
Cli,
13+
scanner::RescanAssets,
14+
ui::{
15+
UiState,
16+
core::EguiWindow,
17+
native_windows::{NativeEditorWindow, asset_creation::skeleton::CreateSkeletonWindow},
18+
state_management::global::RegisterStateComponent,
19+
},
20+
};
21+
22+
#[derive(Debug, Component, Default, Clone)]
23+
pub struct SkeletonManager;
24+
25+
impl RegisterStateComponent for SkeletonManager {
26+
fn register(world: &mut World, _global_state_entity: Entity) {
27+
world.add_observer(RequestCreateSkeleton::observe);
28+
world.add_observer(CreateSkeleton::observe);
29+
}
30+
}
31+
32+
/// Will open a "create FSM" window popup
33+
#[derive(Event)]
34+
pub struct RequestCreateSkeleton;
35+
36+
impl RequestCreateSkeleton {
37+
pub fn observe(
38+
_: On<RequestCreateSkeleton>,
39+
mut commands: Commands,
40+
ui_state: ResMut<UiState>,
41+
) {
42+
if let Some(active_view_idx) = ui_state.active_view {
43+
let view = &ui_state.views[active_view_idx];
44+
let win =
45+
NativeEditorWindow::create_cmd(&mut commands, view.entity, CreateSkeletonWindow);
46+
47+
let UiState { views, .. } = ui_state.into_inner();
48+
49+
views[active_view_idx]
50+
.dock_state
51+
.add_window(vec![EguiWindow::EntityWindow(win)]);
52+
}
53+
}
54+
}
55+
56+
#[derive(Event)]
57+
pub struct CreateSkeleton {
58+
pub virtual_path: String,
59+
pub skeleton: SkeletonSerial,
60+
}
61+
62+
impl CreateSkeleton {
63+
pub fn observe(event: On<CreateSkeleton>, cli: Res<Cli>, mut commands: Commands) {
64+
let mut final_path = cli.asset_source.clone();
65+
final_path.push(&event.virtual_path);
66+
bevy::log::info!("Creating skeleton at {:?}", final_path);
67+
ron::Options::default()
68+
.to_io_writer_pretty(
69+
std::fs::File::create(final_path).unwrap(),
70+
&event.skeleton,
71+
ron::ser::PrettyConfig::default(),
72+
)
73+
.unwrap();
74+
75+
commands.trigger(RescanAssets);
76+
}
77+
}

0 commit comments

Comments
 (0)