diff --git a/examples/invaders/main.rs b/examples/invaders/main.rs index dd506b4..f4859d7 100644 --- a/examples/invaders/main.rs +++ b/examples/invaders/main.rs @@ -1,5 +1,7 @@ +use std::time::Instant; + use pixels::{Error, Pixels, SurfaceTexture}; -use simple_invaders::{World, SCREEN_HEIGHT, SCREEN_WIDTH}; +use simple_invaders::{Controls, Direction, World, SCREEN_HEIGHT, SCREEN_WIDTH}; use winit::event; use winit::event_loop::{ControlFlow, EventLoop}; @@ -26,6 +28,7 @@ fn main() -> Result<(), Error> { let surface_texture = SurfaceTexture::new(width, height, &surface); let mut fb = Pixels::new(224, 256, surface_texture)?; let mut invaders = World::new(); + let mut last = Instant::now(); event_loop.run(move |event, _, control_flow| match event { event::Event::WindowEvent { event, .. } => match event { @@ -43,7 +46,18 @@ fn main() -> Result<(), Error> { _ => (), }, event::Event::EventsCleared => { - invaders.update(); + // Get a new delta time. + let now = Instant::now(); + let dt = now.duration_since(last); + last = now; + + // TODO: Keyboard and controller input. + let controls = Controls { + direction: Direction::Still, + fire: false, + }; + + invaders.update(dt, controls); window.request_redraw(); } _ => (), diff --git a/simple-invaders/src/controls.rs b/simple-invaders/src/controls.rs new file mode 100644 index 0000000..e08268f --- /dev/null +++ b/simple-invaders/src/controls.rs @@ -0,0 +1,17 @@ +/// Player control inputs. +pub struct Controls { + /// Move the player. + pub direction: Direction, + /// Shoot the cannon. + pub fire: bool, +} + +/// The player can only move left or right, but can also be stationary. +pub enum Direction { + /// Do not move the player. + Still, + /// Move to the left. + Left, + /// Move to the right. + Right, +} diff --git a/simple-invaders/src/lib.rs b/simple-invaders/src/lib.rs index 4382ed1..4879585 100644 --- a/simple-invaders/src/lib.rs +++ b/simple-invaders/src/lib.rs @@ -1,9 +1,19 @@ -mod loader; -mod sprites; +//! A simple Space Invaders clone to demonstrate `pixels`. +//! +//! This doesn't use anything fancy like a game engine, so you may not want to build a game like +//! this in practice. That said, the game is fully functional, and it should not be too difficult +//! to understand the code. +use std::time::Duration; + +pub use controls::{Controls, Direction}; use loader::{load_assets, Assets}; use sprites::{blit, Sprite, SpriteRef, Sprites}; +mod controls; +mod loader; +mod sprites; + /// The screen width is constant (units are in pixels) pub const SCREEN_WIDTH: usize = 224; /// The screen height is constant (units are in pixels) @@ -25,6 +35,7 @@ pub struct World { score: u32, assets: Assets, screen: Vec, + timing: Duration, } /// A tiny position vector @@ -132,11 +143,47 @@ impl World { score: 0, assets, screen, + timing: Duration::default(), } } /// Update the internal state. - pub fn update(&mut self) { + /// + /// # Arguments + /// + /// * `dt`: The time delta since last update. + /// * `controls`: The player inputs. + pub fn update(&mut self, dt: Duration, controls: Controls) { + let one_frame = Duration::from_secs_f64(1.0 / 60.0); + + // Advance the timer by the delta time + self.timing += dt; + + // Step the game logic by one frame at a time + while self.timing >= one_frame { + self.timing -= one_frame; + self.step(&controls); + } + } + + /// Draw the internal state to the screen. + pub fn draw(&mut self) -> &[u8] { + // Clear the screen + self.clear(); + + // Draw the invaders + for row in &self.invaders.grid { + for col in row { + if let Some(invader) = col { + blit(&mut self.screen, &invader.pos, &invader.sprite); + } + } + } + + &self.screen + } + + fn step(&mut self, _controls: &Controls) { // Find the next invader let mut invader = None; while let None = invader { @@ -159,23 +206,6 @@ impl World { invader.sprite.update_frame(frame); } - /// Draw the internal state to the screen. - pub fn draw(&mut self) -> &[u8] { - // Clear the screen - self.clear(); - - // Draw the invaders - for row in &self.invaders.grid { - for col in row { - if let Some(invader) = col { - blit(&mut self.screen, &invader.pos, &invader.sprite); - } - } - } - - &self.screen - } - /// Clear the screen fn clear(&mut self) { for (i, byte) in self.screen.iter_mut().enumerate() {