Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/bob/rigging/Bob-Chest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Head.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Left-Arm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Left-Leg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Right-Arm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Right-Leg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bob/rigging/Bob-Weapon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/game/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub(crate) mod moving;
pub(crate) mod npc;
pub(crate) mod plasma;
pub(crate) mod player;
pub(crate) mod ragdoll;

#[derive(Component)]
pub(crate) struct SpawnedLevelEntity;
Expand Down
37 changes: 37 additions & 0 deletions src/game/components/ragdoll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use bevy::prelude::*;

/// Marker: kennzeichnet die Chest-Entity des Ragdolls.
/// Physik (Velocity, Gravity, Collision) wird von avian2d gesteuert.
#[derive(Component, Debug, Clone, Copy)]
pub(crate) struct RagdollChest;

/// Marker: kennzeichnet die fliegende Waffe des Ragdolls.
/// Physik wird von avian2d gesteuert (Dynamic RigidBody).
#[derive(Component, Debug, Clone, Copy)]
pub(crate) struct RagdollWeapon;

/// Eine einzelne Gliedmaße.
/// Wenn `chest_entity` Some ist, wird die Position jedes Frame hart an den
/// Joint-Punkt des Chests geknüpft (Position Constraint). Nur die Rotation
/// ist frei.
#[derive(Component, Debug, Clone)]
pub(crate) struct RagdollLimb {
pub(crate) angular_velocity: f32,
/// Entity des Chests, an dem dieses Glied hängt.
pub(crate) chest_entity: Option<Entity>,
/// Offset des Joint-Ankerpunkts vom Chest-Zentrum (Chest-Local-Space, y-up, skaliert).
pub(crate) chest_joint_local: Vec2,
/// Offset des Pivot-Punkts von diesem Glied-Zentrum (Limb-Local-Space, y-up, skaliert).
pub(crate) limb_pivot_local: Vec2,
}

/// Marker: Bobs Sprite ist versteckt weil ein Ragdoll aktiv ist.
#[derive(Component, Debug, Clone, Copy)]
pub(crate) struct RagdollActive;

/// Wird einmalig gefeuert wenn Bobs HP auf 0 fällt.
#[derive(Event, Debug, Clone, Copy)]
pub(crate) struct PlayerDiedEvent {
pub(crate) player_position: Vec2,
pub(crate) killer_position: Option<Vec2>,
}
38 changes: 24 additions & 14 deletions src/game/game_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ mod animation;
mod npc;
#[path = "systems/player.rs"]
mod player;
#[path = "systems/ragdoll.rs"]
mod ragdoll;
#[path = "systems/setup.rs"]
mod setup;

Expand Down Expand Up @@ -85,16 +87,18 @@ impl ActiveLevelBounds {

impl Plugin for GameViewPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
app.add_event::<crate::game::components::ragdoll::PlayerDiedEvent>()
.add_systems(
OnEnter(crate::AppState::GameView),
(
setup::setup_game_view,
camera::snap_camera_to_player,
player::configure_player_controller,
hud::spawn_player_health_hud,
hud::spawn_player_health_hud, // ← aus main
)
.chain(),
)
// --- Block 1: Input / Physik / Kampf ---
.add_systems(
Update,
(
Expand All @@ -107,6 +111,8 @@ impl Plugin for GameViewPlugin {
npc::control_moving_entities,
combat::tick_invincibility_timers,
combat::apply_hostile_contact_damage,
ragdoll::spawn_ragdoll.after(combat::apply_hostile_contact_damage),
ragdoll::update_ragdoll_parts,
combat::shoot_plasma.before(combat::update_plasma_beams),
(
combat::update_plasma_beams,
Expand All @@ -116,22 +122,27 @@ impl Plugin for GameViewPlugin {
.chain()
.before(animation::tick_hit_state_timers)
.before(animation::apply_state_animation),
(
animation::sync_death_state_from_health,
combat::disable_dead_npc_collisions,
combat::play_hostile_death_quotes,
animation::tick_hit_state_timers,
animation::apply_state_animation,
combat::despawn_dead_entities,
),
)
.run_if(in_state(crate::AppState::GameView)),
)
// --- Block 2: Animation / UI / Debug / Zustandsübergänge ---
.add_systems(
Update,
(
animation::sync_death_state_from_health,
combat::disable_dead_npc_collisions, // ← aus main
combat::play_hostile_death_quotes,
animation::tick_hit_state_timers,
animation::apply_state_animation,
combat::despawn_dead_entities,
debug::toggle_hitbox_debug_lines,
debug::update_debug_stats_labels,
debug::toggle_debug_overlay,
debug::draw_hitbox_debug_lines,
hud::update_player_health_hud,
hud::update_player_health_hud, // ← aus main
(
combat::detect_player_defeated,
combat::detect_player_reached_exit,
combat::detect_player_defeated, // ← aus main
combat::detect_player_reached_exit, // ← aus main
combat::return_to_main_menu,
),
)
Expand All @@ -145,4 +156,3 @@ impl Plugin for GameViewPlugin {
}
}


22 changes: 20 additions & 2 deletions src/game/systems/combat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::game::components::plasma::{
PLASMA_IMPACT_LIFETIME_SECS, PLASMA_IMPACT_MAX_SPEED, PLASMA_IMPACT_MIN_SPEED,
PLASMA_IMPACT_PARTICLE_COUNT, PLASMA_ORIGIN_HEIGHT_RATIO_FROM_BOTTOM, PLASMA_Z,
};
use crate::game::components::ragdoll::{PlayerDiedEvent, RagdollActive};
use crate::game::components::SpawnedLevelEntity;
use crate::audio_settings::AudioSettings;
use crate::AppState;
Expand Down Expand Up @@ -64,6 +65,7 @@ pub(super) fn tick_invincibility_timers(
pub(super) fn apply_hostile_contact_damage(
mut commands: Commands,
hostile_query: Query<(&Damage, Option<&Health>), (With<Hostile>, Without<Player>)>,
hostile_transforms: Query<&Transform, (With<Hostile>, Without<Player>)>,
mut hostile_states: Query<
(&mut AnimationState, Option<&HitStateTimer>),
(With<Hostile>, Without<Player>),
Expand All @@ -74,11 +76,13 @@ pub(super) fn apply_hostile_contact_damage(
&avian2d::prelude::CollidingEntities,
&mut Health,
&mut AnimationState,
&Transform,
),
(With<Player>, Without<InvincibilityTimer>, Without<Hostile>),
(With<Player>, Without<InvincibilityTimer>, Without<Hostile>, Without<RagdollActive>),
>,
mut player_died: EventWriter<PlayerDiedEvent>,
) {
for (player_entity, colliding_entities, mut health, mut player_state) in &mut player_query {
for (player_entity, colliding_entities, mut health, mut player_state, player_transform) in &mut player_query {
if health.is_dead() {
continue;
}
Expand All @@ -99,6 +103,20 @@ pub(super) fn apply_hostile_contact_damage(
commands
.entity(player_entity)
.insert(HitStateTimer::new(HIT_STATE_SECONDS, player_state.version));
} else {
// Bob just died — fire ragdoll event.
let killer_pos = hostile_transforms
.get(colliding_entity)
.ok()
.map(|t| t.translation.xy());

player_died.send(PlayerDiedEvent {
player_position: player_transform.translation.xy(),
killer_position: killer_pos,
});

// Prevent duplicate events in subsequent frames.
commands.entity(player_entity).insert(RagdollActive);
}

if let Ok((mut hostile_state, hostile_hit_timer)) = hostile_states.get_mut(colliding_entity)
Expand Down
Loading