Skip to content

Commit

Permalink
Merge pull request #1 from 0xAlcibiades/fix/physics
Browse files Browse the repository at this point in the history
Make physics and paddles better
  • Loading branch information
0xAlcibiades authored Dec 28, 2024
2 parents 4482883 + 2da636e commit 50a1d41
Show file tree
Hide file tree
Showing 5 changed files with 841 additions and 386 deletions.
11 changes: 1 addition & 10 deletions src/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct MusicPlugin;
///
/// The state persists across game state changes to maintain user preferences
/// for music playback.
#[derive(Resource)]
#[derive(Resource, Default)]
struct MusicState {
/// Indicates if music should be playing (true) or muted (false)
playing: bool,
Expand All @@ -31,15 +31,6 @@ struct MusicState {
handle: Option<Handle<AudioInstance>>,
}

impl Default for MusicState {
fn default() -> Self {
Self {
playing: false, // Start with music enabled by default
handle: None, // No audio instance at initialization
}
}
}

impl Plugin for MusicPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(AudioPlugin)
Expand Down
234 changes: 163 additions & 71 deletions src/ball.rs
Original file line number Diff line number Diff line change
@@ -1,122 +1,214 @@
//! Ball Physics Module
//!
//! This module handles all aspects of the game ball, including:
//! - Physical properties and behavior
//! - Spawn and cleanup logic
//! - Velocity maintenance
//! - Collision detection and response
//! This module implements the core ball mechanics for the Pong game using the Bevy game engine
//! and Rapier2D physics engine. It handles all aspects of the ball's behavior including:
//!
//! The ball uses Rapier2D physics for realistic movement and collisions.
//! - Ball creation and initialization
//! - Physics properties and collision response
//! - Velocity management and speed constraints
//! - Cleanup and state management
//! - Collision detection and event handling
//!
//! The ball uses Rapier2D's rigid body physics system for realistic movement and collisions,
//! with carefully tuned parameters to ensure engaging gameplay while maintaining physical plausibility.
use crate::GameState;
use bevy::app::{App, Plugin, Update};
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;

/// Physical properties of the ball that define its behavior
const BALL_SIZE: f32 = 0.3; // Diameter in world units
const BALL_SPEED: f32 = 10.0; // Constant speed the ball should maintain
const SPEED_TOLERANCE: f32 = 0.5; // Allowed speed variation before correction
const RESTITUTION: f32 = 1.5; // Bounciness (>1 means gaining energy)
const BALL_MASS: f32 = 0.1; // Mass affects collision response
/// Physical properties and gameplay constants for the ball
///
/// These constants define both the visual and physical characteristics of the ball,
/// carefully tuned to provide satisfying gameplay mechanics while maintaining
/// physical plausibility.
const BALL_SIZE: f32 = 0.3; // Ball diameter in world units (small enough for precise gameplay)
const MIN_VELOCITY: f32 = 7.0; // Minimum ball speed (ensures game keeps moving)
const MAX_VELOCITY: f32 = 20.0; // Maximum ball speed (prevents ball from becoming too fast)
const RESTITUTION: f32 = 0.9; // Bounce elasticity (slightly inelastic for better control)
const BALL_MASS: f32 = 0.0027; // Ball mass (tuned for realistic collision responses)

/// Marker component to identify ball entities.
/// Used for querying and managing ball-specific behavior.
/// Marker component for identifying ball entities in the game world.
///
/// This component is used as a tag to:
/// - Query for ball entities in systems
/// - Filter collision events involving the ball
/// - Manage ball-specific behavior and cleanup
///
/// # Example Usage
/// ```rust
/// // Query for ball entities
/// fn ball_system(query: Query<&Transform, With<Ball>>) {
/// for transform in query.iter() {
/// // Process ball position
/// }
/// }
/// ```
#[derive(Component)]
pub struct Ball;

/// Creates a new ball entity with all necessary components for physics and rendering.
/// Creates a new ball entity with complete physics and rendering setup.
///
/// This function creates a ball entity configured with:
/// - Visual representation (white circle mesh)
/// - Physics body and collider
/// - Initial velocity based on serving direction
/// - Collision properties and response settings
/// - Physics modifiers for gameplay behavior
///
/// # Arguments
/// * `commands` - Command buffer for entity creation
/// * `meshes` - Asset storage for the ball's mesh
/// * `materials` - Asset storage for the ball's material
/// * `served_by_p1` - Direction ball should initially move (true = right, false = left)
/// * `commands` - Command buffer for entity creation and component insertion
/// * `meshes` - Asset storage for managing the ball's visual mesh
/// * `materials` - Asset storage for managing the ball's material/color
/// * `served_by_p1` - Boolean flag indicating serve direction (true = right, false = left)
///
/// # Physics Configuration
/// The ball is configured with:
/// - Dynamic rigid body for physics-based movement
/// - Zero friction to maintain momentum
/// - Zero gravity for 2D pong mechanics
/// - Continuous collision detection for reliability
/// - Custom mass and restitution for desired bounce behavior
///
/// # Example
/// ```rust
/// create_ball(&mut commands, &mut meshes, &mut materials, true); // Serve to the right
/// ```
pub fn create_ball(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
served_by_p1: bool,
) {
// Determine initial direction based on server
// Calculate initial direction and velocity
let direction = if served_by_p1 { 1 } else { -1 };
let initial_velocity = Vec2::new(MIN_VELOCITY * direction as f32, 0.0);

commands.spawn((
// Identification and visual components
Ball, // Marker component
Mesh2d(meshes.add(Circle::new(BALL_SIZE / 2.0))), // Visual shape
MeshMaterial2d(materials.add(ColorMaterial::from(Color::WHITE))), // Color
Transform::from_xyz(0.0, 0.0, 0.0), // Start at center
// Physics body configuration
RigidBody::Dynamic, // Moves based on physics
Collider::ball(BALL_SIZE / 2.0), // Circular collision shape
// Initial velocity (horizontal only)
Velocity::linear(Vec2::new(BALL_SPEED * direction as f32, 0.0)),
// Collision response properties
Restitution {
commands
.spawn(Ball)
// Visual Components
// Creates a circular mesh for rendering with appropriate size
.insert(Mesh2d(meshes.add(Circle::new(BALL_SIZE / 2.0))))
// Applies white color material to the ball
.insert(MeshMaterial2d(
materials.add(ColorMaterial::from(Color::WHITE)),
))
// Positions ball at center of screen initially
.insert(Transform::from_xyz(0.0, 0.0, 0.0))
// Physics Body Configuration
// Sets up dynamic rigid body for physics simulation
.insert(RigidBody::Dynamic)
// Creates circular collider matching visual size
.insert(Collider::ball(BALL_SIZE / 2.0))
// Sets initial movement velocity
.insert(Velocity::linear(initial_velocity))
// Collision Properties
// Configures bounce behavior
.insert(Restitution {
coefficient: RESTITUTION,
combine_rule: CoefficientCombineRule::Max, // Use highest restitution in collisions
},
Friction {
coefficient: 0.0, // Frictionless
combine_rule: CoefficientCombineRule::Min, // Use lowest friction in collisions
},
// Physics behavior modifiers
Damping {
linear_damping: 0.0, // No speed loss over time
angular_damping: 0.0, // No rotation slowdown
},
GravityScale(0.0), // Ignore gravity
// Collision detection settings
Ccd::enabled(), // Continuous collision detection
ActiveCollisionTypes::all(), // Detect all collision types
ActiveEvents::COLLISION_EVENTS, // Generate collision events
AdditionalMassProperties::Mass(BALL_MASS), // Set specific mass
));
combine_rule: CoefficientCombineRule::Max,
})
// Removes friction for consistent movement
.insert(Friction {
coefficient: 0.0,
combine_rule: CoefficientCombineRule::Min,
})
// Physics Modifiers
// Disables velocity damping to maintain speed
.insert(Damping {
linear_damping: 0.0,
angular_damping: 0.0,
})
// Removes gravity effect
.insert(GravityScale(0.0))
// Collision Detection Setup
// Enables continuous collision detection for fast movement
.insert(Ccd::enabled())
// Prevents physics sleep for consistent behavior
.insert(Sleeping::disabled())
// Enables all collision types for comprehensive detection
.insert(ActiveCollisionTypes::all())
// Enables collision event generation
.insert(ActiveEvents::COLLISION_EVENTS)
// Sets mass for collision response calculations
.insert(AdditionalMassProperties::Mass(BALL_MASS));
}

/// System to clean up the ball when exiting the Playing state.
/// This prevents the ball from persisting in other game states.
/// System that removes the ball entity when exiting the Playing state.
///
/// This cleanup system ensures that:
/// - Ball is properly despawned when leaving gameplay
/// - No ball entities persist in other game states
/// - Memory is properly freed
/// - Game state transitions are clean
///
/// # System Parameters
/// * `commands` - Command buffer for entity manipulation
/// * `ball_query` - Query to find ball entities for cleanup
fn cleanup_ball(mut commands: Commands, ball_query: Query<Entity, With<Ball>>) {
for entity in ball_query.iter() {
commands.entity(entity).despawn();
}
}

/// System that maintains constant ball speed regardless of collisions.
/// System that maintains the ball's velocity within gameplay constraints.
///
/// This system ensures that:
/// - Ball never moves too slowly (maintains minimum speed)
/// - Ball never moves too quickly (caps maximum speed)
/// - Direction is preserved when adjusting speed
/// - Ball maintains consistent gameplay feel
///
/// This is necessary because:
/// 1. Collisions can change the ball's speed
/// 2. We want the game to maintain a consistent pace
/// 3. Numerical errors can accumulate over time
/// The system runs every frame during gameplay to:
/// 1. Check current ball speed
/// 2. Compare against min/max bounds
/// 3. Adjust if necessary while preserving direction
/// 4. Handle edge cases (like zero velocity)
///
/// The system checks the current speed against BALL_SPEED and
/// corrects it if it deviates beyond SPEED_TOLERANCE.
/// # Physics Notes
/// - Uses vector normalization to preserve direction
/// - Handles potential division by zero
/// - Maintains speed constraints for consistent gameplay
fn maintain_ball_velocity(mut query: Query<&mut Velocity, With<Ball>>) {
for mut velocity in query.iter_mut() {
let current_velocity = velocity.linvel;
let current_speed = current_velocity.length();

// Correct speed if it's outside tolerance range
if (current_speed - BALL_SPEED).abs() > SPEED_TOLERANCE {
// Maintain direction but normalize speed
velocity.linvel = current_velocity.normalize() * BALL_SPEED;
// Only adjust non-zero velocities to prevent normalization issues
if current_speed != 0.0 {
// Determine new speed based on constraints
let new_speed = if current_speed.abs() < MIN_VELOCITY {
MIN_VELOCITY // Enforce minimum speed
} else if current_speed.abs() > MAX_VELOCITY {
MAX_VELOCITY // Cap maximum speed
} else {
current_speed // Maintain current speed if within bounds
};

// Apply new speed while preserving direction
velocity.linvel = current_velocity.normalize() * new_speed;
}
}
}

/// Plugin that manages all ball-related systems.
/// Plugin that manages all ball-related systems and behavior.
///
/// This plugin integrates the ball systems into the game by:
/// - Adding cleanup system for state transitions
/// - Adding velocity maintenance system for gameplay
/// - Organizing ball-related functionality
///
/// The plugin ensures proper initialization and cleanup of ball
/// mechanics while maintaining clean integration with the game's
/// state system.
pub struct BallPlugin;

impl Plugin for BallPlugin {
fn build(&self, app: &mut App) {
app
// Clean up ball when leaving Playing state
// Add cleanup system for state transitions
.add_systems(OnExit(GameState::Playing), cleanup_ball)
// Maintain ball velocity during gameplay
.add_systems(
Update,
maintain_ball_velocity.run_if(in_state(GameState::Playing)),
);
// Add velocity maintenance system during gameplay updates
.add_systems(Update, maintain_ball_velocity);
}
}
2 changes: 1 addition & 1 deletion src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const DASH_GAP: f32 = 0.4; // Gap between dashes

/// Physics settings for the walls.
/// Walls are bouncy to create more interesting gameplay.
const WALL_RESTITUTION: f32 = 1.5; // Wall bounciness (>1 means adding energy)
const WALL_RESTITUTION: f32 = 2.0; // Wall bounciness (>1 means adding energy)

/// Creates the black background color resource.
/// This sets the clear color for the game's rendering.
Expand Down
Loading

0 comments on commit 50a1d41

Please sign in to comment.