diff --git a/assets/textures/mario.png b/assets/textures/mario.png new file mode 100644 index 0000000..296341a Binary files /dev/null and b/assets/textures/mario.png differ diff --git a/src/engine/character/mod.rs b/src/engine/character/mod.rs index a190839..03f952c 100644 --- a/src/engine/character/mod.rs +++ b/src/engine/character/mod.rs @@ -1,82 +1,199 @@ -use std::time::Duration; +use std::time::{ + Instant, + Duration, +}; use sfml::system::Vector2; use sfml::window::Event; -use sfml::graphics::{ - Drawable, - RenderStates, - RenderTarget, - Sprite, - Color, -}; +use sfml::graphics::IntRect; +use sfml::window::Key; use engine::scene::Updatable; use engine::controls::Controls; +use engine::renderer::Drawable; +use engine::texture::Texture; +use engine::physics; +use engine::math::{ + duration_as_f32, + duration_as_frame, +}; + +/// The different sides a character can face. +pub enum Side { + /// The character looks to the left. + Left, + + /// The character looks to the right. + Right, +} + +impl Side { + /// Returns the side corresponding to the force. + /// + /// Returns None if the force is null. + pub fn from_force(force: Vector2) -> Option { + if force.x > 0.0 { + Some(Side::Right) + } else if force.x < 0.0 { + Some(Side::Left) + } else { + None + } + } + + /// Returns the offset in the texture. + pub fn offset(&self) -> i32 { + match *self { + Side::Left => 0, + Side::Right => 32, + } + } +} /// A character, enemy or controllable. pub struct Character { /// The position of the character. - position: Vector2, + pub position: Vector2, /// The speed of the character. - speed: Vector2, + pub speed: Vector2, /// If the player is controlling a character. controls: Option, + + /// The side of the character. + side: Side, + + /// The counter of jumps. + /// + /// When it's 0, the character can no longer jump. + jump_counter: usize, + + /// The maximum number of jumps a character can do. + /// + /// It's reset when the character hits the ground. + max_jump: usize, + + /// The timer of the character's animation. + animation_timer: Option, } impl Character { - /// Creates a character in (0, 0). - pub fn new() -> Character { + + fn generic(controls: Option) -> Character { Character { position: Vector2::new(0.0, 0.0), speed: Vector2::new(0.0, 0.0), - controls: None, + controls: controls, + side: Side::Right, + jump_counter: 1, + max_jump: 1, + animation_timer: None, } } + /// Creates a character in (0, 0). + pub fn new() -> Character { + Character::generic(None) + } + /// Creates a character with the specified controls. pub fn with_controls(controls: Controls) -> Character { - Character { - position: Vector2::new(0.0, 0.0), - speed: Vector2::new(0.0, 0.0), - controls: Some(controls), - } + Character::generic(Some(controls)) } /// Sets the position of the character. pub fn set_position>>(&mut self, position: S) { self.position = position.into(); } + + /// Makes the character jump. + pub fn jump(&mut self) { + if self.jump_counter > 0 { + self.jump_counter -= 1; + self.speed.y = physics::JUMP_SPEED.y; + } + } + + /// Resets the jump counter. + pub fn ground_collision(&mut self) { + self.jump_counter = self.max_jump; + } + } impl Updatable for Character { fn update(&mut self, duration: &Duration) { + let mut force: Vector2 = Vector2::new(0.0, 0.0); + + // Manage the input of the player + if Key::Left.is_pressed() { + force.x -= 1.0; + } + + if Key::Right.is_pressed() { + force.x += 1.0; + } + + if let Some(side) = Side::from_force(force) { + + self.side = side; + + if self.animation_timer.is_none() { + self.animation_timer = Some(Instant::now()); + } + + } else { + + self.animation_timer = None; + + } + + + let duration = duration_as_f32(duration); + + // Compute acceleration + let accel = physics::G; + + // Compute speed + self.speed.x *= physics::GROUND_FRICTION.x; + self.speed += accel * duration + force * 64.0; + + // Compute position + self.position += self.speed * duration; + } fn manage_event(&mut self, event: &Event) { + match event { + Event::KeyPressed { + code: Key::Return, .. + } => self.jump(), + + _ => (), + } } } impl Drawable for Character { - fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>( - &'a self, - target: &mut RenderTarget, - states: RenderStates<'texture, 'shader, 'shader_texture> - ) { + fn texture(&self) -> Texture { + Texture::Mario + } - use sfml::graphics::Transformable; - let mut sprite = Sprite::new(); - sprite.set_origin((32.0, 0.0)); - sprite.set_position(self.position); - sprite.set_color(&if self.controls.is_some() { - Color::rgb(255, 0, 0) + fn texture_rect(&self) -> IntRect { + let frame = if let Some(started) = self.animation_timer { + duration_as_frame(&Instant::now().duration_since(started), 2) } else { - Color::rgb(255, 255, 255) - }); - sprite.draw(target, states); + 0 + }; + + IntRect::new(self.side.offset(), frame * 32, 32, 32) + } + + fn position(&self) -> Vector2 { + self.position } } diff --git a/src/engine/math/mod.rs b/src/engine/math/mod.rs index 4f233c9..b8ff2a3 100644 --- a/src/engine/math/mod.rs +++ b/src/engine/math/mod.rs @@ -1,8 +1,22 @@ +use std::time::Duration; + use std::ops::{ Index, IndexMut }; +/// Converts a duration into an animation frame number. +pub fn duration_as_frame(duration: &Duration, total: usize) -> i32 { + let secs = duration_as_f32(duration); + + (secs * 4.0) as i32 % total as i32 +} + +/// Converts a duration into its number of seconds. +pub fn duration_as_f32(duration: &Duration) -> f32 { + duration.as_secs() as f32 + duration.subsec_nanos() as f32 / 1_000_000_000.0 +} + /// A generic matrix type, useful for levels. pub struct Matrix { diff --git a/src/engine/mod.rs b/src/engine/mod.rs index fe62f62..bd3be80 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -13,3 +13,9 @@ pub mod texture; /// This module contains the math tools that will be used. pub mod math; + +/// This module contains our wrapper of SFML window. +pub mod renderer; + +/// This module contains everything related to physics. +pub mod physics; diff --git a/src/engine/physics/mod.rs b/src/engine/physics/mod.rs new file mode 100644 index 0000000..a633965 --- /dev/null +++ b/src/engine/physics/mod.rs @@ -0,0 +1,10 @@ +use sfml::system::Vector2; + +/// The gravity force. +pub const G: Vector2 = Vector2 { x: 0.0, y: 50.0 * 32.0 }; + +/// The friction with the ground. +pub const GROUND_FRICTION: Vector2 = Vector2 { x: 0.5, y: 1.0 }; + +/// The speed of a jump. +pub const JUMP_SPEED: Vector2 = Vector2 { x: 0.0, y: -600.0 }; diff --git a/src/engine/renderer/mod.rs b/src/engine/renderer/mod.rs new file mode 100644 index 0000000..058842a --- /dev/null +++ b/src/engine/renderer/mod.rs @@ -0,0 +1,110 @@ +use std::time::Instant; + +use sfml::graphics::{ + RenderWindow, + RenderTarget, + Sprite, + Color, + IntRect, +}; + +use sfml::window::{ + Style, + Event, +}; + +use sfml::system::{ + Vector2, +}; + +use engine::texture::{Texture, TextureManager}; +use engine::scene::Scene; + +/// Our custom drawable trait. +pub trait Drawable { + /// Returns the texture of the drawable. + fn texture(&self) -> Texture; + + /// Returns the coordinates to use on the texture. + fn texture_rect(&self) -> IntRect; + + /// Returns the position on which the drawable should be drawn. + fn position(&self) -> Vector2; +} + +/// The game window. +pub struct Renderer { + + /// The window on which the rendering will be done. + window: RenderWindow, + + /// The texture manager needed by the renderer. + texture_manager: TextureManager, + + /// The global timer of the renderer. + started: Instant, + +} + +impl Renderer { + /// Creates a new renderer. + pub fn new(width: u32, height: u32) -> Renderer { + + let mut window = RenderWindow::new( + (width, height), + "Bomberman", + Style::CLOSE, + &Default::default(), + ); + window.set_vertical_sync_enabled(true); + window.set_framerate_limit(60); + + Renderer { + window: window, + texture_manager: TextureManager::new(), + started: Instant::now(), + } + } + + /// Returns the animation state of the renderer, between 0 and 3. + pub fn animation_state(&self) -> i32 { + let duration = Instant::now().duration_since(self.started); + let ns = 1_000_000_000 * duration.as_secs() + duration.subsec_nanos() as u64; + let result = (ns as usize / 250_000_000) % 4; + result as i32 + } + + /// Clears the window. + pub fn clear(&mut self) { + self.window.clear(&Color::rgb(0, 0, 0)); + } + + /// Draws a drawable. + pub fn draw(&mut self, drawable: &D) { + let texture = self.texture_manager.get(drawable.texture()); + let mut sprite = Sprite::with_texture(&texture); + sprite.set_texture_rect(&drawable.texture_rect()); + + use sfml::graphics::Transformable; + sprite.set_position(drawable.position()); + + self.window.draw(&sprite); + } + + /// Draws a scene on the window. + pub fn draw_scene(&mut self, scene: &Scene) { + for c in scene.characters() { + self.draw(c); + } + } + + /// Triggers the display. + pub fn display(&mut self) { + self.window.display(); + } + + /// Check if there are new events, returns the top of the stack. + pub fn poll_event(&mut self) -> Option { + self.window.poll_event() + } +} diff --git a/src/engine/scene/mod.rs b/src/engine/scene/mod.rs index 169a2a0..e4e843a 100644 --- a/src/engine/scene/mod.rs +++ b/src/engine/scene/mod.rs @@ -1,11 +1,6 @@ use std::time::Duration; use sfml::window::Event; -use sfml::graphics::{ - RenderTarget, - RenderStates, - Drawable, -}; use engine::character::Character; @@ -14,6 +9,7 @@ pub struct Scene { /// The characters contained in the scene. characters: Vec, + } impl Scene { @@ -32,8 +28,15 @@ impl Scene { /// Updates the whole scene. pub fn update(&mut self, duration: &Duration) { + for c in &mut self.characters { c.update(duration); + + if c.position.y > 500.0 { + c.position.y = 500.0; + c.speed.y = 0.0; + c.ground_collision(); + } } } @@ -43,22 +46,12 @@ impl Scene { c.manage_event(event); } } -} -impl Drawable for Scene { - fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>( - &'a self, - target: &mut RenderTarget, - states: RenderStates<'texture, 'shader, 'shader_texture> - ) { - - for c in &self.characters { - let mut s = RenderStates::default(); - s.transform = states.transform.clone(); - s.blend_mode = states.blend_mode; - c.draw(target, s); - } + /// Returns a reference to the characters of the scene. + pub fn characters(&self) -> &Vec { + &self.characters } + } /// Trait that needs to be implemented for everything that can be updatable. diff --git a/src/engine/texture/mod.rs b/src/engine/texture/mod.rs index 0df1d9f..6ea7057 100644 --- a/src/engine/texture/mod.rs +++ b/src/engine/texture/mod.rs @@ -57,12 +57,13 @@ macro_rules! make_textures { } make_textures!( + Mario, mario, make_mario_texture, "../../../assets/textures/mario.png", ); impl TextureManager { /// Creates a textures from an array of bytes. fn make_texture_from_bytes(bytes: Vec) -> SfTexture { - SfTexture::from_memory(&bytes, &IntRect::new(0, 0, 500, 500)) + SfTexture::from_memory(&bytes, &IntRect::new(0, 0, 256, 256)) .expect("Failed to create texture") } } diff --git a/src/game/main.rs b/src/game/main.rs index beeaf1e..2413f2d 100644 --- a/src/game/main.rs +++ b/src/game/main.rs @@ -7,21 +7,14 @@ use std::time::{ }; use sfml::window::{ - ContextSettings, - Style, Event, Key, }; -use sfml::graphics::{ - RenderWindow, - RenderTarget, - Color, -}; - use rusty::engine::scene::Scene; use rusty::engine::character::Character; use rusty::engine::controls::Controls; +use rusty::engine::renderer::Renderer; fn main() { let game_width = 800; @@ -32,27 +25,16 @@ fn main() { let mut scene = Scene::new(); scene.add(character); - scene.add(Character::new()); - let context_settings = ContextSettings { - ..Default::default() - }; - - let mut window = RenderWindow::new( - (game_width, game_height), - "Free Rusty Maker", - Style::CLOSE, - &context_settings, - ); - - window.set_framerate_limit(60); + let mut renderer = Renderer::new(game_width, game_height); + let mut after_loop = None; let mut running = true; loop { // Manage the events - while let Some(event) = window.poll_event() { + while let Some(event) = renderer.poll_event() { match event { // Quit the game if window is closed @@ -71,15 +53,23 @@ fn main() { } // Manage the physics - // ... + let duration = if let Some(instant) = after_loop { + Instant::now().duration_since(instant) + } else { + Duration::from_millis(20) + }; + after_loop = Some(Instant::now()); + + scene.update(&duration); // Do the rendering - window.clear(&Color::rgb(50, 200, 50)); - window.draw(&scene); + renderer.clear(); + renderer.draw_scene(&scene); // Display and manage the frame rate - window.display(); + renderer.display(); + if ! running { break;