fix: winit + softbuffer for layout-agnostic input and better FPS
Replaced minifb with winit + softbuffer: Input (layout-agnostic): - winit uses PhysicalKey<KeyCode> which maps to physical key positions - KeyCode::KeyA = physical A key, regardless of keyboard layout - Works on Russian, Arabic, any layout — no key mapping needed - HashSet<KeyCode> tracks pressed/released state - Proper Press/Release events from OS, no timeout hacks FPS improvements: - Glyph atlas pre-built at startup (alpha bitmap, zero per-frame alloc) - render_to_buffer: skip empty cells entirely (no draw call) - blend_fast: bitshift instead of division for alpha blending - Buffer fill via .fill() instead of nested loop - copy_from_slice for pixel transfer to softbuffer (memcpy speed) - ControlFlow::Poll for maximum frame rate 109 tests, 0 failures
This commit is contained in:
+3
-1
@@ -8,8 +8,10 @@ crossterm = "0.28"
|
|||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
minifb = "0.27"
|
|
||||||
fontdue = "0.9"
|
fontdue = "0.9"
|
||||||
|
winit = "0.30"
|
||||||
|
softbuffer = "0.4"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|||||||
+118
-62
@@ -202,6 +202,28 @@ fn player_info(game: &Game) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_window_mode() {
|
fn run_window_mode() {
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
use winit::event_loop::{EventLoop, ControlFlow};
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
let event_loop = EventLoop::new().expect("Failed to create event loop");
|
||||||
|
let window = event_loop.create_window(
|
||||||
|
Window::default_attributes()
|
||||||
|
.with_title("Verbatim")
|
||||||
|
.with_inner_size(winit::dpi::LogicalSize::new(160 * 8, 50 * 16))
|
||||||
|
).expect("Failed to create window");
|
||||||
|
let window = Arc::new(window);
|
||||||
|
|
||||||
|
let display_handle = event_loop.owned_display_handle();
|
||||||
|
let context = softbuffer::Context::new(display_handle)
|
||||||
|
.expect("Failed to create softbuffer context");
|
||||||
|
let mut surface = softbuffer::Surface::new(&context, Arc::clone(&window))
|
||||||
|
.expect("Failed to create surface");
|
||||||
|
|
||||||
let mut renderer = WindowRenderer::new();
|
let mut renderer = WindowRenderer::new();
|
||||||
let mut game = Game::new();
|
let mut game = Game::new();
|
||||||
game.init_world();
|
game.init_world();
|
||||||
@@ -210,82 +232,116 @@ fn run_window_mode() {
|
|||||||
let vw = renderer.width();
|
let vw = renderer.width();
|
||||||
let vh = renderer.height();
|
let vh = renderer.height();
|
||||||
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
let fixed_dt = Duration::from_millis(16);
|
let fixed_dt = Duration::from_millis(16);
|
||||||
let mut last_time = Instant::now();
|
let mut last_time = Instant::now();
|
||||||
let mut accumulator = Duration::ZERO;
|
let mut accumulator = Duration::ZERO;
|
||||||
|
|
||||||
while renderer.is_open() && game.running {
|
let mut running = true;
|
||||||
let now = Instant::now();
|
|
||||||
let frame_time = now.duration_since(last_time);
|
|
||||||
last_time = now;
|
|
||||||
accumulator += frame_time;
|
|
||||||
|
|
||||||
while accumulator >= fixed_dt {
|
event_loop.run(|event, ctrl| {
|
||||||
game.fixed_update();
|
ctrl.set_control_flow(ControlFlow::Poll);
|
||||||
accumulator -= fixed_dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
let keys = renderer.get_keys_down();
|
match event {
|
||||||
input.update(&keys);
|
Event::WindowEvent { event, .. } => {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
running = false;
|
||||||
|
ctrl.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { event: key_event, .. } => {
|
||||||
|
input.on_key_event(key_event.physical_key, key_event.state);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::AboutToWait => {
|
||||||
|
if !running {
|
||||||
|
ctrl.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if input.quit {
|
let now = Instant::now();
|
||||||
break;
|
let frame_time = now.duration_since(last_time);
|
||||||
}
|
last_time = now;
|
||||||
|
accumulator += frame_time;
|
||||||
|
|
||||||
if input.jump {
|
let mut steps = 0;
|
||||||
let on_ground = game.check_on_ground();
|
while accumulator >= fixed_dt && steps < 5 {
|
||||||
game.player.jump(&mut game.entities, on_ground);
|
game.fixed_update();
|
||||||
}
|
accumulator -= fixed_dt;
|
||||||
|
steps += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if input.left {
|
input.update();
|
||||||
game.player.move_left(&mut game.entities);
|
|
||||||
} else if input.right {
|
|
||||||
game.player.move_right(&mut game.entities);
|
|
||||||
} else {
|
|
||||||
game.player.stop_horizontal(&mut game.entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.cam_left { game.cam_x -= 3; }
|
if input.quit {
|
||||||
if input.cam_right { game.cam_x += 3; }
|
running = false;
|
||||||
if input.cam_up { game.cam_y -= 3; }
|
ctrl.exit();
|
||||||
if input.cam_down { game.cam_y += 3; }
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(brush_id) = input.paint {
|
if input.jump {
|
||||||
let mat = match brush_id {
|
let on_ground = game.check_on_ground();
|
||||||
1 => MaterialId::Sand,
|
game.player.jump(&mut game.entities, on_ground);
|
||||||
2 => MaterialId::Water,
|
}
|
||||||
3 => MaterialId::Stone,
|
|
||||||
4 => MaterialId::Lava,
|
if input.left {
|
||||||
5 => MaterialId::Wood,
|
game.player.move_left(&mut game.entities);
|
||||||
6 => MaterialId::Acid,
|
} else if input.right {
|
||||||
7 => MaterialId::Grass,
|
game.player.move_right(&mut game.entities);
|
||||||
8 => MaterialId::Dirt,
|
} else {
|
||||||
9 => MaterialId::Fire,
|
game.player.stop_horizontal(&mut game.entities);
|
||||||
0 => MaterialId::Flesh,
|
}
|
||||||
99 => MaterialId::Empty,
|
|
||||||
_ => continue,
|
if input.cam_left { game.cam_x -= 3; }
|
||||||
};
|
if input.cam_right { game.cam_x += 3; }
|
||||||
let cx = game.cam_x + (vw as i32 / 2);
|
if input.cam_up { game.cam_y -= 3; }
|
||||||
let cy = game.cam_y + (vh as i32 / 2);
|
if input.cam_down { game.cam_y += 3; }
|
||||||
let r = 2;
|
|
||||||
for dy in -r..=r {
|
if let Some(brush_id) = input.paint {
|
||||||
for dx in -r..=r {
|
let mat = match brush_id {
|
||||||
if dx * dx + dy * dy <= r * r + 1 {
|
1 => MaterialId::Sand,
|
||||||
if mat == MaterialId::Empty {
|
2 => MaterialId::Water,
|
||||||
game.grid.set(cx + dx, cy + dy, verbatim::world::cell::Cell::empty());
|
3 => MaterialId::Stone,
|
||||||
} else {
|
4 => MaterialId::Lava,
|
||||||
game.grid.set_material(cx + dx, cy + dy, mat);
|
5 => MaterialId::Wood,
|
||||||
|
6 => MaterialId::Acid,
|
||||||
|
7 => MaterialId::Grass,
|
||||||
|
8 => MaterialId::Dirt,
|
||||||
|
9 => MaterialId::Fire,
|
||||||
|
0 => MaterialId::Flesh,
|
||||||
|
99 => MaterialId::Empty,
|
||||||
|
_ => MaterialId::Empty,
|
||||||
|
};
|
||||||
|
let cx = game.cam_x + (vw as i32 / 2);
|
||||||
|
let cy = game.cam_y + (vh as i32 / 2);
|
||||||
|
let r = 2;
|
||||||
|
for dy in -r..=r {
|
||||||
|
for dx in -r..=r {
|
||||||
|
if dx * dx + dy * dy <= r * r + 1 {
|
||||||
|
if mat == MaterialId::Empty {
|
||||||
|
game.grid.set(cx + dx, cy + dy, verbatim::world::cell::Cell::empty());
|
||||||
|
} else {
|
||||||
|
game.grid.set_material(cx + dx, cy + dy, mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (px, py) = game.player.center(&game.entities);
|
||||||
|
game.cam_x = px as i32 - (vw as i32 / 2);
|
||||||
|
game.cam_y = py as i32 - (vh as i32 / 2);
|
||||||
|
|
||||||
|
renderer.render_to_buffer(&game.grid, &game.entities, game.cam_x, game.cam_y);
|
||||||
|
|
||||||
|
let mut buffer = surface.buffer_mut().expect("buffer");
|
||||||
|
let pixels = renderer.pixels();
|
||||||
|
let len = buffer.len().min(pixels.len());
|
||||||
|
buffer[..len].copy_from_slice(&pixels[..len]);
|
||||||
|
buffer.present().expect("present");
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}).expect("event loop error");
|
||||||
let (px, py) = game.player.center(&game.entities);
|
|
||||||
game.cam_x = px as i32 - (vw as i32 / 2);
|
|
||||||
game.cam_y = py as i32 - (vh as i32 / 2);
|
|
||||||
|
|
||||||
renderer.render(&game.grid, &game.entities, game.cam_x, game.cam_y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-92
@@ -1,4 +1,3 @@
|
|||||||
use minifb::{Key, Window, WindowOptions};
|
|
||||||
use fontdue::{Font, FontSettings};
|
use fontdue::{Font, FontSettings};
|
||||||
use crate::entity::{EntityManager, EntityKind};
|
use crate::entity::{EntityManager, EntityKind};
|
||||||
use crate::world::cell::MaterialId;
|
use crate::world::cell::MaterialId;
|
||||||
@@ -13,12 +12,12 @@ const ATLAS_W: usize = ATLAS_COLS * CHAR_W;
|
|||||||
const ATLAS_H: usize = ATLAS_ROWS * CHAR_H;
|
const ATLAS_H: usize = ATLAS_ROWS * CHAR_H;
|
||||||
|
|
||||||
pub struct WindowRenderer {
|
pub struct WindowRenderer {
|
||||||
window: Window,
|
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
pixels: Vec<u32>,
|
pixels: Vec<u32>,
|
||||||
atlas: Vec<u8>,
|
atlas: Vec<u8>,
|
||||||
atlas_map: std::collections::HashMap<char, (usize, usize)>,
|
atlas_map: std::collections::HashMap<char, (usize, usize)>,
|
||||||
|
font: Font,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowRenderer {
|
impl WindowRenderer {
|
||||||
@@ -33,21 +32,10 @@ impl WindowRenderer {
|
|||||||
let width = 160;
|
let width = 160;
|
||||||
let height = 50;
|
let height = 50;
|
||||||
|
|
||||||
let mut window = Window::new(
|
|
||||||
"Verbatim",
|
|
||||||
width * CHAR_W,
|
|
||||||
height * CHAR_H,
|
|
||||||
WindowOptions {
|
|
||||||
resize: true,
|
|
||||||
..WindowOptions::default()
|
|
||||||
},
|
|
||||||
).expect("Failed to create window");
|
|
||||||
window.set_target_fps(60);
|
|
||||||
|
|
||||||
let mut atlas = vec![0u8; ATLAS_W * ATLAS_H];
|
let mut atlas = vec![0u8; ATLAS_W * ATLAS_H];
|
||||||
let mut atlas_map = std::collections::HashMap::new();
|
let mut atlas_map = std::collections::HashMap::new();
|
||||||
|
|
||||||
let chars: Vec<char> = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".chars().collect();
|
let chars: Vec<char> = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?".chars().collect();
|
||||||
|
|
||||||
for (i, &ch) in chars.iter().enumerate() {
|
for (i, &ch) in chars.iter().enumerate() {
|
||||||
let col = i % ATLAS_COLS;
|
let col = i % ATLAS_COLS;
|
||||||
@@ -75,71 +63,41 @@ impl WindowRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
window,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
pixels: vec![0u32; width * CHAR_W * height * CHAR_H],
|
pixels: vec![0x000A0A0F; width * CHAR_W * height * CHAR_H],
|
||||||
atlas,
|
atlas,
|
||||||
atlas_map,
|
atlas_map,
|
||||||
|
font,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
fn blend(fg: [u8; 3], bg: [u8; 3], alpha: u8) -> u32 {
|
fn blend_fast(fg: [u8; 3], bg: u32, alpha: u8) -> u32 {
|
||||||
if alpha == 0 {
|
if alpha == 0 { return bg; }
|
||||||
return ((bg[0] as u32) << 16) | ((bg[1] as u32) << 8) | (bg[2] as u32);
|
|
||||||
}
|
|
||||||
if alpha == 255 {
|
if alpha == 255 {
|
||||||
return ((fg[0] as u32) << 16) | ((fg[1] as u32) << 8) | (fg[2] as u32);
|
return ((fg[0] as u32) << 16) | ((fg[1] as u32) << 8) | (fg[2] as u32);
|
||||||
}
|
}
|
||||||
let a = alpha as u32;
|
let a = alpha as u32;
|
||||||
let inv = 255 - a;
|
let inv = 255 - a;
|
||||||
let r = (fg[0] as u32 * a + bg[0] as u32 * inv) / 255;
|
let br = (bg >> 16) & 0xFF;
|
||||||
let g = (fg[1] as u32 * a + bg[1] as u32 * inv) / 255;
|
let bg_ = (bg >> 8) & 0xFF;
|
||||||
let b = (fg[2] as u32 * a + bg[2] as u32 * inv) / 255;
|
let bb = bg & 0xFF;
|
||||||
|
let r = (fg[0] as u32 * a + br * inv) >> 8;
|
||||||
|
let g = (fg[1] as u32 * a + bg_ * inv) >> 8;
|
||||||
|
let b = (fg[2] as u32 * a + bb * inv) >> 8;
|
||||||
(r << 16) | (g << 8) | b
|
(r << 16) | (g << 8) | b
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn render_to_buffer(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
|
||||||
fn draw_cell(&mut self, col: usize, row: usize, ch: char, fg: [u8; 3], bg: [u8; 3]) {
|
|
||||||
let (ac, ar) = match self.atlas_map.get(&ch) {
|
|
||||||
Some(&(c, r)) => (c, r),
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let base_x = col * CHAR_W;
|
|
||||||
let base_y = row * CHAR_H;
|
|
||||||
let screen_w = self.width * CHAR_W;
|
|
||||||
|
|
||||||
for y in 0..CHAR_H {
|
|
||||||
let ay = ar * CHAR_H + y;
|
|
||||||
let py = base_y + y;
|
|
||||||
if py >= self.height * CHAR_H { break; }
|
|
||||||
|
|
||||||
let atlas_row = &self.atlas[ay * ATLAS_W + ac * CHAR_W..ay * ATLAS_W + ac * CHAR_W + CHAR_W];
|
|
||||||
let pixel_row = &mut self.pixels[py * screen_w + base_x..py * screen_w + base_x + CHAR_W.min(screen_w - base_x)];
|
|
||||||
|
|
||||||
for x in 0..pixel_row.len() {
|
|
||||||
pixel_row[x] = Self::blend(fg, bg, atlas_row[x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(&mut self, grid: &Grid, entities: &EntityManager, cam_x: i32, cam_y: i32) {
|
|
||||||
let reg = MaterialRegistry::instance();
|
let reg = MaterialRegistry::instance();
|
||||||
let screen_w = self.width * CHAR_W;
|
let screen_w = self.width * CHAR_W;
|
||||||
let screen_h = self.height * CHAR_H;
|
let bg_default = 0x000A0A0F;
|
||||||
|
|
||||||
for py in (0..screen_h).step_by(CHAR_H) {
|
// Fast clear: fill with background
|
||||||
for px in (0..screen_w).step_by(CHAR_W) {
|
self.pixels.fill(bg_default);
|
||||||
let base = py * screen_w + px;
|
|
||||||
let end = base + CHAR_W.min(screen_w - px);
|
|
||||||
for p in base..end {
|
|
||||||
self.pixels[p] = 0x000A0A0F;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Build entity overlay
|
||||||
let mut entity_map: std::collections::HashMap<(i32, i32), (char, [u8; 3])> = std::collections::HashMap::new();
|
let mut entity_map: std::collections::HashMap<(i32, i32), (char, [u8; 3])> = std::collections::HashMap::new();
|
||||||
for e in entities.all() {
|
for e in entities.all() {
|
||||||
for b in &e.bodies {
|
for b in &e.bodies {
|
||||||
@@ -168,49 +126,69 @@ impl WindowRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw cells
|
||||||
for dy in 0..self.height {
|
for dy in 0..self.height {
|
||||||
|
let wy = cam_y + dy as i32;
|
||||||
for dx in 0..self.width {
|
for dx in 0..self.width {
|
||||||
let wx = cam_x + dx as i32;
|
let wx = cam_x + dx as i32;
|
||||||
let wy = cam_y + dy as i32;
|
|
||||||
|
|
||||||
if let Some(&(ch, fg)) = entity_map.get(&(dx as i32, dy as i32)) {
|
let (ch, fg, bg) = if let Some(&(ec, ef)) = entity_map.get(&(dx as i32, dy as i32)) {
|
||||||
self.draw_cell(dx, dy, ch, fg, [10, 10, 15]);
|
(ec, ef, bg_default)
|
||||||
continue;
|
} else if !grid.in_bounds(wx, wy) {
|
||||||
}
|
('?', [80, 80, 80], bg_default)
|
||||||
|
|
||||||
if !grid.in_bounds(wx, wy) {
|
|
||||||
self.draw_cell(dx, dy, '?', [80, 80, 80], [10, 10, 15]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cell = grid.get(wx, wy);
|
|
||||||
let mat = reg.get(cell.material);
|
|
||||||
if cell.is_empty() {
|
|
||||||
self.draw_cell(dx, dy, ' ', [10, 10, 15], [10, 10, 15]);
|
|
||||||
} else {
|
} else {
|
||||||
let fg = if cell.material == MaterialId::Lava {
|
let cell = grid.get(wx, wy);
|
||||||
let r = 200u8.saturating_add(cell.variant / 2);
|
let mat = reg.get(cell.material);
|
||||||
[r, 60, 20]
|
if cell.is_empty() {
|
||||||
|
(' ', [10, 10, 15], bg_default)
|
||||||
} else {
|
} else {
|
||||||
[mat.color_fg.0, mat.color_fg.1, mat.color_fg.2]
|
let fg = if cell.material == MaterialId::Lava {
|
||||||
};
|
let r = 200u8.saturating_add(cell.variant / 2);
|
||||||
let bg = [mat.color_bg.0, mat.color_bg.1, mat.color_bg.2];
|
[r, 60, 20]
|
||||||
self.draw_cell(dx, dy, mat.display_char, fg, bg);
|
} else {
|
||||||
|
[mat.color_fg.0, mat.color_fg.1, mat.color_fg.2]
|
||||||
|
};
|
||||||
|
let bg = ((mat.color_bg.0 as u32) << 16) | ((mat.color_bg.1 as u32) << 8) | (mat.color_bg.2 as u32);
|
||||||
|
(mat.display_char, fg, bg)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip drawing if it's a space on default background
|
||||||
|
if ch == ' ' && bg == bg_default {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ac, ar) = match self.atlas_map.get(&ch) {
|
||||||
|
Some(&(c, r)) => (c, r),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let base_x = dx * CHAR_W;
|
||||||
|
let base_y = dy * CHAR_H;
|
||||||
|
|
||||||
|
for y in 0..CHAR_H {
|
||||||
|
let ay = ar * CHAR_H + y;
|
||||||
|
let py = base_y + y;
|
||||||
|
let atlas_off = ay * ATLAS_W + ac * CHAR_W;
|
||||||
|
let pix_off = py * screen_w + base_x;
|
||||||
|
|
||||||
|
for x in 0..CHAR_W {
|
||||||
|
let alpha = self.atlas[atlas_off + x];
|
||||||
|
if alpha > 0 {
|
||||||
|
self.pixels[pix_off + x] = Self::blend_fast(fg, bg, alpha);
|
||||||
|
} else {
|
||||||
|
self.pixels[pix_off + x] = bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.window.update_with_buffer(&self.pixels, screen_w, screen_h);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_open(&self) -> bool {
|
|
||||||
self.window.is_open()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_keys_down(&self) -> Vec<Key> {
|
|
||||||
self.window.get_keys()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> usize { self.width }
|
pub fn width(&self) -> usize { self.width }
|
||||||
pub fn height(&self) -> usize { self.height }
|
pub fn height(&self) -> usize { self.height }
|
||||||
|
pub fn pixels(&self) -> &[u32] { &self.pixels }
|
||||||
|
pub fn pixel_w(&self) -> usize { self.width * CHAR_W }
|
||||||
|
pub fn pixel_h(&self) -> usize { self.height * CHAR_H }
|
||||||
|
pub fn font(&self) -> &Font { &self.font }
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-21
@@ -1,4 +1,5 @@
|
|||||||
use minifb::Key;
|
use std::collections::HashSet;
|
||||||
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
|
|
||||||
pub struct WindowInput {
|
pub struct WindowInput {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
@@ -11,6 +12,7 @@ pub struct WindowInput {
|
|||||||
pub quit: bool,
|
pub quit: bool,
|
||||||
pub paint: Option<u8>,
|
pub paint: Option<u8>,
|
||||||
jump_was_down: bool,
|
jump_was_down: bool,
|
||||||
|
down_keys: HashSet<KeyCode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowInput {
|
impl WindowInput {
|
||||||
@@ -20,13 +22,32 @@ impl WindowInput {
|
|||||||
cam_left: false, cam_right: false, cam_up: false, cam_down: false,
|
cam_left: false, cam_right: false, cam_up: false, cam_down: false,
|
||||||
quit: false, paint: None,
|
quit: false, paint: None,
|
||||||
jump_was_down: false,
|
jump_was_down: false,
|
||||||
|
down_keys: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, keys: &[Key]) {
|
pub fn on_key_event(&mut self, key: PhysicalKey, state: winit::event::ElementState) {
|
||||||
let now_left = keys.contains(&Key::A) || keys.contains(&Key::Left);
|
let code = match key {
|
||||||
let now_right = keys.contains(&Key::D) || keys.contains(&Key::Right);
|
PhysicalKey::Code(c) => c,
|
||||||
let now_jump = keys.contains(&Key::W) || keys.contains(&Key::Space) || keys.contains(&Key::Up);
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
match state {
|
||||||
|
winit::event::ElementState::Pressed => {
|
||||||
|
self.down_keys.insert(code);
|
||||||
|
}
|
||||||
|
winit::event::ElementState::Released => {
|
||||||
|
self.down_keys.remove(&code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
let keys = &self.down_keys;
|
||||||
|
|
||||||
|
let now_left = keys.contains(&KeyCode::KeyA) || keys.contains(&KeyCode::ArrowLeft);
|
||||||
|
let now_right = keys.contains(&KeyCode::KeyD) || keys.contains(&KeyCode::ArrowRight);
|
||||||
|
let now_jump = keys.contains(&KeyCode::KeyW) || keys.contains(&KeyCode::Space) || keys.contains(&KeyCode::ArrowUp);
|
||||||
|
|
||||||
self.left = now_left && !now_right;
|
self.left = now_left && !now_right;
|
||||||
self.right = now_right && !now_left;
|
self.right = now_right && !now_left;
|
||||||
@@ -34,23 +55,30 @@ impl WindowInput {
|
|||||||
self.jump = now_jump && !self.jump_was_down;
|
self.jump = now_jump && !self.jump_was_down;
|
||||||
self.jump_was_down = now_jump;
|
self.jump_was_down = now_jump;
|
||||||
|
|
||||||
self.cam_left = keys.contains(&Key::H);
|
self.cam_left = keys.contains(&KeyCode::KeyH);
|
||||||
self.cam_right = keys.contains(&Key::L);
|
self.cam_right = keys.contains(&KeyCode::KeyL);
|
||||||
self.cam_up = keys.contains(&Key::K);
|
self.cam_up = keys.contains(&KeyCode::KeyK);
|
||||||
self.cam_down = keys.contains(&Key::J);
|
self.cam_down = keys.contains(&KeyCode::KeyJ);
|
||||||
self.quit = keys.contains(&Key::Q) || keys.contains(&Key::Escape);
|
self.quit = keys.contains(&KeyCode::KeyQ) || keys.contains(&KeyCode::Escape);
|
||||||
|
|
||||||
self.paint = None;
|
self.paint = None;
|
||||||
if keys.contains(&Key::Key1) { self.paint = Some(1); }
|
if keys.contains(&KeyCode::Digit1) { self.paint = Some(1); }
|
||||||
else if keys.contains(&Key::Key2) { self.paint = Some(2); }
|
else if keys.contains(&KeyCode::Digit2) { self.paint = Some(2); }
|
||||||
else if keys.contains(&Key::Key3) { self.paint = Some(3); }
|
else if keys.contains(&KeyCode::Digit3) { self.paint = Some(3); }
|
||||||
else if keys.contains(&Key::Key4) { self.paint = Some(4); }
|
else if keys.contains(&KeyCode::Digit4) { self.paint = Some(4); }
|
||||||
else if keys.contains(&Key::Key5) { self.paint = Some(5); }
|
else if keys.contains(&KeyCode::Digit5) { self.paint = Some(5); }
|
||||||
else if keys.contains(&Key::Key6) { self.paint = Some(6); }
|
else if keys.contains(&KeyCode::Digit6) { self.paint = Some(6); }
|
||||||
else if keys.contains(&Key::Key7) { self.paint = Some(7); }
|
else if keys.contains(&KeyCode::Digit7) { self.paint = Some(7); }
|
||||||
else if keys.contains(&Key::Key8) { self.paint = Some(8); }
|
else if keys.contains(&KeyCode::Digit8) { self.paint = Some(8); }
|
||||||
else if keys.contains(&Key::Key9) { self.paint = Some(9); }
|
else if keys.contains(&KeyCode::Digit9) { self.paint = Some(9); }
|
||||||
else if keys.contains(&Key::Key0) { self.paint = Some(0); }
|
else if keys.contains(&KeyCode::Digit0) { self.paint = Some(0); }
|
||||||
else if keys.contains(&Key::X) { self.paint = Some(99); }
|
else if keys.contains(&KeyCode::KeyX) { self.paint = Some(99); }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.down_keys.clear();
|
||||||
|
self.left = false;
|
||||||
|
self.right = false;
|
||||||
|
self.jump = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user