Files
endless-runner-vibe/src/player.rs
Gabriel Kaszewski 090f5d4a6d 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>
2026-03-06 23:03:09 +01:00

81 lines
2.1 KiB
Rust

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
}
}