//! This module helps us dealing with characters. use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::engine::bbox::Bbox; use crate::engine::controls::Controls; use crate::engine::event::Action; use crate::engine::math::{clamp, duration_as_f64, duration_as_frame}; 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: SystemTime, /// Whether the character is walking or not. walking: 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: UNIX_EPOCH, 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.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 } /// Changes the controls. pub fn set_controls(&mut self, controls: Option) { self.controls = controls; } /// Returns a view that looks at the character. pub fn view(&self) -> Bbox { Bbox::from_center_and_size(self.position, Vector::new(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, now: SystemTime, duration: Duration) { let mut force = Vector::new(0.0, 0.0); if let Some(ref controls) = self.controls { // force += controls.direction(); } if let Some(side) = Side::from_force(force) { if !self.walking { self.animation_timer = now; } self.walking = true; self.side = side; } else { if self.walking { self.animation_timer = now; } 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; } /// An action was asked to the character. fn manage_action(&mut self, action: Action) { match action { Action::Button1 => { self.jump(); } _ => (), } } } impl Drawable for Character { fn texture(&self) -> Texture { Texture::Rusty } fn texture_rect(&self, now: SystemTime) -> Bbox { let frame = duration_as_frame(now.duration_since(self.animation_timer).unwrap(), 4) as f64; 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 } }