game/src/engine/character.rs

231 lines
5.9 KiB
Rust

//! 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<Side> {
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<Controls>,
/// 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<Controls>) -> 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<Controls> {
&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
}
}