Fix delta time in the invaders example (#252)
- Minor rewrite of the winit integration using the `game-loop` crate for fixed time-step updates. - Updates are now handled at 240 fps, regardless of frame rate. - Frame rate is capped at 240 fps. - Adds a pause key. - Closes #11
This commit is contained in:
parent
afd15436d6
commit
3968c9748a
|
@ -12,6 +12,7 @@ default = ["optimize"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
env_logger = "0.9"
|
env_logger = "0.9"
|
||||||
|
game-loop = { version = "0.8", features = ["window"] }
|
||||||
getrandom = "0.2"
|
getrandom = "0.2"
|
||||||
gilrs = "0.8"
|
gilrs = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -10,11 +10,21 @@ The pixels have invaded!
|
||||||
cargo run --release --package invaders
|
cargo run --release --package invaders
|
||||||
```
|
```
|
||||||
|
|
||||||
## Controls
|
## Keyboard Controls
|
||||||
|
|
||||||
<kbd>Left</kbd> <kbd>Right</kbd> Move tank
|
<kbd>🡰</kbd> <kbd>🡲</kbd>: Move tank
|
||||||
|
|
||||||
<kbd>space</kbd> Fire cannon
|
<kbd>Space</kbd>: Fire cannon
|
||||||
|
|
||||||
|
<kbd>Pause</kbd> <kbd>P</kbd>: Pause
|
||||||
|
|
||||||
|
## GamePad Controls
|
||||||
|
|
||||||
|
`D-Pad 🡰` `D-Pad 🡲`: Move tank
|
||||||
|
|
||||||
|
`XBox 🅐` `PS 🅧` `Switch 🅑`: Fire cannon
|
||||||
|
|
||||||
|
`XBox/PS ≡` `Switch ⊕︀`: Pause
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
|
|
|
@ -61,8 +61,8 @@ impl Collision {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (x, y) in corners.iter() {
|
for (x, y) in corners.iter() {
|
||||||
let col = (x - left) / GRID.x + invaders.bounds.left_col;
|
let col = x.saturating_sub(left) / GRID.x + invaders.bounds.left_col;
|
||||||
let row = (y - top) / GRID.y + invaders.bounds.top_row;
|
let row = y.saturating_sub(top) / GRID.y + invaders.bounds.top_row;
|
||||||
|
|
||||||
if col < COLS && row < ROWS && invaders.grid[row][col].is_some() {
|
if col < COLS && row < ROWS && invaders.grid[row][col].is_some() {
|
||||||
let detail = BulletDetail::Invader(col, row);
|
let detail = BulletDetail::Invader(col, row);
|
||||||
|
|
|
@ -7,14 +7,13 @@
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crate::collision::Collision;
|
use crate::collision::Collision;
|
||||||
pub use crate::controls::{Controls, Direction};
|
pub use crate::controls::{Controls, Direction};
|
||||||
use crate::geo::Point;
|
use crate::geo::Point;
|
||||||
use crate::loader::{load_assets, Assets};
|
use crate::loader::{load_assets, Assets};
|
||||||
use crate::sprites::{blit, Animation, Drawable, Frame, Sprite, SpriteRef};
|
use crate::sprites::{blit, Animation, Drawable, Frame, Sprite, SpriteRef};
|
||||||
use randomize::PCG32;
|
use randomize::PCG32;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
mod collision;
|
mod collision;
|
||||||
mod controls;
|
mod controls;
|
||||||
|
@ -28,6 +27,12 @@ pub const WIDTH: usize = 224;
|
||||||
/// The screen height is constant (units are in pixels)
|
/// The screen height is constant (units are in pixels)
|
||||||
pub const HEIGHT: usize = 256;
|
pub const HEIGHT: usize = 256;
|
||||||
|
|
||||||
|
// Fixed time step (240 fps)
|
||||||
|
pub const FPS: usize = 240;
|
||||||
|
pub const TIME_STEP: Duration = Duration::from_nanos(1_000_000_000 / FPS as u64);
|
||||||
|
// Internally, the game advances at 60 fps
|
||||||
|
const ONE_FRAME: Duration = Duration::from_nanos(1_000_000_000 / 60);
|
||||||
|
|
||||||
// Invader positioning
|
// Invader positioning
|
||||||
const START: Point = Point::new(24, 64);
|
const START: Point = Point::new(24, 64);
|
||||||
const GRID: Point = Point::new(16, 16);
|
const GRID: Point = Point::new(16, 16);
|
||||||
|
@ -92,7 +97,7 @@ struct Bounds {
|
||||||
struct Player {
|
struct Player {
|
||||||
sprite: SpriteRef,
|
sprite: SpriteRef,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
dt: usize,
|
dt: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shield entity.
|
/// The shield entity.
|
||||||
|
@ -108,7 +113,7 @@ struct Shield {
|
||||||
struct Laser {
|
struct Laser {
|
||||||
sprite: SpriteRef,
|
sprite: SpriteRef,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
dt: usize,
|
dt: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cannon entity.
|
/// The cannon entity.
|
||||||
|
@ -116,7 +121,37 @@ struct Laser {
|
||||||
struct Bullet {
|
struct Bullet {
|
||||||
sprite: SpriteRef,
|
sprite: SpriteRef,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
dt: usize,
|
dt: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait DeltaTime {
|
||||||
|
fn update(&mut self) -> usize;
|
||||||
|
|
||||||
|
fn update_dt(dest_dt: &mut Duration, step: Duration) -> usize {
|
||||||
|
*dest_dt += TIME_STEP;
|
||||||
|
let frames = dest_dt.as_nanos() / step.as_nanos();
|
||||||
|
*dest_dt -= Duration::from_nanos((frames * step.as_nanos()) as u64);
|
||||||
|
|
||||||
|
frames as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaTime for Player {
|
||||||
|
fn update(&mut self) -> usize {
|
||||||
|
Self::update_dt(&mut self.dt, ONE_FRAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaTime for Laser {
|
||||||
|
fn update(&mut self) -> usize {
|
||||||
|
Self::update_dt(&mut self.dt, ONE_FRAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaTime for Bullet {
|
||||||
|
fn update(&mut self) -> usize {
|
||||||
|
Self::update_dt(&mut self.dt, TIME_STEP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
@ -168,7 +203,7 @@ impl World {
|
||||||
let player = Player {
|
let player = Player {
|
||||||
sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)),
|
sprite: SpriteRef::new(&assets, Player1, Duration::from_millis(100)),
|
||||||
pos: PLAYER_START,
|
pos: PLAYER_START,
|
||||||
dt: 0,
|
dt: Duration::default(),
|
||||||
};
|
};
|
||||||
let bullet = None;
|
let bullet = None;
|
||||||
let collision = Collision::default();
|
let collision = Collision::default();
|
||||||
|
@ -200,36 +235,34 @@ impl World {
|
||||||
///
|
///
|
||||||
/// * `dt`: The time delta since last update.
|
/// * `dt`: The time delta since last update.
|
||||||
/// * `controls`: The player inputs.
|
/// * `controls`: The player inputs.
|
||||||
pub fn update(&mut self, dt: &Duration, controls: &Controls) {
|
pub fn update(&mut self, controls: &Controls) {
|
||||||
if self.gameover {
|
if self.gameover {
|
||||||
// TODO: Add a game over screen
|
// TODO: Add a game over screen
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let one_frame = Duration::new(0, 16_666_667);
|
|
||||||
|
|
||||||
// Advance the timer by the delta time
|
// Advance the timer by the delta time
|
||||||
self.dt += *dt;
|
self.dt += TIME_STEP;
|
||||||
|
|
||||||
// Clear the collision details
|
// Clear the collision details
|
||||||
self.collision.clear();
|
self.collision.clear();
|
||||||
|
|
||||||
// Step the invaders one by one
|
// Step the invaders one by one
|
||||||
while self.dt >= one_frame {
|
while self.dt >= ONE_FRAME {
|
||||||
self.dt -= one_frame;
|
self.dt -= ONE_FRAME;
|
||||||
self.step_invaders();
|
self.step_invaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle player movement and animation
|
// Handle player movement and animation
|
||||||
self.step_player(controls, dt);
|
self.step_player(controls);
|
||||||
|
|
||||||
if let Some(bullet) = &mut self.bullet {
|
if let Some(bullet) = &mut self.bullet {
|
||||||
// Handle bullet movement
|
// Handle bullet movement
|
||||||
let velocity = update_dt(&mut bullet.dt, dt) * 4;
|
let velocity = bullet.update();
|
||||||
|
|
||||||
if bullet.pos.y > velocity {
|
if bullet.pos.y > velocity {
|
||||||
bullet.pos.y -= velocity;
|
bullet.pos.y -= velocity;
|
||||||
bullet.sprite.animate(&self.assets, dt);
|
bullet.sprite.animate(&self.assets);
|
||||||
|
|
||||||
// Handle collisions
|
// Handle collisions
|
||||||
if self
|
if self
|
||||||
|
@ -250,11 +283,11 @@ impl World {
|
||||||
// Handle laser movement
|
// Handle laser movement
|
||||||
let mut destroy = Vec::new();
|
let mut destroy = Vec::new();
|
||||||
for (i, laser) in self.lasers.iter_mut().enumerate() {
|
for (i, laser) in self.lasers.iter_mut().enumerate() {
|
||||||
let velocity = update_dt(&mut laser.dt, dt) * 2;
|
let velocity = laser.update() * 2;
|
||||||
|
|
||||||
if laser.pos.y < self.player.pos.y {
|
if laser.pos.y < self.player.pos.y {
|
||||||
laser.pos.y += velocity;
|
laser.pos.y += velocity;
|
||||||
laser.sprite.animate(&self.assets, dt);
|
laser.sprite.animate(&self.assets);
|
||||||
|
|
||||||
// Handle collisions
|
// Handle collisions
|
||||||
if self.collision.laser_to_player(laser, &self.player) {
|
if self.collision.laser_to_player(laser, &self.player) {
|
||||||
|
@ -387,28 +420,28 @@ impl World {
|
||||||
let laser = Laser {
|
let laser = Laser {
|
||||||
sprite: SpriteRef::new(&self.assets, Frame::Laser1, Duration::from_millis(16)),
|
sprite: SpriteRef::new(&self.assets, Frame::Laser1, Duration::from_millis(16)),
|
||||||
pos: invader.pos + LASER_OFFSET,
|
pos: invader.pos + LASER_OFFSET,
|
||||||
dt: 0,
|
dt: Duration::default(),
|
||||||
};
|
};
|
||||||
self.lasers.push(laser);
|
self.lasers.push(laser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step_player(&mut self, controls: &Controls, dt: &Duration) {
|
fn step_player(&mut self, controls: &Controls) {
|
||||||
let frames = update_dt(&mut self.player.dt, dt);
|
let frames = self.player.update();
|
||||||
let width = self.player.sprite.width();
|
let width = self.player.sprite.width();
|
||||||
|
|
||||||
match controls.direction {
|
match controls.direction {
|
||||||
Direction::Left => {
|
Direction::Left => {
|
||||||
if self.player.pos.x > width {
|
if self.player.pos.x > width {
|
||||||
self.player.pos.x -= frames;
|
self.player.pos.x -= frames;
|
||||||
self.player.sprite.animate(&self.assets, dt);
|
self.player.sprite.animate(&self.assets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Direction::Right => {
|
Direction::Right => {
|
||||||
if self.player.pos.x < WIDTH - width * 2 {
|
if self.player.pos.x < WIDTH - width * 2 {
|
||||||
self.player.pos.x += frames;
|
self.player.pos.x += frames;
|
||||||
self.player.sprite.animate(&self.assets, dt);
|
self.player.sprite.animate(&self.assets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -418,7 +451,7 @@ impl World {
|
||||||
self.bullet = Some(Bullet {
|
self.bullet = Some(Bullet {
|
||||||
sprite: SpriteRef::new(&self.assets, Frame::Bullet1, Duration::from_millis(32)),
|
sprite: SpriteRef::new(&self.assets, Frame::Bullet1, Duration::from_millis(32)),
|
||||||
pos: self.player.pos + BULLET_OFFSET,
|
pos: self.player.pos + BULLET_OFFSET,
|
||||||
dt: 0,
|
dt: Duration::default(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -612,11 +645,3 @@ fn next_invader<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_dt(dest_dt: &mut usize, dt: &Duration) -> usize {
|
|
||||||
*dest_dt += dt.subsec_nanos() as usize;
|
|
||||||
let frames = *dest_dt / 16_666_667;
|
|
||||||
*dest_dt -= frames * 16_666_667;
|
|
||||||
|
|
||||||
frames
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
use crate::loader::Assets;
|
||||||
|
use crate::TIME_STEP;
|
||||||
|
use crate::{Point, HEIGHT, WIDTH};
|
||||||
|
use line_drawing::Bresenham;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::loader::Assets;
|
|
||||||
use crate::{Point, HEIGHT, WIDTH};
|
|
||||||
use line_drawing::Bresenham;
|
|
||||||
|
|
||||||
// This is the type stored in the `Assets` hash map
|
// This is the type stored in the `Assets` hash map
|
||||||
pub(crate) type CachedSprite = (usize, usize, Rc<[u8]>);
|
pub(crate) type CachedSprite = (usize, usize, Rc<[u8]>);
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ pub(crate) trait Drawable {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait Animation {
|
pub(crate) trait Animation {
|
||||||
fn animate(&mut self, assets: &Assets, dt: &Duration);
|
fn animate(&mut self, assets: &Assets);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sprite {
|
impl Sprite {
|
||||||
|
@ -172,11 +172,11 @@ impl Drawable for SpriteRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animation for SpriteRef {
|
impl Animation for SpriteRef {
|
||||||
fn animate(&mut self, assets: &Assets, dt: &Duration) {
|
fn animate(&mut self, assets: &Assets) {
|
||||||
if self.duration.subsec_nanos() == 0 {
|
if self.duration.subsec_nanos() == 0 {
|
||||||
self.step_frame(assets);
|
self.step_frame(assets);
|
||||||
} else {
|
} else {
|
||||||
self.dt += *dt;
|
self.dt += TIME_STEP;
|
||||||
|
|
||||||
while self.dt >= self.duration {
|
while self.dt >= self.duration {
|
||||||
self.dt -= self.duration;
|
self.dt -= self.duration;
|
||||||
|
|
|
@ -1,24 +1,117 @@
|
||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use gilrs::{Button, Gilrs};
|
use game_loop::{game_loop, Time, TimeTrait as _};
|
||||||
|
use gilrs::{Button, GamepadId, Gilrs};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use pixels::{Error, Pixels, SurfaceTexture};
|
use pixels::{Error, Pixels, SurfaceTexture};
|
||||||
use simple_invaders::{Controls, Direction, World, HEIGHT, WIDTH};
|
use simple_invaders::{Controls, Direction, World, FPS, HEIGHT, TIME_STEP, WIDTH};
|
||||||
use std::{env, time::Instant};
|
use std::{env, time::Duration};
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
event::{Event, VirtualKeyCode},
|
event::{Event, VirtualKeyCode},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::EventLoop,
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
use winit_input_helper::WinitInputHelper;
|
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,
|
||||||
|
/// State for key edge detection.
|
||||||
|
held: [bool; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
held: [false; 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_controls(&mut self, event: &Event<()>) {
|
||||||
|
// Let winit_input_helper collect events to build its state.
|
||||||
|
self.input.update(event);
|
||||||
|
|
||||||
|
// 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 held = [
|
||||||
|
self.input.key_held(VirtualKeyCode::Pause),
|
||||||
|
self.input.key_held(VirtualKeyCode::P),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut left = self.input.key_held(VirtualKeyCode::Left);
|
||||||
|
let mut right = self.input.key_held(VirtualKeyCode::Right);
|
||||||
|
let mut fire = self.input.key_held(VirtualKeyCode::Space);
|
||||||
|
let mut pause = (held[0] ^ self.held[0] & held[0]) | (held[1] ^ self.held[1] & held[1]);
|
||||||
|
|
||||||
|
self.held = held;
|
||||||
|
|
||||||
|
// 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.is_pressed(Button::South);
|
||||||
|
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> {
|
fn main() -> Result<(), Error> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
let mut input = WinitInputHelper::new();
|
|
||||||
let mut gilrs = Gilrs::new().unwrap();
|
|
||||||
|
|
||||||
// Enable debug mode with `DEBUG=true` environment variable
|
// Enable debug mode with `DEBUG=true` environment variable
|
||||||
let debug = env::var("DEBUG")
|
let debug = env::var("DEBUG")
|
||||||
|
@ -37,95 +130,57 @@ fn main() -> Result<(), Error> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pixels = {
|
let pixels = {
|
||||||
let window_size = window.inner_size();
|
let window_size = window.inner_size();
|
||||||
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
|
||||||
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture)?
|
Pixels::new(WIDTH as u32, HEIGHT as u32, surface_texture)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut invaders = World::new(generate_seed(), debug);
|
let game = Game::new(pixels, debug);
|
||||||
let mut time = Instant::now();
|
|
||||||
let mut gamepad = None;
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
game_loop(
|
||||||
// The one and only event that winit_input_helper doesn't have for us...
|
event_loop,
|
||||||
if let Event::RedrawRequested(_) = event {
|
window,
|
||||||
invaders.draw(pixels.get_frame());
|
game,
|
||||||
if pixels
|
FPS as u32,
|
||||||
.render()
|
0.1,
|
||||||
.map_err(|e| error!("pixels.render() failed: {}", e))
|
move |g| {
|
||||||
.is_err()
|
// Update the world
|
||||||
{
|
if !g.game.paused {
|
||||||
*control_flow = ControlFlow::Exit;
|
g.game.world.update(&g.game.controls);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
move |g| {
|
||||||
|
// Drawing
|
||||||
|
g.game.world.draw(g.game.pixels.get_frame());
|
||||||
|
if let Err(e) = g.game.pixels.render() {
|
||||||
|
error!("pixels.render() failed: {}", e);
|
||||||
|
g.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pump the gilrs event loop and find an active gamepad
|
// Sleep the main thread to limit drawing to the fixed time step.
|
||||||
while let Some(gilrs::Event { id, event, .. }) = gilrs.next_event() {
|
// See: https://github.com/parasyte/pixels/issues/174
|
||||||
let pad = gilrs.gamepad(id);
|
let dt = TIME_STEP.as_secs_f64() - Time::now().sub(&g.current_instant());
|
||||||
if gamepad.is_none() {
|
if dt > 0.0 {
|
||||||
debug!("Gamepad with id {} is connected: {}", id, pad.name());
|
std::thread::sleep(Duration::from_secs_f64(dt));
|
||||||
gamepad = Some(id);
|
|
||||||
} else if event == gilrs::ev::EventType::Disconnected {
|
|
||||||
debug!("Gamepad with id {} is disconnected: {}", id, pad.name());
|
|
||||||
gamepad = None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|g, event| {
|
||||||
|
// Update controls
|
||||||
|
g.game.update_controls(&event);
|
||||||
|
|
||||||
// For everything else, for let winit_input_helper collect events to build its state.
|
|
||||||
// It returns `true` when it is time to update our game state and request a redraw.
|
|
||||||
if input.update(&event) {
|
|
||||||
// Close events
|
// Close events
|
||||||
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
if g.game.input.key_pressed(VirtualKeyCode::Escape) || g.game.input.quit() {
|
||||||
*control_flow = ControlFlow::Exit;
|
g.exit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let controls = {
|
|
||||||
// Keyboard controls
|
|
||||||
let mut left = input.key_held(VirtualKeyCode::Left);
|
|
||||||
let mut right = input.key_held(VirtualKeyCode::Right);
|
|
||||||
let mut fire = input.key_pressed(VirtualKeyCode::Space);
|
|
||||||
|
|
||||||
// Gamepad controls
|
|
||||||
if let Some(id) = gamepad {
|
|
||||||
let gamepad = gilrs.gamepad(id);
|
|
||||||
|
|
||||||
left = left || gamepad.is_pressed(Button::DPadLeft);
|
|
||||||
right = right || gamepad.is_pressed(Button::DPadRight);
|
|
||||||
fire = fire
|
|
||||||
|| gamepad.button_data(Button::South).map_or(false, |button| {
|
|
||||||
button.is_pressed() && button.counter() == gilrs.counter()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let direction = if left {
|
|
||||||
Direction::Left
|
|
||||||
} else if right {
|
|
||||||
Direction::Right
|
|
||||||
} else {
|
|
||||||
Direction::Still
|
|
||||||
};
|
|
||||||
|
|
||||||
Controls { direction, fire }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resize the window
|
// Resize the window
|
||||||
if let Some(size) = input.window_resized() {
|
if let Some(size) = g.game.input.window_resized() {
|
||||||
pixels.resize_surface(size.width, size.height);
|
g.game.pixels.resize_surface(size.width, size.height);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
// Get a new delta time.
|
);
|
||||||
let now = Instant::now();
|
|
||||||
let dt = now.duration_since(time);
|
|
||||||
time = now;
|
|
||||||
|
|
||||||
// Update the game logic and request redraw
|
|
||||||
invaders.update(&dt, &controls);
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a pseudorandom seed for the game's PRNG.
|
/// Generate a pseudorandom seed for the game's PRNG.
|
||||||
|
|
Loading…
Reference in a new issue