pixels/examples/invaders/src/main.rs
Alex 0b380b637d
Allow accessing frame on immutable buffer (#288)
* Allow accessing frame on immutable buffer

* Rename `get_frame` to `get_frame_mut`

* Use `get_frame_mut` in examples
2022-08-14 15:44:23 -07:00

190 lines
6 KiB
Rust

#![deny(clippy::all)]
#![forbid(unsafe_code)]
use game_loop::{game_loop, Time, TimeTrait as _};
use gilrs::{Button, GamepadId, Gilrs};
use log::{debug, error};
use pixels::{Error, Pixels, SurfaceTexture};
use simple_invaders::{Controls, Direction, World, FPS, HEIGHT, TIME_STEP, WIDTH};
use std::{env, time::Duration};
use winit::{
dpi::LogicalSize, event::VirtualKeyCode, event_loop::EventLoop, window::WindowBuilder,
};
use winit_input_helper::WinitInputHelper;
/// Uber-struct representing the entire game.
struct Game {
/// Software renderer.
pixels: Pixels,
/// Invaders world.
world: World,
/// Player controls for world updates.
controls: Controls,
/// Event manager.
input: WinitInputHelper,
/// GamePad manager.
gilrs: Gilrs,
/// GamePad ID for the player.
gamepad: Option<GamepadId>,
/// Game pause state.
paused: bool,
}
impl Game {
fn new(pixels: Pixels, debug: bool) -> Self {
Self {
pixels,
world: World::new(generate_seed(), debug),
controls: Controls::default(),
input: WinitInputHelper::new(),
gilrs: Gilrs::new().unwrap(), // XXX: Don't unwrap.
gamepad: None,
paused: false,
}
}
fn update_controls(&mut self) {
// Pump the gilrs event loop and find an active gamepad
while let Some(gilrs::Event { id, event, .. }) = self.gilrs.next_event() {
let pad = self.gilrs.gamepad(id);
if self.gamepad.is_none() {
debug!("Gamepad with id {} is connected: {}", id, pad.name());
self.gamepad = Some(id);
} else if event == gilrs::ev::EventType::Disconnected {
debug!("Gamepad with id {} is disconnected: {}", id, pad.name());
self.gamepad = None;
}
}
self.controls = {
// Keyboard controls
let mut left = self.input.key_held(VirtualKeyCode::Left);
let mut right = self.input.key_held(VirtualKeyCode::Right);
let mut fire = self.input.key_pressed(VirtualKeyCode::Space);
let mut pause = self.input.key_pressed(VirtualKeyCode::Pause)
| self.input.key_pressed(VirtualKeyCode::P);
// GamePad controls
if let Some(id) = self.gamepad {
let gamepad = self.gilrs.gamepad(id);
left |= gamepad.is_pressed(Button::DPadLeft);
right |= gamepad.is_pressed(Button::DPadRight);
fire |= gamepad.button_data(Button::South).map_or(false, |button| {
button.is_pressed() && button.counter() == self.gilrs.counter()
});
pause |= gamepad.button_data(Button::Start).map_or(false, |button| {
button.is_pressed() && button.counter() == self.gilrs.counter()
});
}
self.gilrs.inc();
if pause {
self.paused = !self.paused;
}
let direction = if left {
Direction::Left
} else if right {
Direction::Right
} else {
Direction::Still
};
Controls { direction, fire }
};
}
}
fn main() -> Result<(), Error> {
env_logger::init();
let event_loop = EventLoop::new();
// Enable debug mode with `DEBUG=true` environment variable
let debug = env::var("DEBUG")
.unwrap_or_else(|_| "false".to_string())
.parse()
.unwrap_or(false);
let window = {
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
let scaled_size = LogicalSize::new(WIDTH as f64 * 3.0, HEIGHT as f64 * 3.0);
WindowBuilder::new()
.with_title("pixel invaders")
.with_inner_size(scaled_size)
.with_min_inner_size(size)
.build(&event_loop)
.unwrap()
};
let pixels = {
let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture)?
};
let game = Game::new(pixels, debug);
game_loop(
event_loop,
window,
game,
FPS as u32,
0.1,
move |g| {
// Update the world
if !g.game.paused {
g.game.world.update(&g.game.controls);
}
},
move |g| {
// Drawing
g.game.world.draw(g.game.pixels.get_frame_mut());
if let Err(e) = g.game.pixels.render() {
error!("pixels.render() failed: {}", e);
g.exit();
}
// Sleep the main thread to limit drawing to the fixed time step.
// See: https://github.com/parasyte/pixels/issues/174
let dt = TIME_STEP.as_secs_f64() - Time::now().sub(&g.current_instant());
if dt > 0.0 {
std::thread::sleep(Duration::from_secs_f64(dt));
}
},
|g, event| {
// Let winit_input_helper collect events to build its state.
if g.game.input.update(event) {
// Update controls
g.game.update_controls();
// Close events
if g.game.input.key_pressed(VirtualKeyCode::Escape) || g.game.input.quit() {
g.exit();
return;
}
// Resize the window
if let Some(size) = g.game.input.window_resized() {
g.game.pixels.resize_surface(size.width, size.height);
}
}
},
);
}
/// Generate a pseudorandom seed for the game's PRNG.
fn generate_seed() -> (u64, u64) {
use byteorder::{ByteOrder, NativeEndian};
use getrandom::getrandom;
let mut seed = [0_u8; 16];
getrandom(&mut seed).expect("failed to getrandom");
(
NativeEndian::read_u64(&seed[0..8]),
NativeEndian::read_u64(&seed[8..16]),
)
}