bevy-ichun 0.6.0

A simple kinematic character controller for avian3d
Documentation
//! # Jump Action
//!
//! This module provides jumping functionality for kinematic character controllers.
//! It handles jump impulses, air jumping, and platform interaction management.

use avian3d::math::Scalar;
use bevy::ecs::{entity::Entity, system::Commands};

use crate::kcc::{JumpedRecently, Kcc, KccVelocity};

/// Action for applying jump impulses to a character.
///
/// This action handles vertical movement through jumping mechanics. It provides:
///
/// * **Ground-based jumping**: Standard jumping that only works when grounded
/// * **Air jumping**: Optional ability to jump while airborne
/// * **External force handling**: Reduces downward forces when jumping for better feel
/// * **Platform interaction**: Temporarily disables platform syncing after jumps
///
/// The jump system sets the Y velocity directly to the jump impulse value,
/// providing consistent jump height regardless of current velocity.
///
/// ## Example
///
/// ```rust
/// use your_crate::actions::JumpAction;
///
/// // Standard ground-only jump
/// let ground_jump = JumpAction::new(500.0);
///
/// // Double jump (can jump in air)
/// let double_jump = JumpAction::new(400.0)
///     .with_allow_in_air(true);
/// ```
#[derive(Default, Debug)]
pub struct JumpAction {
    /// The upward impulse strength applied when jumping.
    /// Higher values result in higher jumps.
    pub jump_impulse: Scalar,

    /// Whether the character can jump while not grounded.
    /// When `false`, jumps only work when the character is on the ground.
    /// When `true`, allows for double jumps, air dashes, etc.
    pub allow_in_air: bool,
}

impl JumpAction {
    /// Creates a new jump action with the specified impulse strength.
    ///
    /// By default, jumping is only allowed when grounded. Use [`with_allow_in_air`]
    /// to enable air jumping.
    ///
    /// # Arguments
    ///
    /// * `jump_impulse` - The upward velocity to apply when jumping.
    ///   Typical values range from 300.0 to 800.0 depending on your game's scale.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use your_crate::actions::JumpAction;
    ///
    /// // Light jump
    /// let light_jump = JumpAction::new(350.0);
    ///
    /// // High jump
    /// let high_jump = JumpAction::new(700.0);
    /// ```
    ///
    /// [`with_allow_in_air`]: Self::with_allow_in_air
    pub fn new(jump_impulse: Scalar) -> Self {
        Self {
            jump_impulse,
            allow_in_air: false,
        }
    }

    /// Configures whether the character can jump while airborne.
    ///
    /// # Arguments
    ///
    /// * `allow` - `true` to enable air jumping, `false` to restrict to ground only
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    ///
    /// # Examples
    ///
    /// ```rust
    /// use your_crate::actions::JumpAction;
    ///
    /// // Enable double jumping
    /// let double_jump = JumpAction::new(450.0)
    ///     .with_allow_in_air(true);
    ///
    /// // Explicitly disable air jumping (default behavior)
    /// let ground_only = JumpAction::new(500.0)
    ///     .with_allow_in_air(false);
    /// ```
    pub fn with_allow_in_air(mut self, allow: bool) -> Self {
        self.allow_in_air = allow;
        self
    }

    pub(super) fn apply(
        &mut self,
        commands: &mut Commands,
        entity: Entity,
        kcc_vel: &mut KccVelocity,
        kcc: &mut Kcc,
    ) {
        // Check if jump is allowed based on grounded state
        if !kcc.is_grounded && !self.allow_in_air {
            return;
        }

        // Reduce downward external forces when jumping
        // This improves jump feel by preventing external forces from immediately
        // counteracting the jump impulse
        if kcc.external_force.y < 0.0 {
            kcc.external_force.y *= 0.3;
        }

        // Apply jump impulse to velocity
        // Uses velocity directly for consistent jump behavior
        // Setting directly (rather than adding) ensures consistent jump height
        kcc_vel.0.y = self.jump_impulse;

        // Mark entity as having jumped recently to temporarily disable platform syncing
        // This prevents platforms from pulling the KCC down immediately after a jump,
        // which would make jumps feel unresponsive or inconsistent
        commands.entity(entity).insert(JumpedRecently::default());
    }
}