Implement hitbox viewer #81
Generated
+407
-151
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -13,7 +13,7 @@ debug_features = ["bevy/free_camera"]
|
|||||||
gamecube-adapter = ["dep:gc-adapter-neo"]
|
gamecube-adapter = ["dep:gc-adapter-neo"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.19", features = ["asset_processor", "basis-universal", "compressed_image_saver", "bevy_settings"] }
|
bevy = { version = "0.19.1", features = ["asset_processor", "basis-universal", "compressed_image_saver", "bevy_settings"] }
|
||||||
# Loads the scenes you author in the editor (assets/*.bsn) and builds
|
# Loads the scenes you author in the editor (assets/*.bsn) and builds
|
||||||
# physics colliders from them. An ordinary dependency; remove it if you
|
# physics colliders from them. An ordinary dependency; remove it if you
|
||||||
# stop using authored scenes.
|
# stop using authored scenes.
|
||||||
@@ -24,6 +24,7 @@ arrayvec = "0.7.8"
|
|||||||
bevy-inspector-egui = "0.37.0"
|
bevy-inspector-egui = "0.37.0"
|
||||||
bevy_egui = "0.40.0"
|
bevy_egui = "0.40.0"
|
||||||
bevy_ggrs = { git = "https://github.com/gschup/bevy_ggrs" }
|
bevy_ggrs = { git = "https://github.com/gschup/bevy_ggrs" }
|
||||||
|
bevygraph = { git = "https://github.com/tbillington/bevy_debug_view" }
|
||||||
serde = "1.0.229"
|
serde = "1.0.229"
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
|
|||||||
@@ -99,4 +99,5 @@ pub enum AnimKind {
|
|||||||
AttackForwardTilt,
|
AttackForwardTilt,
|
||||||
AttackDownTilt,
|
AttackDownTilt,
|
||||||
AttackNeutralAir,
|
AttackNeutralAir,
|
||||||
|
AttackForwardAir,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ impl AttackKind {
|
|||||||
AttackKind::NeutralAir => AnimKind::AttackNeutralAir,
|
AttackKind::NeutralAir => AnimKind::AttackNeutralAir,
|
||||||
AttackKind::Jab => AnimKind::AttackJab1,
|
AttackKind::Jab => AnimKind::AttackJab1,
|
||||||
AttackKind::UpTilt => AnimKind::AttackUpTilt,
|
AttackKind::UpTilt => AnimKind::AttackUpTilt,
|
||||||
AttackKind::ForwardAir => AnimKind::AttackUpTilt,
|
AttackKind::ForwardAir => AnimKind::AttackForwardAir,
|
||||||
AttackKind::BackAir => AnimKind::AttackUpTilt,
|
AttackKind::BackAir => AnimKind::AttackUpTilt,
|
||||||
AttackKind::DownAir => AnimKind::AttackUpTilt,
|
AttackKind::DownAir => AnimKind::AttackUpTilt,
|
||||||
AttackKind::UpAir => AnimKind::AttackUpTilt,
|
AttackKind::UpAir => AnimKind::AttackUpTilt,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Standalone, read-only attack-script previewer used by `hitbox-editor`.
|
//! Standalone, read-only attack-script previewer used by `hitbox-editor`.
|
||||||
|
|
||||||
use bevy::{gltf::Gltf, prelude::*, world_serialization::WorldAssetRoot};
|
use bevy::{camera::CameraProjection, gltf::Gltf, prelude::*, world_serialization::WorldAssetRoot};
|
||||||
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
||||||
use clap::{Parser, ValueEnum};
|
use clap::{Parser, ValueEnum};
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ impl Plugin for HitboxViewerPlugin {
|
|||||||
})
|
})
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
FixedUpdate,
|
||||||
(
|
(
|
||||||
request_fighter_dependencies,
|
request_fighter_dependencies,
|
||||||
spawn_preview_fighter,
|
spawn_preview_fighter,
|
||||||
@@ -532,11 +532,82 @@ fn draw_ui(
|
|||||||
ui.label("Waiting for the processed attack script…");
|
ui.label("Waiting for the processed attack script…");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
draw_summary_timeline(ui, &mut state, script, end);
|
||||||
draw_timeline(ui, &mut state, script, end);
|
draw_timeline(ui, &mut state, script, end);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_summary_timeline(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
state: &mut ViewerState,
|
||||||
|
script: &FighterAttackScript,
|
||||||
|
end: u32,
|
||||||
|
) {
|
||||||
|
let row_height = 20.0;
|
||||||
|
let label_width = 55.0;
|
||||||
|
let height = (row_height + 25.0f32).min(ui.available_height());
|
||||||
|
let (response, painter) = ui.allocate_painter(
|
||||||
|
egui::vec2(ui.available_width(), height),
|
||||||
|
egui::Sense::click_and_drag(),
|
||||||
|
);
|
||||||
|
let rect = response.rect;
|
||||||
|
let timeline = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(rect.left() + label_width, rect.top()),
|
||||||
|
rect.right_bottom(),
|
||||||
|
);
|
||||||
|
if (response.clicked() || response.dragged())
|
||||||
|
&& let Some(pointer) = response.interact_pointer_pos()
|
||||||
|
&& timeline.contains(pointer)
|
||||||
|
{
|
||||||
|
let fraction = ((pointer.x - timeline.left()) / timeline.width()).clamp(0.0, 1.0);
|
||||||
|
state.frame = ((end.saturating_sub(1)) as f32 * fraction).round() as u32;
|
||||||
|
state.playing = false;
|
||||||
|
}
|
||||||
|
for frame in 0..end {
|
||||||
|
if frame % 5 != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let x = frame_x(timeline, frame, end);
|
||||||
|
painter.line_segment(
|
||||||
|
[egui::pos2(x, rect.top()), egui::pos2(x, rect.bottom())],
|
||||||
|
egui::Stroke::new(1.0_f32, egui::Color32::from_gray(55)),
|
||||||
|
);
|
||||||
|
painter.text(
|
||||||
|
egui::pos2(x + 2.0, rect.top()),
|
||||||
|
egui::Align2::LEFT_TOP,
|
||||||
|
frame.to_string(),
|
||||||
|
egui::FontId::monospace(10.0),
|
||||||
|
egui::Color32::GRAY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for frame in 0..end {
|
||||||
|
let top = rect.top() + 20.0 as f32;
|
||||||
|
painter.text(
|
||||||
|
egui::pos2(rect.left(), top + 2.0),
|
||||||
|
egui::Align2::LEFT_TOP,
|
||||||
|
format!("HB {}", 2),
|
||||||
|
egui::FontId::monospace(12.0),
|
||||||
|
egui::Color32::LIGHT_GRAY,
|
||||||
|
);
|
||||||
|
let active_hitbox = script
|
||||||
|
.hitboxes
|
||||||
|
.iter()
|
||||||
|
.filter(|hitbox| hitbox.start_frame <= frame && hitbox.end_frame > frame)
|
||||||
|
.max_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
let bar = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(frame_x(timeline, frame, end), top + 2.0),
|
||||||
|
egui::pos2(frame_x(timeline, frame + 1, end), top + row_height - 2.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(_active_hitbox) = active_hitbox {
|
||||||
|
painter.rect_filled(bar, 2.0, egui::Color32::from_rgb(245, 75, 55));
|
||||||
|
} else {
|
||||||
|
painter.rect_stroke(bar, 2.0, egui::Stroke::new(2.0f32, egui::Color32::from_rgb(75, 75, 75)), egui::StrokeKind::Inside);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_timeline(
|
fn draw_timeline(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
state: &mut ViewerState,
|
state: &mut ViewerState,
|
||||||
|
|||||||
Reference in New Issue
Block a user