//! This module helps us dealing with characters. use std::time::{Duration, Instant}; use crate::engine::bbox::Bbox; use crate::engine::controls::{Action, Controls}; use crate::engine::event::{Event, Keyboard}; use crate::engine::math::{clamp, duration_as_f64}; use crate::engine::physics; use crate::engine::scene::Updatable; use crate::engine::texture::Texture; use crate::engine::vector::Vector; use crate::engine::Drawable; use crate::log; /// 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: Vector) -> 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 => 32, Side::Right => 0, } } } /// A character, enemy or controllable. pub struct Character { /// The position of the character. pub position: Vector, /// The speed of the character. pub speed: Vector, /// 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: Duration, /// Whether the character is walking or not. walking: bool, /// 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: Vector::new(0.0, 0.0), speed: Vector::new(0.0, 0.0), controls, side: Side::Right, jump_counter: 1, max_jump: 1, animation_timer: Duration::from_millis(0), can_jump: true, walking: false, } } /// 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)) } /// 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; } } /// Makes the player die. pub fn die(&self) { log!("dead"); } /// 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) -> Bbox { Bbox::new(self.position.x, self.position.y, 24.0 * 16.0, 24.0 * 9.0) } /// Returns the collision bounding box of the character. pub fn bbox(&self) -> Bbox { Bbox::new(self.position.x + 8.0, self.position.y, 16.0, 32.0) } } impl Updatable for Character { fn update(&mut self, duration: &Duration, keyboard: &Keyboard) { let mut force = Vector::new(0.0, 0.0); if let Some(ref controls) = self.controls { force += controls.direction(keyboard); } if let Some(side) = Side::from_force(force) { if !self.walking { self.animation_timer = Duration::from_millis(0); } self.walking = true; self.side = side; } else { if self.walking { self.animation_timer = Duration::from_millis(0); } self.walking = false; } let duration = duration_as_f64(duration); // Compute acceleration let accel = physics::G + force * 64.0 * 64.0; // Compute speed self.speed.x *= physics::GROUND_FRICTION.x; self.speed += accel * duration; if self.speed.y > physics::MAXIMUM_VERTICAL_SPEED { self.speed.y = physics::MAXIMUM_VERTICAL_SPEED; } let limit = match self.controls { Some(ref controls) if controls.is_running() => physics::MAXIMUM_RUNNING_SPEED, _ => physics::MAXIMUM_WALKING_SPEED, }; self.speed.x = clamp(self.speed.x, -limit, limit); // Compute position self.position += self.speed * duration; } fn manage_event(&mut self, event: &Event) { let action = if let Some(ref controls) = self.controls { controls.convert(event) } else { None }; match action { Some(Action::Jump(true)) => { self.jump(); self.can_jump = false; } Some(Action::Jump(false)) => { self.can_jump = true; } _ => (), } } } impl Drawable for Character { fn texture(&self) -> Texture { Texture::Rusty } fn texture_rect(&self) -> Bbox { let frame = 0.0; // duration_as_frame(&Instant::now().duration_since(self.animation_timer), 4); let offset = if self.walking { 64.0 } else { 0.0 }; Bbox::new(self.side.offset() as f64 + offset, frame * 32.0, 32.0, 32.0) } fn position(&self) -> Vector { self.position } }