bevy-ichun 0.6.0

A simple kinematic character controller for avian3d
Documentation
//! # Move Action
//!
//! This module provides movement functionality for kinematic character controllers.
//! It handles ground and air movement with configurable acceleration, air control,
//! and speed limiting.

use avian3d::math::{AdjustPrecision, Scalar};
use bevy::{
    math::{Vec2, Vec3},
    time::Time,
    transform::components::Transform,
};

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

/// Action for applying movement forces to a character.
///
/// This action handles both ground and air movement with sophisticated control systems:
///
/// * **Ground Movement**: Full acceleration and control when grounded
/// * **Air Control**: Reduced but configurable control while airborne
/// * **Speed Limiting**: Different maximum speeds for ground and air movement
/// * **Rotation Awareness**: Can account for character rotation in movement calculations
///
/// The movement system uses acceleration-based physics, meaning characters gradually
/// build up speed rather than instantly reaching target velocity. This provides
/// more natural and controllable movement feel.
///
/// ## Example
///
/// ```rust
/// use bevy::math::Vec3;
/// use your_crate::actions::MoveAction;
///
/// // Basic movement (forward)
/// let forward = MoveAction::new(Vec3::new(0.0, 0.0, 1.0));
///
/// // Fast movement with good air control
/// let agile_movement = MoveAction::new(Vec3::new(1.0, 0.0, 0.0))
///     .with_movement_acceleration(150.0)
///     .with_air_control(0.8)
///     .with_air_max_speed_multiplier(1.2);
/// ```
#[derive(Default, Debug)]
pub struct MoveAction {
    /// The desired movement direction and speed.
    pub desired_velocity: Vec3,

    /// The acceleration used for character movement.
    pub movement_acceleration: Scalar,

    /// How much control the character has in the air (0.0 - 1.0).
    /// Lower values give less control, higher values give more control.
    /// * 0.0 = No air control (character continues in original direction)
    /// * 0.5 = Moderate air control (common for platformers)
    /// * 1.0 = Full air control (same as ground movement)
    pub air_control_factor: Scalar,

    /// How quickly air control takes effect.
    /// Higher values make air movement more responsive.
    /// This multiplier is applied on top of the air control factor.
    pub air_acceleration_factor: Scalar,

    /// Max speed in air relative to ground speed.
    /// Values above 1.0 allow faster movement in air than on ground.
    /// * 0.8 = Air movement is slower than ground
    /// * 1.0 = Same speed in air and on ground
    /// * 1.2 = Can move faster in air (useful for advanced movement)
    pub air_max_speed_multiplier: Scalar,

    /// Whether to account for the character's rotation when calculating movement.
    /// * `true`: Movement is relative to character facing (forward moves in facing direction)
    /// * `false`: Movement is in world space (forward always moves toward world +Z)
    pub account_rotation: bool,
}

impl MoveAction {
    /// Creates a new movement action with the specified desired velocity.
    ///
    /// The action is created with sensible defaults for most use cases:
    /// - Movement acceleration: 100.0
    /// - Air control factor: 0.3 (30% air control)
    /// - Air acceleration factor: 50.0
    /// - Air max speed multiplier: 1.0 (same speed as ground)
    /// - Account rotation: true (rotation-relative movement)
    ///
    /// # Arguments
    ///
    /// * `desired_velocity` - The movement direction and strength.
    ///   Typically a normalized Vec3 (e.g., Vec3::new(1.0, 0.0, 0.0) for right movement)
    ///
    /// # Examples
    ///
    /// ```rust
    /// use bevy::math::Vec3;
    /// use your_crate::actions::MoveAction;
    ///
    /// // Move forward
    /// let forward = MoveAction::new(Vec3::Z);
    ///
    /// // Move diagonally (forward-right)
    /// let diagonal = MoveAction::new(Vec3::new(1.0, 0.0, 1.0).normalize());
    ///
    /// // Analog input (partial strength)
    /// let partial = MoveAction::new(Vec3::new(0.7, 0.0, 0.0));
    /// ```
    pub fn new(desired_velocity: Vec3) -> Self {
        Self {
            desired_velocity,
            movement_acceleration: 100.0,
            air_control_factor: 0.3,
            air_acceleration_factor: 50.0,
            air_max_speed_multiplier: 1.0,
            account_rotation: true,
        }
    }

    /// Sets the movement acceleration.
    ///
    /// # Arguments
    ///
    /// * `acceleration` - The acceleration value. Higher values make the character
    ///   reach target speed faster.
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    pub fn with_movement_acceleration(mut self, acceleration: Scalar) -> Self {
        self.movement_acceleration = acceleration;
        self
    }

    /// Sets the air control factor.
    ///
    /// # Arguments
    ///
    /// * `factor` - Air control strength (0.0 - 1.0). Higher values give more
    ///   control while airborne.
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    pub fn with_air_control(mut self, factor: Scalar) -> Self {
        self.air_control_factor = factor;
        self
    }

