Initial work on proper character movement
This commit is contained in:
Generated
+28
@@ -479,6 +479,12 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "az"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -2055,6 +2061,14 @@ dependencies = [
|
|||||||
"thread_local",
|
"thread_local",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_wind_waker_shader"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "git+https://github.com/lavaeater/bevy_wind_waker_shader.git#1b017c3ef6a4fbdaff115df9ec5ae4954b2fb6ba"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_window"
|
name = "bevy_window"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@@ -3211,6 +3225,18 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixed"
|
||||||
|
version = "1.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"bytemuck",
|
||||||
|
"half",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
@@ -3405,8 +3431,10 @@ dependencies = [
|
|||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
"bevy_egui",
|
"bevy_egui",
|
||||||
"bevy_ggrs",
|
"bevy_ggrs",
|
||||||
|
"bevy_wind_waker_shader",
|
||||||
"bitfields",
|
"bitfields",
|
||||||
"clap",
|
"clap",
|
||||||
|
"fixed",
|
||||||
"jackdaw_runtime",
|
"jackdaw_runtime",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ bevy_ggrs = { git = "https://github.com/gschup/bevy_ggrs" }
|
|||||||
serde = "1.0.229"
|
serde = "1.0.229"
|
||||||
bitfields = "3.0.0"
|
bitfields = "3.0.0"
|
||||||
clap = { version = "4.6.6", features = ["derive"] }
|
clap = { version = "4.6.6", features = ["derive"] }
|
||||||
|
bevy_wind_waker_shader = { git = "https://github.com/lavaeater/bevy_wind_waker_shader.git" }
|
||||||
|
fixed = "1.31.0"
|
||||||
|
|
||||||
# Enable a small amount of optimization in the dev profile.
|
# Enable a small amount of optimization in the dev profile.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use bevy::{mesh::skinning::SkinnedMesh, prelude::*};
|
||||||
|
|
||||||
|
use crate::{fighter::{FighterECB, FighterTranslation, FighterVisual}, player::Player};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct FighterAnimations {
|
||||||
|
pub wait: Handle<AnimationClip>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct FighterAnimationPlayerLink(Entity);
|
||||||
|
|
||||||
|
pub fn apply_fighter_translation_to_visuals(query: Query<(&FighterTranslation, &FighterECB, &mut Transform)>) {
|
||||||
|
for (translation, ecb, mut transform) in query {
|
||||||
|
let translation = translation.0 + ecb.get_bottom_point();
|
||||||
|
transform.translation = Vec3::new(translation.x, translation.y, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_fighter_animation_player(
|
||||||
|
mut commands: Commands,
|
||||||
|
animated: Query<Entity, Added<AnimationPlayer>>,
|
||||||
|
parents: Query<&ChildOf>,
|
||||||
|
fighters: Query<Entity, With<Player>>,
|
||||||
|
) {
|
||||||
|
for child in &animated {
|
||||||
|
let Some(parent) = parents
|
||||||
|
.iter_ancestors(child)
|
||||||
|
.filter_map(|parent| fighters.get(parent).ok())
|
||||||
|
.next()
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
commands.entity(parent).insert(
|
||||||
|
FighterAnimationPlayerLink(child)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_animation(
|
||||||
|
children: Query<&mut Transform, With<FighterVisual>>,
|
||||||
|
) {
|
||||||
|
for mut trf in children {
|
||||||
|
// Melee model scale is in decimeters
|
||||||
|
trf.scale = Vec3::splat(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,11 @@ pub struct FighterAttributes {
|
|||||||
pub stick_dash_accel: f32,
|
pub stick_dash_accel: f32,
|
||||||
pub max_dash_vel: f32,
|
pub max_dash_vel: f32,
|
||||||
|
|
||||||
|
pub jump_vertical_velocity: f32,
|
||||||
|
|
||||||
pub ground_friction: f32,
|
pub ground_friction: f32,
|
||||||
|
pub terminal_velocity: f32,
|
||||||
|
pub gravity: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterAttributes {
|
impl FighterAttributes {
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
use bevy::{color::palettes::css::HOT_PINK, prelude::*};
|
use bevy::{color::palettes::css::HOT_PINK, prelude::*};
|
||||||
|
|
||||||
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::stage::StageLine;
|
use crate::fighter::state::FighterStateContext;
|
||||||
|
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||||
|
use crate::stage::StagePoly;
|
||||||
|
use crate::stage::line::{StageCollision, StageLineID};
|
||||||
|
/*
|
||||||
pub fn collide_fighter_with_scene(
|
pub fn collide_fighter_with_scene(
|
||||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||||
stage_planes: Query<&StageLine>,
|
stage_planes: Query<&StagePoly>,
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
) {
|
) {
|
||||||
for (ecb, mut velocity, mut translation) in fighters {
|
for (ecb, mut velocity, mut translation) in fighters {
|
||||||
@@ -29,3 +32,36 @@ pub fn collide_fighter_with_scene(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub struct AirCollisionWithStageResult {
|
||||||
|
pub line_id: StageLineID,
|
||||||
|
pub hit_position: Vec2,
|
||||||
|
pub hit_normal: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn air_collide_with_stage(
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<AirCollisionWithStageResult> {
|
||||||
|
let ray_segment = Segment2d::new(
|
||||||
|
state_context.prev_translation.0 + state_context.prev_ecb.get_bottom_point(),
|
||||||
|
state_context.translation.0 + state_context.ecb.get_bottom_point(),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (poly_idx, poly) in state_context.stage_collision.stage_polys.iter().enumerate() {
|
||||||
|
if let Some(intersect_result) = poly.intersect_ray(ray_segment) {
|
||||||
|
return Some(AirCollisionWithStageResult {
|
||||||
|
line_id: {
|
||||||
|
StageLineID {
|
||||||
|
polygon: poly_idx,
|
||||||
|
segment: intersect_result.segment_idx,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hit_position: intersect_result.position,
|
||||||
|
hit_normal: intersect_result.normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,3 +28,16 @@ pub fn fighter_debug(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_config(
|
||||||
|
mut config_store: ResMut<GizmoConfigStore>,
|
||||||
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
|
real_time: Res<Time<Real>>,
|
||||||
|
mut virtual_time: ResMut<Time<Virtual>>,
|
||||||
|
) {
|
||||||
|
if keyboard.just_pressed(KeyCode::KeyT) {
|
||||||
|
for (_, config, _) in config_store.iter_mut() {
|
||||||
|
config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-1
@@ -4,12 +4,18 @@ use bevy::prelude::*;
|
|||||||
/// the stage. Purely geometry — the collision response lives in
|
/// the stage. Purely geometry — the collision response lives in
|
||||||
/// [`super::collision`].
|
/// [`super::collision`].
|
||||||
#[derive(Component, Debug, Clone, Copy)]
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
#[require(Transform)]
|
#[require(FighterPreviousECB)]
|
||||||
pub struct FighterECB {
|
pub struct FighterECB {
|
||||||
pub vertical_half: f32,
|
pub vertical_half: f32,
|
||||||
pub horizontal_half: f32,
|
pub horizontal_half: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy, Default)]
|
||||||
|
pub struct FighterPreviousECB {
|
||||||
|
pub vertical_half: f32,
|
||||||
|
pub horizontal_half: f32,
|
||||||
|
}
|
||||||
|
|
||||||
impl FighterECB {
|
impl FighterECB {
|
||||||
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
||||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||||
@@ -18,4 +24,22 @@ impl FighterECB {
|
|||||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||||
[rel_up, rel_right, rel_down, rel_left]
|
[rel_up, rel_right, rel_down, rel_left]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_bottom_point(&self) -> Vec2 {
|
||||||
|
Vec2::new(0.0, -self.vertical_half)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterPreviousECB {
|
||||||
|
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
||||||
|
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||||
|
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
||||||
|
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
||||||
|
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||||
|
[rel_up, rel_right, rel_down, rel_left]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bottom_point(&self) -> Vec2 {
|
||||||
|
Vec2::new(0.0, -self.vertical_half)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-27
@@ -5,6 +5,7 @@ use bevy::prelude::*;
|
|||||||
use bevy_egui::EguiPrimaryContextPass;
|
use bevy_egui::EguiPrimaryContextPass;
|
||||||
use bevy_ggrs::prelude::*;
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
pub mod animation;
|
||||||
pub mod attributes;
|
pub mod attributes;
|
||||||
pub mod collision;
|
pub mod collision;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
@@ -20,7 +21,7 @@ pub use attributes::FighterAttributes;
|
|||||||
pub use ecb::FighterECB;
|
pub use ecb::FighterECB;
|
||||||
pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity, Grounded};
|
pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity, Grounded};
|
||||||
|
|
||||||
use crate::schedule::GameplaySet;
|
use crate::{fighter::state::FighterState, schedule::GameplaySet, stage::line::StageCollision};
|
||||||
use state::state_interrupt_system;
|
use state::state_interrupt_system;
|
||||||
|
|
||||||
/// Owns fighter simulation, the rollback registrations for fighter
|
/// Owns fighter simulation, the rollback registrations for fighter
|
||||||
@@ -28,43 +29,47 @@ use state::state_interrupt_system;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FighterPlugin;
|
pub struct FighterPlugin;
|
||||||
|
|
||||||
|
#[derive(Component, Copy, Clone, Hash)]
|
||||||
|
pub enum FighterFacingDirection {
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
#[require(Transform)]
|
||||||
|
pub struct FighterVisual;
|
||||||
|
|
||||||
impl Plugin for FighterPlugin {
|
impl Plugin for FighterPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app
|
app
|
||||||
// Every state gets a chance to interrupt itself, in priority order.
|
// Every state gets a chance to interrupt itself, in priority order.
|
||||||
.add_systems(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(animation::setup_fighter_animation_player, state::state_interrupt_system,).in_set(GameplaySet::Interrupt),
|
||||||
state_interrupt_system::<state::wait::WaitState>,
|
|
||||||
state_interrupt_system::<state::walk::WalkState>,
|
|
||||||
state_interrupt_system::<state::dash::DashState>,
|
|
||||||
state_interrupt_system::<state::run::RunState>,
|
|
||||||
)
|
|
||||||
.chain()
|
|
||||||
.in_set(GameplaySet::Interrupt),
|
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(state::state_update_system,).in_set(GameplaySet::StateUpdate),
|
||||||
state::wait::wait_update,
|
|
||||||
state::walk::walk_update,
|
|
||||||
state::dash::dash_update,
|
|
||||||
state::run::run_update,
|
|
||||||
)
|
)
|
||||||
.chain()
|
/*.add_systems(
|
||||||
.in_set(GameplaySet::StateUpdate),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(motion::integrate_gravity, motion::apply_motion)
|
(motion::integrate_gravity, motion::apply_grounded_motion)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(GameplaySet::Physics),
|
.in_set(GameplaySet::Physics),
|
||||||
|
)*/
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
state::state_collision_interrupt_system.in_set(GameplaySet::Collision),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
collision::collide_fighter_with_scene.in_set(GameplaySet::Collision),
|
(
|
||||||
|
animation::apply_fighter_translation_to_visuals,
|
||||||
|
animation::apply_animation,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(GameplaySet::Animation),
|
||||||
)
|
)
|
||||||
.add_observer(state::dash::on_insert_dash)
|
|
||||||
// Rollback registration lives next to the systems that write these
|
// Rollback registration lives next to the systems that write these
|
||||||
// components; a component simulated here but missing from this list
|
// components; a component simulated here but missing from this list
|
||||||
// is a desync waiting to happen.
|
// is a desync waiting to happen.
|
||||||
@@ -74,13 +79,12 @@ impl Plugin for FighterPlugin {
|
|||||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||||
.rollback_component_with_copy::<FighterAttributes>()
|
.rollback_component_with_copy::<FighterAttributes>()
|
||||||
.rollback_component_with_copy::<Grounded>()
|
.rollback_component_with_copy::<Grounded>()
|
||||||
.rollback_component_with_copy::<state::wait::WaitState>()
|
.rollback_resource_with_reflect::<StageCollision>()
|
||||||
.rollback_component_with_copy::<state::walk::WalkState>()
|
|
||||||
.rollback_component_with_copy::<state::dash::DashState>()
|
|
||||||
.rollback_component_with_copy::<state::run::RunState>()
|
|
||||||
.checksum_component::<state::dash::DashState>(state::dash::hash_dash_state)
|
|
||||||
// Debug views.
|
// Debug views.
|
||||||
.add_systems(FixedPostUpdate, debug::debug_draw_ecb)
|
.add_systems(FixedPostUpdate, debug::debug_draw_ecb)
|
||||||
.add_systems(EguiPrimaryContextPass, debug::fighter_debug);
|
.add_systems(EguiPrimaryContextPass, debug::fighter_debug)
|
||||||
|
.add_systems(EguiPrimaryContextPass, debug::update_config)
|
||||||
|
.rollback_component_with_clone::<FighterState>()
|
||||||
|
.checksum_component_with_hash::<FighterState>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::fighter::FighterAttributes;
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy)]
|
#[derive(Component, Clone, Copy)]
|
||||||
pub struct Grounded;
|
pub struct Grounded;
|
||||||
|
|
||||||
@@ -16,20 +18,27 @@ pub struct FighterPreviousTranslation(pub Vec2);
|
|||||||
#[require(FighterPreviousTranslation)]
|
#[require(FighterPreviousTranslation)]
|
||||||
pub struct FighterTranslation(pub Vec2);
|
pub struct FighterTranslation(pub Vec2);
|
||||||
|
|
||||||
pub fn integrate_gravity(_fighters: Query<&mut FighterVelocity>, _time: Res<Time<Fixed>>) {}
|
pub fn apply_air_motion(
|
||||||
|
|
||||||
pub fn apply_motion(
|
|
||||||
fighters: Query<(
|
fighters: Query<(
|
||||||
&FighterVelocity,
|
&FighterVelocity,
|
||||||
&mut FighterTranslation,
|
&mut FighterTranslation,
|
||||||
&mut FighterPreviousTranslation,
|
&mut FighterPreviousTranslation,
|
||||||
|
&mut Transform,
|
||||||
)>,
|
)>,
|
||||||
_time: Res<Time<Fixed>>,
|
_time: Res<Time<Fixed>>,
|
||||||
) {
|
) {
|
||||||
for (velocity, mut translation, mut prev_translation) in fighters {
|
for (velocity, mut translation, mut prev_translation, mut transform) in fighters {
|
||||||
prev_translation.0 = translation.0;
|
prev_translation.0 = translation.0;
|
||||||
translation.0 += velocity.0;
|
translation.0 += velocity.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy_fighter_transform_to_visuals(
|
||||||
|
fighters: Query<(&FighterTranslation, &mut Transform)>,
|
||||||
|
) {
|
||||||
|
for (translation, mut transform) in fighters {
|
||||||
|
transform.translation = Vec3::new(translation.x, translation.y, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fighter_movement() {}
|
pub fn fighter_movement() {}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::fighter::{*};
|
||||||
|
|
||||||
|
pub fn integrate_gravity(velocity: &mut FighterVelocity, attributes: &FighterAttributes) {
|
||||||
|
velocity.y -= attributes.gravity;
|
||||||
|
velocity.y = velocity.y.max(-attributes.terminal_velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_air_motion(
|
||||||
|
velocity: &FighterVelocity,
|
||||||
|
translation: &mut FighterTranslation,
|
||||||
|
prev_translation: &mut FighterPreviousTranslation,
|
||||||
|
) {
|
||||||
|
// Vertical velocity should be 0 on the ground
|
||||||
|
prev_translation.0 = translation.0;
|
||||||
|
translation.0 += velocity.0;
|
||||||
|
}
|
||||||
+64
-66
@@ -2,58 +2,100 @@ use std::hash::{Hash, Hasher};
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
use crate::fighter::state::ground::GroundedStateCommon;
|
||||||
|
use crate::fighter::state::wait::WaitState;
|
||||||
|
use crate::fighter::state::{FighterState};
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity, collision};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::{
|
use crate::input::{
|
||||||
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
||||||
FighterInput,
|
FighterInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct DashStateCreator {}
|
#[derive(Debug, Clone)]
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone, Copy)]
|
|
||||||
pub struct DashState {
|
pub struct DashState {
|
||||||
frames_in_dash: u32,
|
frames_in_dash: u32,
|
||||||
direction: f32,
|
direction: f32,
|
||||||
|
ground_common: GroundedStateCommon
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_dash_state(dash_state: &DashState) -> u64 {
|
impl std::hash::Hash for DashState {
|
||||||
let mut hasher = bevy_ggrs::checksum_hasher();
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
dash_state.direction.to_bits().hash(&mut hasher);
|
self.direction.to_bits().hash(state);
|
||||||
dash_state.frames_in_dash.hash(&mut hasher);
|
self.frames_in_dash.hash(state);
|
||||||
hasher.finish()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DashState {
|
impl DashState {
|
||||||
pub fn new(direction: f32) -> Self {
|
pub fn new(direction: f32, ground_common: GroundedStateCommon) -> Self {
|
||||||
Self {
|
Self {
|
||||||
frames_in_dash: 0,
|
frames_in_dash: 0,
|
||||||
direction,
|
direction,
|
||||||
|
ground_common
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterState for DashState {
|
impl FighterStateImpl for DashState {
|
||||||
|
const NAME: &'static str = "Dash";
|
||||||
fn check_interrupt(
|
fn check_interrupt(
|
||||||
&self,
|
&self,
|
||||||
state_context: &FighterStateContext,
|
state_context: &FighterStateContext,
|
||||||
) -> Option<FighterStateTransition> {
|
) -> Option<FighterState> {
|
||||||
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
||||||
// Dash finish shenanigans
|
// Dash finish shenanigans
|
||||||
run::check_input(state_context)
|
run::check_input(state_context, &self.ground_common)
|
||||||
.or_else(|| walk::check_input(state_context))
|
.or_else(|| walk::check_input(state_context, &self.ground_common))
|
||||||
.or_else(|| wait::check_input(state_context))
|
.or_else(|| wait::check_input(state_context, &self.ground_common))
|
||||||
} else {
|
} else {
|
||||||
check_smash_input_with_dir(state_context)
|
check_smash_input_with_dir(state_context)
|
||||||
.filter(|dir| *dir != self.direction)
|
.filter(|dir| *dir != self.direction)
|
||||||
.and_then(|dir| Some(FighterStateTransition::Dash(dir)))
|
.and_then(|dir| Some(FighterState::Dash(DashState::new(dir, self.ground_common.clone()))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(state_context: &mut FighterStateContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.input.clear_buffer();
|
state_context.input.clear_buffer();
|
||||||
|
state_context.velocity.x = state_context.fighter_attribs.dash_initial_velocity * self.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
|
self.frames_in_dash += 1;
|
||||||
|
|
||||||
|
let last_frame = state_context.input.get_last_frame();
|
||||||
|
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_dashrun(&last_frame);
|
||||||
|
let accel = ground::compute_ground_accel(
|
||||||
|
accel,
|
||||||
|
target_vel,
|
||||||
|
state_context.velocity.x,
|
||||||
|
state_context.fighter_attribs,
|
||||||
|
state_context.game_settings,
|
||||||
|
);
|
||||||
|
state_context.velocity.x += accel;
|
||||||
|
|
||||||
|
ground::apply_grounded_motion(
|
||||||
|
state_context.velocity,
|
||||||
|
state_context.translation,
|
||||||
|
state_context.prev_translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
|
// Adjust translation
|
||||||
|
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
|
let grounded_common = GroundedStateCommon {
|
||||||
|
current_line_id: res.line_id
|
||||||
|
};
|
||||||
|
Some(FighterState::Wait(WaitState { grounded_common } ))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||||
@@ -66,51 +108,7 @@ fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
pub fn check_input(state_context: &FighterStateContext, ground_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||||
check_smash_input_with_dir(state_context).map(|dir| FighterStateTransition::Dash(dir))
|
check_smash_input_with_dir(state_context).map(|dir| FighterState::Dash(DashState::new(dir, ground_common.clone())));
|
||||||
}
|
None
|
||||||
|
|
||||||
pub fn dash_update(
|
|
||||||
dash_query: Query<(
|
|
||||||
&mut DashState,
|
|
||||||
&FighterAttributes,
|
|
||||||
&FighterInput,
|
|
||||||
&mut FighterVelocity,
|
|
||||||
)>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for (mut dash, attributes, input, mut velocity) in dash_query {
|
|
||||||
dash.frames_in_dash += 1;
|
|
||||||
|
|
||||||
let last_frame = input.get_last_frame();
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
|
||||||
let accel = ground::compute_ground_accel(
|
|
||||||
accel,
|
|
||||||
target_vel,
|
|
||||||
velocity.x,
|
|
||||||
attributes,
|
|
||||||
game_settings.as_ref(),
|
|
||||||
);
|
|
||||||
velocity.x += accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_insert_dash(
|
|
||||||
add: On<Insert, DashState>,
|
|
||||||
query: Query<
|
|
||||||
(
|
|
||||||
Entity,
|
|
||||||
&mut FighterInput,
|
|
||||||
&FighterAttributes,
|
|
||||||
&mut FighterVelocity,
|
|
||||||
),
|
|
||||||
With<DashState>,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
for (entity, mut input, attribs, mut velocity) in query {
|
|
||||||
if entity == add.entity {
|
|
||||||
velocity.x = attribs.dash_initial_velocity * input.get_last_frame().movement.x.signum();
|
|
||||||
input.clear_buffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||||
|
use crate::fighter::state::{FighterState, air};
|
||||||
|
use crate::fighter::state::ground::GroundedStateCommon;
|
||||||
|
use crate::fighter::state::wait::WaitState;
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity, collision, motion};
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::stage::line::StageCollision;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash)]
|
||||||
|
pub struct FallState;
|
||||||
|
|
||||||
|
impl FighterStateImpl for FallState {
|
||||||
|
const NAME: &'static str = "Fall";
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||||
|
|
||||||
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
|
air::integrate_gravity(state_context.velocity, state_context.fighter_attribs);
|
||||||
|
air::apply_air_motion(state_context.velocity, state_context.translation, state_context.prev_translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
|
// Adjust translation
|
||||||
|
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
|
let grounded_common = GroundedStateCommon {
|
||||||
|
current_line_id: res.line_id
|
||||||
|
};
|
||||||
|
Some(FighterState::Wait(WaitState { grounded_common } ))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||||
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
|
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||||
|
return Some(FighterState::Fall(FallState));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,8 +2,20 @@
|
|||||||
//! ECS types so it stays cheap to reason about and to unit-test — it has to be
|
//! ECS types so it stays cheap to reason about and to unit-test — it has to be
|
||||||
//! bit-for-bit deterministic for rollback.
|
//! bit-for-bit deterministic for rollback.
|
||||||
|
|
||||||
use crate::fighter::FighterAttributes;
|
use crate::fighter::state::FighterStateContext;
|
||||||
|
use crate::fighter::{
|
||||||
|
FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||||
|
};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::stage::StagePoly;
|
||||||
|
use crate::stage::line::{StageCollision, StageLineID, StagePolyLineSegment, StagePolyLineSegmentType};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Reflect, Hash, Clone, Debug)]
|
||||||
|
pub struct GroundedStateCommon {
|
||||||
|
pub current_line_id: StageLineID,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||||
if friction.abs() > ground_vel.abs() {
|
if friction.abs() > ground_vel.abs() {
|
||||||
@@ -53,3 +65,79 @@ pub fn compute_ground_accel(
|
|||||||
accel
|
accel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum GroundedMotionResult {
|
||||||
|
InGround,
|
||||||
|
InAir,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_grounded_motion(
|
||||||
|
velocity: &FighterVelocity,
|
||||||
|
translation: &mut FighterTranslation,
|
||||||
|
prev_translation: &mut FighterPreviousTranslation,
|
||||||
|
) {
|
||||||
|
// Vertical velocity should be 0 on the ground
|
||||||
|
prev_translation.0 = translation.0;
|
||||||
|
translation.0 += Vec2::new(velocity.0.x ,0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collide_with_stage_grounded(
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
ground_common: &mut GroundedStateCommon,
|
||||||
|
can_walk_off: bool
|
||||||
|
) -> GroundedMotionResult {
|
||||||
|
let translation = &mut state_context.translation;
|
||||||
|
let ecb = &state_context.ecb;
|
||||||
|
let stage_collision = &state_context.stage_collision;
|
||||||
|
|
||||||
|
let stage_poly = stage_collision
|
||||||
|
.stage_polys
|
||||||
|
.get(ground_common.current_line_id.polygon);
|
||||||
|
|
||||||
|
if let Some(stage_poly) = stage_poly {
|
||||||
|
let ecb_bottom_pos = translation.0 + ecb.get_bottom_point();
|
||||||
|
let left_segment_idx = ground_common
|
||||||
|
.current_line_id
|
||||||
|
.segment
|
||||||
|
.checked_sub(1)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let right_segment_idx =
|
||||||
|
(ground_common.current_line_id.segment + 1) % stage_poly.segments.len();
|
||||||
|
|
||||||
|
// First, we check the current segment, if that fails, we check the surrounding ones
|
||||||
|
let grounded_ecb_bottom_pos = [
|
||||||
|
ground_common.current_line_id.segment.clone(),
|
||||||
|
left_segment_idx,
|
||||||
|
right_segment_idx,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.find_map(|segment_idx| {
|
||||||
|
let segment = stage_poly.get_segment(*segment_idx)?;
|
||||||
|
if segment.segment_type != StagePolyLineSegmentType::Floor {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line_dir = segment.segment.direction();
|
||||||
|
let projected_point = segment.segment.point1() + (ecb_bottom_pos - segment.segment.point1()).project_onto(line_dir.as_vec2());
|
||||||
|
let closest_point = segment.segment.closest_point(ecb_bottom_pos);
|
||||||
|
let distance = closest_point.distance(projected_point);
|
||||||
|
|
||||||
|
if distance < 0.01 {
|
||||||
|
return Some((projected_point, *segment_idx, segment.normal));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((bp, new_segment_idx, normal)) = grounded_ecb_bottom_pos {
|
||||||
|
translation.0 = bp - ecb.get_bottom_point();
|
||||||
|
state_context.velocity.0 = state_context.velocity.0 - state_context.velocity.0.project_onto_normalized(normal);
|
||||||
|
ground_common.current_line_id.segment = new_segment_idx;
|
||||||
|
GroundedMotionResult::InGround
|
||||||
|
} else {
|
||||||
|
GroundedMotionResult::InAir
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GroundedMotionResult::InAir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::fighter::state::{FighterState, FighterStateImpl};
|
||||||
|
|
||||||
|
pub struct JumpState {
|
||||||
|
pub counter: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JumpSquatState {
|
||||||
|
pub counter: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl FighterStateImpl for JumpSquatState {
|
||||||
|
const NAME: &'static str = "JumpSquat";
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &super::FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterStateImpl for JumpState {
|
||||||
|
const NAME: &'static str = "Jump";
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &super::FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _state_context: &mut super::FighterStateContext) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
-43
@@ -2,65 +2,105 @@
|
|||||||
//! implements, the context they inspect, and the generic system that runs the
|
//! implements, the context they inspect, and the generic system that runs the
|
||||||
//! interrupt check. One state per submodule.
|
//! interrupt check. One state per submodule.
|
||||||
|
|
||||||
|
use bevy::ecs::component::Mutable;
|
||||||
|
use bevy::ecs::system::ScheduleSystem;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::{GgrsSchedule, RollbackApp};
|
||||||
|
|
||||||
use crate::fighter::FighterAttributes;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
|
use crate::fighter::{FighterAttributes, FighterECB, FighterPreviousTranslation, FighterTranslation, FighterVelocity};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
|
use crate::schedule::GameplaySet;
|
||||||
|
use crate::stage::StagePoly;
|
||||||
|
use crate::stage::line::StageCollision;
|
||||||
|
|
||||||
pub mod dash;
|
pub mod dash;
|
||||||
pub mod ground;
|
pub mod ground;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod wait;
|
pub mod wait;
|
||||||
pub mod walk;
|
pub mod walk;
|
||||||
|
pub mod fall;
|
||||||
|
pub mod air;
|
||||||
|
pub mod jump;
|
||||||
|
|
||||||
pub enum FighterStateTransition {
|
pub enum FighterStateTransition {
|
||||||
Wait,
|
Wait,
|
||||||
Walk,
|
Walk,
|
||||||
Dash(f32),
|
Dash(f32),
|
||||||
Run,
|
Run,
|
||||||
|
Fall,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FighterState: Component + Sized {
|
macro_rules! fighter_states {
|
||||||
fn remove(&self, state_context: &FighterStateContext, commands: &mut Commands) {
|
($($variant:ident => $ty:ty),* $(,)?) => {
|
||||||
commands.entity(state_context.entity).remove::<Self>();
|
#[derive(Component, Clone, Debug, Hash)]
|
||||||
|
pub enum FighterState { $($variant($ty)),* }
|
||||||
|
|
||||||
|
impl FighterState {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self { $(Self::$variant(_) => <$ty>::NAME),* }
|
||||||
|
}
|
||||||
|
pub fn check_interrupt(&self, ctx: &FighterStateContext) -> Option<FighterState> {
|
||||||
|
match self { $(Self::$variant(s) => s.check_interrupt(ctx)),* }
|
||||||
|
}
|
||||||
|
pub fn update(&mut self, ctx: &mut FighterStateContext) {
|
||||||
|
match self { $(Self::$variant(s) => s.update(ctx)),* }
|
||||||
|
}
|
||||||
|
pub fn on_enter(&mut self, ctx: &mut FighterStateContext) {
|
||||||
|
match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
|
||||||
}
|
}
|
||||||
fn check_interrupt(
|
|
||||||
&self,
|
|
||||||
state_context: &FighterStateContext,
|
|
||||||
) -> Option<FighterStateTransition>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FighterStateContext<'a> {
|
pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
pub input: &'a mut FighterInput,
|
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
|
||||||
pub fighter_attribs: &'a FighterAttributes,
|
}
|
||||||
pub game_settings: &'a GameSettings,
|
}
|
||||||
pub entity: Entity,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_state(
|
$(impl From<$ty> for FighterState {
|
||||||
transition: FighterStateTransition,
|
fn from(s: $ty) -> Self { Self::$variant(s) }
|
||||||
state_context: FighterStateContext,
|
})*
|
||||||
commands: &mut Commands,
|
|
||||||
) {
|
|
||||||
let mut ent_cmd = commands.entity(state_context.entity);
|
|
||||||
match transition {
|
|
||||||
FighterStateTransition::Wait => ent_cmd.insert(wait::WaitState),
|
|
||||||
FighterStateTransition::Walk => ent_cmd.insert(walk::WalkState),
|
|
||||||
FighterStateTransition::Dash(dir) => ent_cmd.insert(dash::DashState::new(dir)),
|
|
||||||
FighterStateTransition::Run => ent_cmd.insert(run::RunState),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state_debug_name(transition: &FighterStateTransition) -> &'static str {
|
pub trait FighterStateImpl: Sized {
|
||||||
match transition {
|
const NAME: &'static str;
|
||||||
FighterStateTransition::Wait => "wait",
|
fn check_interrupt(
|
||||||
FighterStateTransition::Walk => "walk",
|
&self,
|
||||||
FighterStateTransition::Dash(_) => "dash",
|
state_context: &FighterStateContext,
|
||||||
FighterStateTransition::Run => "run",
|
) -> Option<FighterState>;
|
||||||
|
|
||||||
|
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||||
|
fn update(&mut self, _state_context: &mut FighterStateContext);
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
_state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FighterStateContext<'a> {
|
||||||
|
pub translation: &'a mut FighterTranslation,
|
||||||
|
pub prev_translation: &'a mut FighterPreviousTranslation,
|
||||||
|
pub input: &'a mut FighterInput,
|
||||||
|
pub velocity: &'a mut FighterVelocity,
|
||||||
|
pub ecb: &'a mut FighterECB,
|
||||||
|
pub prev_ecb: &'a mut FighterPreviousECB,
|
||||||
|
pub fighter_attribs: &'a FighterAttributes,
|
||||||
|
pub game_settings: &'a GameSettings,
|
||||||
|
pub entity: Entity,
|
||||||
|
pub stage_collision: &'a StageCollision,
|
||||||
|
}
|
||||||
|
|
||||||
|
fighter_states! {
|
||||||
|
Wait => wait::WaitState,
|
||||||
|
Walk => walk::WalkState,
|
||||||
|
Dash => dash::DashState,
|
||||||
|
Run => run::RunState,
|
||||||
|
Fall => fall::FallState
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct StateNameDebug(pub &'static str);
|
pub struct StateNameDebug(pub &'static str);
|
||||||
|
|
||||||
@@ -70,25 +110,91 @@ impl std::fmt::Display for StateNameDebug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
pub fn state_update_system(
|
||||||
query: Query<(Entity, &T, &mut FighterInput, &FighterAttributes)>,
|
query: Query<(Entity, &mut FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||||
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
mut commands: Commands,
|
mut gizmos: Gizmos,
|
||||||
) {
|
) {
|
||||||
for (ent, state, mut input, attribs) in query {
|
for (ent, mut state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||||
let state_context = FighterStateContext {
|
let mut update_ctx = FighterStateContext {
|
||||||
|
translation: &mut translation,
|
||||||
|
prev_translation: &mut prev_translation,
|
||||||
input: &mut input,
|
input: &mut input,
|
||||||
|
velocity: &mut velocity,
|
||||||
|
ecb: &mut ecb,
|
||||||
|
prev_ecb: &mut prev_ecb,
|
||||||
fighter_attribs: &attribs,
|
fighter_attribs: &attribs,
|
||||||
game_settings: &game_settings,
|
game_settings: &game_settings,
|
||||||
entity: ent,
|
entity: ent,
|
||||||
|
stage_collision: &stage_collision,
|
||||||
|
};
|
||||||
|
state.update(&mut update_ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_interrupt_system (
|
||||||
|
query: Query<(Entity, &FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||||
|
stage_collision: Res<StageCollision>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (ent, state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||||
|
let mut state_context = FighterStateContext {
|
||||||
|
translation: &mut translation,
|
||||||
|
prev_translation: &mut prev_translation,
|
||||||
|
input: &mut input,
|
||||||
|
velocity: &mut velocity,
|
||||||
|
ecb: &mut ecb,
|
||||||
|
prev_ecb: &mut prev_ecb,
|
||||||
|
fighter_attribs: &attribs,
|
||||||
|
game_settings: &game_settings,
|
||||||
|
entity: ent,
|
||||||
|
stage_collision: &stage_collision
|
||||||
};
|
};
|
||||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||||
commands.entity(ent).remove::<T>();
|
let mut new_state = new_state;
|
||||||
info!("State {:?} -> {}", state, get_state_debug_name(&new_state));
|
let old_state_name = state.name();
|
||||||
commands
|
let new_state_name = new_state.name();
|
||||||
.entity(ent)
|
new_state.on_enter(&mut state_context);
|
||||||
.insert(StateNameDebug(get_state_debug_name(&new_state)));
|
commands.entity(ent).insert((
|
||||||
create_state(new_state, state_context, &mut commands);
|
new_state,
|
||||||
|
StateNameDebug(new_state_name)
|
||||||
|
));
|
||||||
|
info!("State {} -> {}", old_state_name, new_state_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state_collision_interrupt_system(
|
||||||
|
query: Query<(Entity, &mut FighterState, &mut FighterTranslation, &mut FighterPreviousTranslation, &mut FighterInput, &mut FighterVelocity, &mut FighterECB, &mut FighterPreviousECB, &FighterAttributes)>,
|
||||||
|
stage_collision: Res<StageCollision>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (ent, mut state, mut translation, mut prev_translation, mut input, mut velocity, mut ecb, mut prev_ecb, attribs) in query {
|
||||||
|
let mut state_context = FighterStateContext {
|
||||||
|
translation: &mut translation,
|
||||||
|
prev_translation: &mut prev_translation,
|
||||||
|
input: &mut input,
|
||||||
|
velocity: &mut velocity,
|
||||||
|
ecb: &mut ecb,
|
||||||
|
prev_ecb: &mut prev_ecb,
|
||||||
|
fighter_attribs: &attribs,
|
||||||
|
game_settings: &game_settings,
|
||||||
|
entity: ent,
|
||||||
|
stage_collision: &stage_collision
|
||||||
|
};
|
||||||
|
if let Some(new_state) = state.check_collision_interrupt(&mut state_context) {
|
||||||
|
let mut new_state = new_state;
|
||||||
|
let old_state_name = state.name();
|
||||||
|
let new_state_name = new_state.name();
|
||||||
|
new_state.on_enter(&mut state_context);
|
||||||
|
commands.entity(ent).insert((
|
||||||
|
new_state,
|
||||||
|
StateNameDebug(new_state_name)
|
||||||
|
));
|
||||||
|
info!("State {} -> {}", old_state_name, new_state_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+52
-27
@@ -1,49 +1,74 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||||
|
use crate::fighter::state::FighterState;
|
||||||
|
use crate::fighter::state::fall::FallState;
|
||||||
|
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone, Copy)]
|
#[derive(Component, Debug, Clone, Hash)]
|
||||||
pub struct RunState;
|
pub struct RunState {
|
||||||
|
pub grounded_common: GroundedStateCommon
|
||||||
|
}
|
||||||
|
|
||||||
impl FighterState for RunState {
|
impl FighterStateImpl for RunState {
|
||||||
|
const NAME: &'static str = "Run";
|
||||||
fn check_interrupt(
|
fn check_interrupt(
|
||||||
&self,
|
&self,
|
||||||
state_context: &FighterStateContext,
|
state_context: &FighterStateContext,
|
||||||
) -> Option<FighterStateTransition> {
|
) -> Option<FighterState> {
|
||||||
if let Some(_) = check_input(state_context) {
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
walk::check_input(state_context).or_else(|| wait::check_input(state_context))
|
walk::check_input(state_context, &self.grounded_common).or_else(|| wait::check_input(state_context, &self.grounded_common))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||||
let input_frame = state_context.input.get_last_frame();
|
|
||||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold
|
|
||||||
{
|
|
||||||
return Some(FighterStateTransition::Run);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_update(
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<RunState>>,
|
let last_frame = state_context.input.get_last_frame();
|
||||||
game_settings: Res<GameSettings>,
|
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_dashrun(&last_frame);
|
||||||
) {
|
|
||||||
for (mut velocity, attributes, input) in fighters {
|
|
||||||
let last_frame = input.get_last_frame();
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
|
||||||
let accel = ground::compute_ground_accel(
|
let accel = ground::compute_ground_accel(
|
||||||
accel,
|
accel,
|
||||||
target_vel,
|
target_vel,
|
||||||
velocity.x,
|
state_context.velocity.x,
|
||||||
attributes,
|
state_context.fighter_attribs,
|
||||||
game_settings.as_ref(),
|
state_context.game_settings,
|
||||||
);
|
);
|
||||||
velocity.x += accel;
|
state_context.velocity.x += accel;
|
||||||
|
|
||||||
|
ground::apply_grounded_motion(
|
||||||
|
state_context.velocity,
|
||||||
|
state_context.translation,
|
||||||
|
state_context.prev_translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||||
|
state_context,
|
||||||
|
&mut self.grounded_common,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
Some(FighterState::Fall(FallState))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||||
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold
|
||||||
|
{
|
||||||
|
return Some(FighterState::Run(RunState { grounded_common: ground_state_common.clone() }));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
+50
-24
@@ -1,43 +1,69 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, walk};
|
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, ground, walk};
|
||||||
|
use crate::fighter::state::FighterState;
|
||||||
|
use crate::fighter::state::fall::FallState;
|
||||||
|
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::stage::line::StageLineID;
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct WaitState;
|
pub struct WaitState {
|
||||||
|
pub grounded_common: GroundedStateCommon
|
||||||
|
}
|
||||||
|
|
||||||
impl FighterState for WaitState {
|
impl FighterStateImpl for WaitState {
|
||||||
|
const NAME: &'static str = "Wait";
|
||||||
fn check_interrupt(
|
fn check_interrupt(
|
||||||
&self,
|
&self,
|
||||||
state_context: &FighterStateContext,
|
state_context: &FighterStateContext,
|
||||||
) -> Option<FighterStateTransition> {
|
) -> Option<FighterState> {
|
||||||
walk::check_input(state_context)
|
walk::check_input(state_context, &self.grounded_common)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self, _state_context: &mut super::FighterStateContext) {}
|
||||||
|
|
||||||
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
|
let friction = if state_context.velocity.x.abs() > state_context.fighter_attribs.max_walk_vel {
|
||||||
|
state_context.fighter_attribs.ground_friction
|
||||||
|
* state_context.game_settings
|
||||||
|
.figher_common
|
||||||
|
.ground_friction_over_walk_speed_multiplier
|
||||||
|
} else {
|
||||||
|
state_context.fighter_attribs.ground_friction
|
||||||
|
};
|
||||||
|
state_context.velocity.x += ground::apply_grounded_friction(friction, state_context.velocity.x);
|
||||||
|
|
||||||
|
ground::apply_grounded_motion(
|
||||||
|
state_context.velocity,
|
||||||
|
state_context.translation,
|
||||||
|
state_context.prev_translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||||
|
state_context,
|
||||||
|
&mut self.grounded_common,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
Some(FighterState::Fall(FallState))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||||
return Some(FighterStateTransition::Wait);
|
return Some(FighterState::Wait(WaitState { grounded_common: ground_state_common.clone() }));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_update(
|
|
||||||
query: Query<(&FighterAttributes, &mut FighterVelocity), With<WaitState>>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for (attribs, mut velocity) in query {
|
|
||||||
let friction = if velocity.x.abs() > attribs.max_walk_vel {
|
|
||||||
attribs.ground_friction
|
|
||||||
* game_settings
|
|
||||||
.figher_common
|
|
||||||
.ground_friction_over_walk_speed_multiplier
|
|
||||||
} else {
|
|
||||||
attribs.ground_friction
|
|
||||||
};
|
|
||||||
velocity.x += ground::apply_grounded_friction(friction, velocity.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+47
-23
@@ -1,43 +1,67 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{FighterState, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
use super::{FighterStateImpl, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||||
|
use crate::fighter::state::fall::FallState;
|
||||||
|
use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon};
|
||||||
|
use crate::fighter::state::{self, FighterState};
|
||||||
use crate::fighter::{FighterAttributes, FighterVelocity};
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct WalkState;
|
pub struct WalkState {
|
||||||
|
pub grounded_common: GroundedStateCommon
|
||||||
|
}
|
||||||
|
|
||||||
impl FighterState for WalkState {
|
impl FighterStateImpl for WalkState {
|
||||||
|
const NAME: &'static str = "Walk";
|
||||||
fn check_interrupt(
|
fn check_interrupt(
|
||||||
&self,
|
&self,
|
||||||
state_context: &FighterStateContext,
|
state_context: &FighterStateContext,
|
||||||
) -> Option<FighterStateTransition> {
|
) -> Option<FighterState> {
|
||||||
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
wait::check_input(state_context, &self.grounded_common).or_else(|| dash::check_input(state_context, &self.grounded_common))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||||
|
|
||||||
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
|
let last_frame = state_context.input.get_last_frame();
|
||||||
|
let (accel, target_vel) = state_context.fighter_attribs.get_accel_and_target_walk(&last_frame);
|
||||||
|
|
||||||
|
let accel =
|
||||||
|
ground::compute_ground_accel(accel, target_vel, state_context.velocity.x, state_context.fighter_attribs, &state_context.game_settings);
|
||||||
|
|
||||||
|
state_context.velocity.x += accel;
|
||||||
|
|
||||||
|
ground::apply_grounded_motion(
|
||||||
|
state_context.velocity,
|
||||||
|
state_context.translation,
|
||||||
|
state_context.prev_translation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_collision_interrupt(
|
||||||
|
&mut self,
|
||||||
|
state_context: &mut FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
|
if let GroundedMotionResult::InAir = ground::collide_with_stage_grounded(
|
||||||
|
state_context,
|
||||||
|
&mut self.grounded_common,
|
||||||
|
false
|
||||||
|
) {
|
||||||
|
Some(FighterState::Fall(FallState))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
pub fn check_input(state_context: &FighterStateContext, ground_state_common: &GroundedStateCommon) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
||||||
return Some(FighterStateTransition::Walk);
|
return Some(FighterState::Walk(WalkState { grounded_common: ground_state_common.clone() }));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn walk_update(
|
|
||||||
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<WalkState>>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for (mut velocity, attributes, input) in fighters {
|
|
||||||
let last_frame = input.get_last_frame();
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_walk(&last_frame);
|
|
||||||
|
|
||||||
let accel =
|
|
||||||
ground::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings);
|
|
||||||
|
|
||||||
velocity.x += accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+35
-20
@@ -21,30 +21,26 @@
|
|||||||
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
//! save, click Rebuild in jackdaw (or run `jd build`) and it
|
||||||
//! appears in `Add Component`. No registration code is needed.
|
//! appears in `Add Component`. No registration code is needed.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::{mesh::SphereMeshBuilder, prelude::*};
|
||||||
|
use bevy_wind_waker_shader::prelude::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use jackdaw_runtime::prelude::*;
|
use jackdaw_runtime::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
FighterAttributes, FighterECB, FighterTranslation, FighterVelocity,
|
FighterAttributes, FighterECB, FighterTranslation, FighterVelocity, FighterVisual, animation::FighterAnimations, state::{FighterState, fall::FallState, wait::WaitState},
|
||||||
state::wait::WaitState,
|
}, game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon}, input::FighterInput, player::Player, schedule::GameplaySchedulePlugin,
|
||||||
},
|
|
||||||
game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon},
|
|
||||||
input::FighterInput,
|
|
||||||
player::Player,
|
|
||||||
schedule::GameplaySchedulePlugin,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod args;
|
||||||
pub mod fighter;
|
pub mod fighter;
|
||||||
pub mod input;
|
|
||||||
pub mod stage;
|
|
||||||
pub mod game_settings;
|
pub mod game_settings;
|
||||||
|
pub mod input;
|
||||||
|
mod math;
|
||||||
pub mod netcode;
|
pub mod netcode;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
mod math;
|
pub mod stage;
|
||||||
mod args;
|
|
||||||
|
|
||||||
/// Your game's Bevy plugin. The editor finds it by this name (override
|
/// Your game's Bevy plugin. The editor finds it by this name (override
|
||||||
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
||||||
@@ -52,13 +48,19 @@ mod args;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GamePlugin;
|
pub struct GamePlugin;
|
||||||
|
|
||||||
pub fn setup(mut commands: Commands) {
|
pub fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
let _fighter = commands
|
// debug stage
|
||||||
|
|
||||||
|
let _stage = commands.spawn((
|
||||||
|
WorldAssetRoot(asset_server.load("stages/YoshiStory.glb#Scene0")),
|
||||||
|
Transform::from_scale(Vec3::splat(0.1)),
|
||||||
|
));
|
||||||
|
|
||||||
|
let _fighter =
|
||||||
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Name("Fighter".into()),
|
Name("Fighter".into()),
|
||||||
Player {
|
Player { handle: 0 },
|
||||||
handle: 0,
|
|
||||||
},
|
|
||||||
FighterECB {
|
FighterECB {
|
||||||
vertical_half: 0.5,
|
vertical_half: 0.5,
|
||||||
horizontal_half: 0.25,
|
horizontal_half: 0.25,
|
||||||
@@ -74,10 +76,22 @@ pub fn setup(mut commands: Commands) {
|
|||||||
base_dash_accel: 0.002,
|
base_dash_accel: 0.002,
|
||||||
stick_dash_accel: 0.01,
|
stick_dash_accel: 0.01,
|
||||||
max_dash_vel: 0.22,
|
max_dash_vel: 0.22,
|
||||||
|
jump_vertical_velocity: 0.25,
|
||||||
dash_initial_velocity: 0.19,
|
dash_initial_velocity: 0.19,
|
||||||
|
terminal_velocity: 0.28,
|
||||||
|
gravity: 0.023,
|
||||||
},
|
},
|
||||||
WaitState {},
|
FighterState::Fall(FallState),
|
||||||
FighterInput::default()
|
FighterInput::default(),
|
||||||
|
FighterAnimations {
|
||||||
|
wait: asset_server.load("fighters/jiggs/jigglypuff-normal.glb#PlyPurin5K_Share_ACTION_Wait")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.with_child((
|
||||||
|
FighterVisual,
|
||||||
|
WorldAssetRoot(asset_server.load(
|
||||||
|
GltfAssetLabel::Scene(0).from_asset("fighters/jiggs/jigglypuff-normal.glb"),
|
||||||
|
)),
|
||||||
))
|
))
|
||||||
.id();
|
.id();
|
||||||
}
|
}
|
||||||
@@ -88,6 +102,7 @@ impl Plugin for GamePlugin {
|
|||||||
// resources; this plugin only wires them together and supplies the
|
// resources; this plugin only wires them together and supplies the
|
||||||
// game-level content and configuration.
|
// game-level content and configuration.
|
||||||
app.add_plugins((
|
app.add_plugins((
|
||||||
|
WindWakerShaderPlugin::default(),
|
||||||
// Brings up GGRS (and with it `GgrsSchedule`), so it goes first.
|
// Brings up GGRS (and with it `GgrsSchedule`), so it goes first.
|
||||||
netcode::FighterNetcodePlugin::default(),
|
netcode::FighterNetcodePlugin::default(),
|
||||||
GameplaySchedulePlugin,
|
GameplaySchedulePlugin,
|
||||||
@@ -102,7 +117,7 @@ impl Plugin for GamePlugin {
|
|||||||
figher_common: FighterSettingsCommon {
|
figher_common: FighterSettingsCommon {
|
||||||
walk_speed_ease: 0.5,
|
walk_speed_ease: 0.5,
|
||||||
ground_max_horizontal_velocity: 0.3,
|
ground_max_horizontal_velocity: 0.3,
|
||||||
ground_friction_over_walk_speed_multiplier: 2.0
|
ground_friction_over_walk_speed_multiplier: 2.0,
|
||||||
},
|
},
|
||||||
input_common: InputSettingsCommon::default(),
|
input_common: InputSettingsCommon::default(),
|
||||||
});
|
});
|
||||||
|
|||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
use bevy::math::{Vec2, primitives::Segment2d};
|
|
||||||
|
|
||||||
pub trait SegmentIntersection {
|
|
||||||
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SegmentIntersection for Segment2d {
|
|
||||||
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
|
||||||
let p0 = self.point1();
|
|
||||||
let p1 = self.point2();
|
|
||||||
let p2 = rhs.point1();
|
|
||||||
let p3 = rhs.point2();
|
|
||||||
|
|
||||||
let s1_x = p1.x - p0.x;
|
|
||||||
let s1_y = p1.y - p0.y;
|
|
||||||
let s2_x = p3.x - p2.x;
|
|
||||||
let s2_y = p3.y - p2.y;
|
|
||||||
|
|
||||||
let denom = -s2_x * s1_y + s1_x * s2_y;
|
|
||||||
if denom.abs() < f32::EPSILON {
|
|
||||||
return None; // Parallel or collinear
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
|
||||||
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
|
||||||
|
|
||||||
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
|
||||||
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
use bevy::math::{Vec2, primitives::Segment2d};
|
||||||
|
|
||||||
|
use fixed::prelude::*;
|
||||||
|
|
||||||
|
pub type FGi32 = fixed::FixedI32<fixed::types::extra::U16>;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod int;
|
||||||
|
pub mod segment;
|
||||||
|
pub mod vec;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
use bevy::math::{Vec2, primitives::Segment2d};
|
||||||
|
|
||||||
|
use fixed::prelude::*;
|
||||||
|
|
||||||
|
use crate::math::vec::FGVec2;
|
||||||
|
|
||||||
|
type FGi32 = fixed::FixedI32<fixed::types::extra::U16>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct FGSegment2d {
|
||||||
|
point1: FGVec2,
|
||||||
|
point2: FGVec2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FGSegment2d {
|
||||||
|
#[inline]
|
||||||
|
pub fn new(point1: FGVec2, point2: FGVec2) -> Self {
|
||||||
|
Self {
|
||||||
|
point1, point2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn point1(&self) -> FGVec2 {
|
||||||
|
self.point1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn point2(&self) -> FGVec2 {
|
||||||
|
self.point2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SegmentIntersection {
|
||||||
|
fn segment_intersection(&self, rhs: &Segment2d) -> Option<FGVec2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentIntersection for Segment2d {
|
||||||
|
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
||||||
|
let p0 = self.point1();
|
||||||
|
let p1 = self.point2();
|
||||||
|
let p2 = rhs.point1();
|
||||||
|
let p3 = rhs.point2();
|
||||||
|
|
||||||
|
let s1_x = p1.x - p0.x;
|
||||||
|
let s1_y = p1.y - p0.y;
|
||||||
|
let s2_x = p3.x - p2.x;
|
||||||
|
let s2_y = p3.y - p2.y;
|
||||||
|
|
||||||
|
let denom = -s2_x * s1_y + s1_x * s2_y;
|
||||||
|
if denom.abs() < f32::EPSILON {
|
||||||
|
return None; // Parallel or collinear
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
||||||
|
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
||||||
|
|
||||||
|
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
||||||
|
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentIntersection for Segment2d {
|
||||||
|
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
||||||
|
let p0 = self.point1();
|
||||||
|
let p1 = self.point2();
|
||||||
|
let p2 = rhs.point1();
|
||||||
|
let p3 = rhs.point2();
|
||||||
|
|
||||||
|
let s1_x = p1.x - p0.x;
|
||||||
|
let s1_y = p1.y - p0.y;
|
||||||
|
let s2_x = p3.x - p2.x;
|
||||||
|
let s2_y = p3.y - p2.y;
|
||||||
|
|
||||||
|
let denom = -s2_x * s1_y + s1_x * s2_y;
|
||||||
|
if denom.abs() < f32::EPSILON {
|
||||||
|
return None; // Parallel or collinear
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
||||||
|
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
||||||
|
|
||||||
|
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
||||||
|
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
use bevy::math::Vec2;
|
||||||
|
use std::ops::{Add, Div, Mul, Sub};
|
||||||
|
|
||||||
|
use super::int::FGi32;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub struct FGVec2 {
|
||||||
|
pub x: FGi32,
|
||||||
|
pub y: FGi32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FGVec2 {
|
||||||
|
pub const ZERO: Self = Self::new(FGi32::ZERO, FGi32::ZERO);
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn new(x: FGi32, y: FGi32) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_i32(x: i32, y: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
x: FGi32::from_num(x),
|
||||||
|
y: FGi32::from_num(y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn length_squared(&self) -> FGi32 {
|
||||||
|
self.x * self.x + self.y * self.y
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn length(&self) -> FGi32 {
|
||||||
|
(self.x * self.x + self.y * self.y).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn normalized_or_zero(&self) -> Self {
|
||||||
|
let len = self.length();
|
||||||
|
if len.is_zero() {
|
||||||
|
Self::ZERO
|
||||||
|
} else {
|
||||||
|
self / len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn to_vec2(&self) -> Vec2 {
|
||||||
|
Vec2::new(self.x.to_num(), self.y.to_num())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
Self::new(self.x * rhs.x, self.y * rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self::new(self.x + rhs.x, self.y + rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
Self::new(self.x - rhs.x, self.y - rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div(self, rhs: Self) -> Self::Output {
|
||||||
|
Self::new(self.x / rhs.x, self.y / rhs.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<FGi32> for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn mul(self, rhs: FGi32) -> Self::Output {
|
||||||
|
Self::new(self.x * rhs, self.y * rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<FGi32> for FGVec2 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div(self, rhs: FGi32) -> Self::Output {
|
||||||
|
Self::new(self.x / rhs, self.y / rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<FGi32> for &FGVec2 {
|
||||||
|
type Output = FGVec2;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div(self, rhs: FGi32) -> Self::Output {
|
||||||
|
(*self).div(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -8,7 +8,7 @@ use bevy_ggrs::prelude::*;
|
|||||||
/// root rather than in `netcode` because "which player is this" is a game
|
/// root rather than in `netcode` because "which player is this" is a game
|
||||||
/// concept; `netcode` only decides where the inputs come from.
|
/// concept; `netcode` only decides where the inputs come from.
|
||||||
#[derive(Default, Component)]
|
#[derive(Default, Component)]
|
||||||
#[require(Rollback)]
|
#[require(Rollback, Transform)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub handle: usize,
|
pub handle: usize,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub enum GameplaySet {
|
|||||||
Physics,
|
Physics,
|
||||||
/// Resolve the integrated positions against the stage.
|
/// Resolve the integrated positions against the stage.
|
||||||
Collision,
|
Collision,
|
||||||
|
Animation
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Declares the order of [`GameplaySet`] inside [`GgrsSchedule`]. Added by
|
/// Declares the order of [`GameplaySet`] inside [`GgrsSchedule`]. Added by
|
||||||
@@ -37,6 +38,7 @@ impl Plugin for GameplaySchedulePlugin {
|
|||||||
GameplaySet::StateUpdate,
|
GameplaySet::StateUpdate,
|
||||||
GameplaySet::Physics,
|
GameplaySet::Physics,
|
||||||
GameplaySet::Collision,
|
GameplaySet::Collision,
|
||||||
|
GameplaySet::Animation,
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-6
@@ -1,16 +1,19 @@
|
|||||||
use bevy::{color::palettes::css::RED, prelude::*};
|
use bevy::{color::palettes::css::RED, prelude::*};
|
||||||
|
|
||||||
use crate::stage::StageLine;
|
use crate::stage::{StagePoly, line::StageCollision};
|
||||||
|
|
||||||
pub fn debug_draw_scene(mut gizmos: Gizmos, planes: Query<&StageLine>) {
|
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
|
||||||
for plane in planes {
|
for poly in stage_collision.stage_polys.iter() {
|
||||||
let from = plane.segment.point1();
|
for segment in poly.segments.iter() {
|
||||||
let to = plane.segment.point2();
|
let from = segment.segment.point1();
|
||||||
|
let to = segment.segment.point2();
|
||||||
let from_3d = Vec3::new(from.x, from.y, 0.0);
|
let from_3d = Vec3::new(from.x, from.y, 0.0);
|
||||||
let end_3d = Vec3::new(to.x, to.y, 0.0);
|
let end_3d = Vec3::new(to.x, to.y, 0.0);
|
||||||
let normal_3d = Vec3::new(plane.normal.x, plane.normal.y, 0.0);
|
let normal_3d = Vec3::new(segment.normal.x, segment.normal.y, 0.0);
|
||||||
gizmos.line(from_3d, end_3d, RED);
|
gizmos.line(from_3d, end_3d, RED);
|
||||||
let midpoint = (from_3d + end_3d) * 0.5;
|
let midpoint = (from_3d + end_3d) * 0.5;
|
||||||
gizmos.arrow(midpoint, midpoint + normal_3d, RED);
|
gizmos.arrow(midpoint, midpoint + normal_3d, RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+124
-13
@@ -1,34 +1,145 @@
|
|||||||
//! Stage collision geometry.
|
//! Stage collision geometry.
|
||||||
|
|
||||||
|
use std::default;
|
||||||
|
|
||||||
use bevy::{math::InvalidDirectionError, prelude::*};
|
use bevy::{math::InvalidDirectionError, prelude::*};
|
||||||
|
|
||||||
use crate::math::SegmentIntersection;
|
use crate::math::segment::SegmentIntersection;
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone, Copy, PartialEq)]
|
||||||
|
pub enum StagePolyLineSegmentType {
|
||||||
|
Floor, Wall, Ceiling
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone)]
|
||||||
|
pub struct StagePolyLineSegment {
|
||||||
|
pub segment: Segment2d,
|
||||||
|
pub normal: Vec2,
|
||||||
|
pub segment_type: StagePolyLineSegmentType
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone, Default)]
|
||||||
|
pub enum StagePolyType {
|
||||||
|
#[default]
|
||||||
|
Closed,
|
||||||
|
Platform
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Hash, Debug, Clone, Copy, Default)]
|
||||||
|
pub struct StageLineID {
|
||||||
|
pub polygon: usize,
|
||||||
|
pub segment: usize
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component, Reflect, Clone, Default)]
|
#[derive(Component, Reflect, Clone, Default)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct StageLine {
|
pub struct StagePoly {
|
||||||
pub segment: Segment2d,
|
poly_type: StagePolyType,
|
||||||
pub normal: Vec2,
|
pub segments: Vec<StagePolyLineSegment>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StagePlaneIntersectResult {
|
pub struct StagePlaneIntersectResult {
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
|
pub segment_idx: usize,
|
||||||
|
pub normal: Vec2
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StageLine {
|
#[derive(Resource, Reflect, Clone, Default)]
|
||||||
pub fn new(start: Vec2, end: Vec2) -> Result<Self, InvalidDirectionError> {
|
#[reflect(Resource)]
|
||||||
let segment = Segment2d::new(start, end);
|
pub struct StageCollision {
|
||||||
let normal = segment.left_normal().as_vec2();
|
pub stage_polys: Vec<StagePoly>
|
||||||
Ok(Self { segment, normal })
|
}
|
||||||
|
|
||||||
|
impl StagePoly {
|
||||||
|
pub fn intersect_ray(&self, ray_segment: Segment2d) -> Option<StagePlaneIntersectResult> {
|
||||||
|
for (i, segment) in self.segments.iter().enumerate() {
|
||||||
|
if let Some(intersection) = segment.segment.segment_intersection(&ray_segment) {
|
||||||
|
println!("DIST3 {} {}", intersection, segment.segment.closest_point(intersection));
|
||||||
|
return Some(StagePlaneIntersectResult {
|
||||||
|
position: intersection,
|
||||||
|
segment_idx: i,
|
||||||
|
normal: segment.normal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersect_ray(&self, segment: Segment2d) -> Option<StagePlaneIntersectResult> {
|
pub fn get_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||||
self.segment
|
self.segments.get(segment_idx)
|
||||||
.segment_intersection(&segment)
|
|
||||||
.map(|position| StagePlaneIntersectResult { position })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_next_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||||
|
self.segments.get(segment_idx % segment_idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_prev_segment(&self, segment_idx: usize) -> Option<&StagePolyLineSegment> {
|
||||||
|
self.segments.get(segment_idx % segment_idx + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(poly_type: StagePolyType, points: &[(Vec2, StagePolyLineSegmentType)]) -> Self {
|
||||||
|
let windows = points.iter().zip(points.iter().cycle().skip(1));
|
||||||
|
|
||||||
|
let segments = windows.map(|segment_window| {
|
||||||
|
let (first_point, segment_type) = segment_window.0;
|
||||||
|
let (second_point, _) = segment_window.1;
|
||||||
|
let segment = Segment2d::new(*first_point, *second_point);
|
||||||
|
StagePolyLineSegment {
|
||||||
|
segment_type: *segment_type,
|
||||||
|
segment,
|
||||||
|
normal: segment.left_normal().as_vec2()
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
poly_type,
|
||||||
|
segments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StagePolyLineSegment {
|
||||||
pub fn project(&self, vec: Vec2) -> Vec2 {
|
pub fn project(&self, vec: Vec2) -> Vec2 {
|
||||||
vec - vec.project_onto_normalized(self.normal)
|
vec - vec.project_onto_normalized(self.normal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn ray_intersection_test() {
|
||||||
|
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||||
|
(Vec2::new(-4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(0.0, 1.0), Vec2::new(0.0, -1.0)));
|
||||||
|
assert!(res.is_some());
|
||||||
|
assert_eq!(res.unwrap().position, Vec2::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ray_intersection_test_side() {
|
||||||
|
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||||
|
(Vec2::new(-4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(4.5, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(2.0, 1.0), Vec2::new(2.0, -1.0)));
|
||||||
|
assert!(res.is_some());
|
||||||
|
assert_eq!(res.unwrap().position, Vec2::new(2.0, 0.0));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersection_test_diagonal() {
|
||||||
|
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Platform, &[
|
||||||
|
(Vec2::new(0.0, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(2.0, 2.0), StagePolyLineSegmentType::Floor),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let res = stage_poly.intersect_ray(Segment2d::new(Vec2::new(1.0, 2.0), Vec2::new(1.0, -2.0)));
|
||||||
|
assert!(res.is_some());
|
||||||
|
assert_eq!(res.unwrap().position, Vec2::new(1.0, 1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -7,7 +7,7 @@ pub mod debug;
|
|||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
|
|
||||||
pub use line::{StageLine, StagePlaneIntersectResult};
|
pub use line::{StagePoly, StagePlaneIntersectResult};
|
||||||
|
|
||||||
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
+26
-14
@@ -1,37 +1,49 @@
|
|||||||
//! The scene content that gets spawned at startup.
|
//! The scene content that gets spawned at startup.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use crate::stage::StageLine;
|
use bevy::{camera_controller::free_camera::FreeCamera, prelude::*};
|
||||||
|
|
||||||
|
use crate::stage::{StagePoly, line::StagePolyLineSegmentType, line::StageCollision};
|
||||||
|
|
||||||
/// set up a simple 3D scene
|
/// set up a simple 3D scene
|
||||||
pub fn test_scene() -> impl SceneList {
|
pub fn test_scene() -> impl SceneList {
|
||||||
|
|
||||||
|
let stage_poly = StagePoly::build(crate::stage::line::StagePolyType::Closed, &[
|
||||||
|
(Vec2::new(-5.6, -0.35), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(-3.92, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(0.0, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(3.92, 0.0), StagePolyLineSegmentType::Floor),
|
||||||
|
(Vec2::new(5.6, -0.35), StagePolyLineSegmentType::Wall),
|
||||||
|
(Vec2::new(5.6, -20.0), StagePolyLineSegmentType::Ceiling),
|
||||||
|
(Vec2::new(-5.6, -20.0), StagePolyLineSegmentType::Wall)
|
||||||
|
]);
|
||||||
|
|
||||||
bsn_list! [
|
bsn_list! [
|
||||||
(
|
(
|
||||||
#Cube
|
#StageCollision
|
||||||
Mesh3d(asset_value(Cuboid::new(5.0, 1.0, 1.0)))
|
template_value(StageCollision {
|
||||||
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
|
stage_polys: vec!(stage_poly)
|
||||||
Transform::from_xyz(0.0, 0.5, 0.0)
|
})
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
#StagePlane
|
DirectionalLight {
|
||||||
template_value(StageLine::new(Vec2::new(-2.5, 1.0), Vec2::new(2.5, 1.0)).unwrap())
|
|
||||||
),
|
|
||||||
(
|
|
||||||
PointLight {
|
|
||||||
shadow_maps_enabled: true,
|
shadow_maps_enabled: true,
|
||||||
}
|
}
|
||||||
Transform::from_xyz(4.0, 8.0, 4.0)
|
Transform {
|
||||||
|
translation: Vec3::new(0.0, 2.0, 0.0),
|
||||||
|
rotation: Quat::from_rotation_x(-PI / 4.),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Camera3d
|
Camera3d
|
||||||
template_value(Transform::from_xyz(0.0, 1.5, 9.0))
|
template_value(Transform::from_xyz(0.0, 1.5, 9.0))
|
||||||
/*FreeCamera {
|
FreeCamera {
|
||||||
sensitivity: 0.2,
|
sensitivity: 0.2,
|
||||||
friction: 25.0,
|
friction: 25.0,
|
||||||
walk_speed: 3.0,
|
walk_speed: 3.0,
|
||||||
run_speed: 9.0,
|
run_speed: 9.0,
|
||||||
}*/
|
}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user