Add 2D endless runner game with pickups and coyote time
- Platformer endless runner: fixed player x, world scrolls left - Logarithmic speed curve: initial + factor * ln(1 + t / time_scale) - Enemies stomped from above; side/bottom contact kills player - Procedural level generation with StdRng seeded from SystemTime - Object pooling via Vec::retain + frontier-based generator - Coyote time: grace window after leaving platform edge - Data-driven pickup system with trait-based effects (ActiveEffect) - Invulnerability, JumpBoost, ScoreMultiplier — extend via config - Score accumulates with per-effect multiplier; stomp bonuses scaled - Game over screen with score, best score, restart prompt - HUD: score, speed bar, active effect timer bars Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
80
src/player.rs
Normal file
80
src/player.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use crate::config::Config;
|
||||
|
||||
pub struct Player {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub prev_y: f32,
|
||||
pub vy: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub on_ground: bool,
|
||||
pub alive: bool,
|
||||
/// Countdown from `coyote_time` to 0. Positive value = jump still allowed.
|
||||
pub coyote_timer: f32,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(cfg: &Config) -> Self {
|
||||
let y = cfg.start_platform_y - cfg.player_height;
|
||||
Self {
|
||||
x: cfg.player_x,
|
||||
y,
|
||||
prev_y: y,
|
||||
vy: 0.0,
|
||||
width: cfg.player_width,
|
||||
height: cfg.player_height,
|
||||
on_ground: true,
|
||||
alive: true,
|
||||
coyote_timer: cfg.coyote_time,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply gravity and integrate position. Must be called before collision resolution.
|
||||
pub fn update(&mut self, dt: f32, cfg: &Config) {
|
||||
self.prev_y = self.y;
|
||||
|
||||
// Refresh coyote window while grounded; tick it down while airborne.
|
||||
if self.on_ground {
|
||||
self.coyote_timer = cfg.coyote_time;
|
||||
} else {
|
||||
self.coyote_timer = (self.coyote_timer - dt).max(0.0);
|
||||
}
|
||||
|
||||
self.vy += cfg.gravity * dt;
|
||||
self.y += self.vy * dt;
|
||||
|
||||
// Reset ground flag; platform collision will set it back.
|
||||
self.on_ground = false;
|
||||
|
||||
if self.y > cfg.screen_height as f32 + 50.0 {
|
||||
self.alive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Jump if the coyote window is open (on ground OR just left a platform).
|
||||
/// `jump_multiplier` comes from active effects.
|
||||
pub fn jump(&mut self, cfg: &Config, jump_multiplier: f32) {
|
||||
if self.coyote_timer > 0.0 {
|
||||
self.vy = cfg.jump_velocity * jump_multiplier;
|
||||
self.coyote_timer = 0.0; // consume the window
|
||||
self.on_ground = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stomp_bounce(&mut self, cfg: &Config) {
|
||||
self.vy = cfg.stomp_bounce_velocity;
|
||||
self.on_ground = false;
|
||||
}
|
||||
|
||||
pub fn bottom(&self) -> f32 {
|
||||
self.y + self.height
|
||||
}
|
||||
|
||||
pub fn prev_bottom(&self) -> f32 {
|
||||
self.prev_y + self.height
|
||||
}
|
||||
|
||||
pub fn right(&self) -> f32 {
|
||||
self.x + self.width
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user