feat: square cells (16x16), slope stepping collision
Square cells: - CHAR_W and CHAR_H both 16 (was 8x16, non-square) - Window size updated to 160*16 x 50*16 = 2560x800 - Applies to both ascii and graphics renderers Slope stepping collision: - Before resolving X, check if new position overlaps solid - If overlap, try stepping up 1 cell — if clear, snap up (walk up slope) - If can't step up, resolve X normally (wall block) - aabb_overlaps_solid() helper for fast overlap check - Player can now walk up 1-cell-high steps and slopes 109 tests, 0 failures
This commit is contained in:
+45
-15
@@ -354,9 +354,7 @@ impl Game {
|
||||
|
||||
let mut nx = cx;
|
||||
let mut ny = cy;
|
||||
// Horizontal: direct velocity, no damping (vector movement)
|
||||
let mut nvx = cvx;
|
||||
// Vertical: gravity + light damping for natural fall
|
||||
let mut nvy = cvy * 0.99;
|
||||
nvy += gravity;
|
||||
|
||||
@@ -366,25 +364,28 @@ impl Game {
|
||||
nvy = nvy / v_mag * max_vel;
|
||||
}
|
||||
|
||||
// Move X then resolve X collisions
|
||||
// Step 1: Try horizontal movement with slope stepping
|
||||
nx += nvx;
|
||||
let (resolved_x, hit_wall_x) = self.resolve_aabb_x(idx, nx, ny, half_w, half_h, nvx);
|
||||
nx = resolved_x;
|
||||
if hit_wall_x {
|
||||
if nvx > 0.0 { nvx = 0.0; }
|
||||
else if nvx < 0.0 { nvx = 0.0; }
|
||||
if self.aabb_overlaps_solid(nx, ny, half_w, half_h) {
|
||||
// Try stepping up 1 cell
|
||||
let step = 1.0;
|
||||
if !self.aabb_overlaps_solid(nx, ny - step, half_w, half_h) {
|
||||
// Can step up — snap to top of the obstacle
|
||||
ny -= step;
|
||||
} else {
|
||||
// Blocked — resolve X
|
||||
let (resolved_x, hit) = self.resolve_aabb_x(idx, nx, ny, half_w, half_h, nvx);
|
||||
nx = resolved_x;
|
||||
if hit { nvx = 0.0; }
|
||||
}
|
||||
}
|
||||
|
||||
// Move Y then resolve Y collisions
|
||||
// Step 2: Vertical movement
|
||||
ny += nvy;
|
||||
let (resolved_y, hit_floor, hit_ceiling) = self.resolve_aabb_y(idx, nx, ny, half_w, half_h, nvy > 0.0);
|
||||
ny = resolved_y;
|
||||
if hit_floor {
|
||||
nvy = 0.0;
|
||||
}
|
||||
if hit_ceiling {
|
||||
nvy = 0.0;
|
||||
}
|
||||
if hit_floor { nvy = 0.0; }
|
||||
if hit_ceiling { nvy = 0.0; }
|
||||
|
||||
// Check material contacts
|
||||
let (touching_lava, touching_fire, touching_acid, in_liquid) = {
|
||||
@@ -445,6 +446,35 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn aabb_overlaps_solid(&self, cx: f32, cy: f32, hw: f32, hh: f32) -> bool {
|
||||
let grid = &self.grid;
|
||||
let left = cx - hw;
|
||||
let right = cx + hw;
|
||||
let top = cy - hh;
|
||||
let bottom = cy + hh;
|
||||
|
||||
let min_x = left.floor() as i32;
|
||||
let max_x = right.ceil() as i32;
|
||||
let min_y = top.floor() as i32;
|
||||
let max_y = bottom.ceil() as i32;
|
||||
|
||||
for y in min_y..=max_y {
|
||||
for x in min_x..=max_x {
|
||||
if !grid.in_bounds(x, y) { continue; }
|
||||
let cell = grid.get(x, y);
|
||||
if !cell.is_solid() { continue; }
|
||||
let cl = x as f32;
|
||||
let cr = (x + 1) as f32;
|
||||
let ct = y as f32;
|
||||
let cb = (y + 1) as f32;
|
||||
if right > cl && left < cr && bottom > ct && top < cb {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn resolve_aabb_x(&self, _idx: usize, cx: f32, cy: f32, hw: f32, hh: f32, vx: f32) -> (f32, bool) {
|
||||
let grid = &self.grid;
|
||||
let left = cx - hw;
|
||||
|
||||
+2
-2
@@ -216,7 +216,7 @@ fn run_ascii_mode() {
|
||||
let window = event_loop.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("Verbatim — ASCII")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(160 * 16, 50 * 16))
|
||||
).expect("Failed to create window");
|
||||
let window = Arc::new(window);
|
||||
|
||||
@@ -358,7 +358,7 @@ fn run_graphics_mode() {
|
||||
let window = event_loop.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("Verbatim — Graphics")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(160 * 16, 50 * 16))
|
||||
).expect("Failed to create window");
|
||||
let window = Arc::new(window);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
use crate::world::material::MaterialRegistry;
|
||||
|
||||
const CHAR_W: u32 = 8;
|
||||
const CHAR_W: u32 = 16;
|
||||
const CHAR_H: u32 = 16;
|
||||
const MAX_FRAMES: usize = 2;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::world::cell::MaterialId;
|
||||
use crate::world::grid::Grid;
|
||||
use crate::world::material::MaterialRegistry;
|
||||
|
||||
const CHAR_W: u32 = 8;
|
||||
const CHAR_W: u32 = 16;
|
||||
const CHAR_H: u32 = 16;
|
||||
const ATLAS_COLS: usize = 16;
|
||||
const ATLAS_ROWS: usize = 16;
|
||||
|
||||
Reference in New Issue
Block a user