use std::time::{ Instant, Duration, }; use sfml::system::Vector2; use sfml::window::Event; use sfml::window::Key; use sfml::graphics::{ IntRect, FloatRect, View, }; 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. pub position: Vector2, /// The speed of the character. 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, /// Indicates that the player has released the jump button. can_jump: bool, } impl Character { /// Creates a character in (0, 0). fn generic(controls: Option) -> Character { Character { position: Vector2::new(0.0, 0.0), speed: Vector2::new(0.0, 0.0), controls: controls, side: Side::Right, jump_counter: 1, max_jump: 1, animation_timer: None, can_jump: true, } } /// 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::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.can_jump && 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; } /// Make the player fall. pub fn fall_off(&mut self) { if self.jump_counter == self.max_jump { self.jump_counter -= 1; } } /// Returns a reference to the controls. pub fn controls(&self) -> &Option { &self.controls } /// Returns a view that looks at the character. pub fn view(&self) -> View { View::new(self.position, Vector2::new(24.0 * 16.0, 24.0 * 9.0)) } /// Returns the collision bounding box of the character. pub fn bbox(&self) -> FloatRect { FloatRect::new(self.position.x, self.position.y, 32.0, 32.0) } } 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(); self.can_jump = false; } , Event::KeyReleased { code: Key::Return, .. } => self.can_jump = true, _ => (), } } } impl Drawable for Character { fn texture(&self) -> Texture { Texture::Mario } fn texture_rect(&self) -> IntRect { let frame = if let Some(started) = self.animation_timer { 1 - duration_as_frame(&Instant::now().duration_since(started), 2) } else { 0 }; IntRect::new(self.side.offset(), frame * 32, 32, 32) } fn position(&self) -> Vector2 { self.position } }