    /// Sets the air acceleration factor.
    ///
    /// # Arguments
    ///
    /// * `factor` - Multiplier for air control responsiveness. Higher values make
    ///   air movement changes happen faster.
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    pub fn with_air_acceleration_factor(mut self, factor: Scalar) -> Self {
        self.air_acceleration_factor = factor;
        self
    }

    /// Sets the air maximum speed multiplier.
    ///
    /// # Arguments
    ///
    /// * `multiplier` - Speed multiplier for air movement relative to ground speed.
    ///   Values > 1.0 allow faster air movement.
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    pub fn with_air_max_speed_multiplier(mut self, multiplier: Scalar) -> Self {
        self.air_max_speed_multiplier = multiplier;
        self
    }

    /// Sets whether to account for character rotation in movement calculations.
    ///
    /// # Arguments
    ///
    /// * `account` - `true` for rotation-relative movement, `false` for world-space movement
    ///
    /// # Returns
    ///
    /// Self, for method chaining
    pub fn with_account_rotation(mut self, account: bool) -> Self {
        self.account_rotation = account;
        self
    }

    /// Applies the movement action to the character's velocity.
    ///
    /// This method performs several calculations:
    /// 1. **Air Control Adjustment**: Reduces acceleration when airborne
    /// 2. **Rotation Handling**: Applies character rotation if enabled
    /// 3. **Velocity Application**: Adds calculated velocity changes
    /// 4. **Speed Limiting**: Enforces maximum air speed limits
    ///
    /// # Arguments
    ///
    /// * `time` - Time resource for delta time calculations
    /// * `kcc_vel` - Mutable reference to character velocity
    /// * `transform` - Character transform (for rotation calculations)
    /// * `kcc` - Character controller state (for grounded checks)
    ///
    /// # Implementation Details
    ///
    /// ## Ground vs Air Movement
    /// - **Grounded**: Uses full `movement_acceleration`
    /// - **Airborne**: Uses `movement_acceleration * air_control_factor * air_acceleration_factor`
    ///
    /// ## Rotation Handling
    /// When `account_rotation` is true, the desired velocity is rotated by the character's
    /// transform rotation. This means:
    /// - Vec3::Z (forward) moves in the character's facing direction
    /// - Vec3::X (right) moves to the character's right side
    ///
    /// ## Speed Limiting
    /// When airborne, horizontal speed is clamped to `movement_acceleration * air_max_speed_multiplier`.
    /// This prevents characters from building up excessive speed while in the air.
    ///
    /// ## Delta Time
    /// All acceleration values are multiplied by delta time to ensure frame-rate independent movement.
    pub fn apply(
        &mut self,
        time: &Time,
        kcc_vel: &mut KccVelocity,
        transform: &Transform,
        kcc: &Kcc,
    ) {
        let delta_time = time.delta_secs_f64().adjust_precision();

        // Apply air control factor if not grounded
        let adjusted_acceleration = if kcc.is_grounded {
            self.movement_acceleration
        } else {
            // Apply reduced control in air
            // Combines air control factor (how much control) with acceleration factor (how responsive)
            self.movement_acceleration * self.air_control_factor * self.air_acceleration_factor
        };

        // Calculate velocity change based on desired direction
        let velocity_direction = if self.account_rotation {
            // Rotate the desired velocity by the transform's rotation
            // This makes movement relative to character facing direction
            transform.rotation.mul_vec3(Vec3::new(
                self.desired_velocity.x * adjusted_acceleration * delta_time,
                0.0, // Y component not affected by horizontal movement
                self.desired_velocity.z * adjusted_acceleration * delta_time,
            ))
        } else {
            // Use world-space velocity directly
            // Movement is always in world directions regardless of character rotation
            Vec3::new(
                self.desired_velocity.x * adjusted_acceleration * delta_time,
                0.0, // Y component not affected by horizontal movement
                self.desired_velocity.z * adjusted_acceleration * delta_time,
            )
        };

        // Apply velocity changes to horizontal components only
        kcc_vel.x += velocity_direction.x;
        kcc_vel.z += velocity_direction.z;

        // Apply max air speed limit if airborne
        if !kcc.is_grounded {
            let max_air_speed = self.movement_acceleration * self.air_max_speed_multiplier;
            let current_horizontal_speed = Vec2::new(kcc_vel.x, kcc_vel.z).length();

            // If current speed exceeds the air speed limit, clamp it
            if current_horizontal_speed > max_air_speed {
                let normalized = Vec2::new(kcc_vel.x, kcc_vel.z).normalize() * max_air_speed;
                kcc_vel.x = normalized.x;
                kcc_vel.z = normalized.y;
            }
        }
    }
}