player texture and animation

This commit is contained in:
Evan Pratten 2021-10-01 22:46:09 -04:00
parent f1c341fdbc
commit 6ef9655a2b
14 changed files with 267 additions and 35 deletions

2
.gitignore vendored
View File

@ -8,3 +8,5 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
/*.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -41,6 +41,13 @@
20,
20,
255
],
"white": [
188,
188,
188,
188
]
}
},
"animation_fps": 15
}

View File

@ -0,0 +1,35 @@
use raylib::math::{Rectangle, Vector2};
use super::MainCharacter;
const GRAVITY_PPS: f32 = 2.0;
pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), ()> {
// Convert the player to a rectangle
let predicted_player_position = player.position + player.velocity;
let player_rect = Rectangle::new(
predicted_player_position.x - (player.size.x / 2.0),
predicted_player_position.y - (player.size.x / 2.0),
player.size.x,
player.size.y,
);
// Calculate a generic "floor" to always collide with
let floor_rect = Rectangle::new(f32::MIN, 0.0, f32::MAX, 1.0);
// If the player is colliding, only apply the x force
if (floor_rect.check_collision_recs(&player_rect) || player_rect.y + player_rect.height > floor_rect.y)
&& player.velocity.y > 0.0
{
player.velocity.y = 0.0;
}
// TODO: Error out if colliding in the X direction
// Apply the force
player.position += player.velocity;
// Apply gravity
player.velocity.y += GRAVITY_PPS;
Ok(())
}

View File

@ -1,22 +1,62 @@
pub mod collisions;
pub mod render;
use raylib::math::Vector2;
use chrono::{DateTime, Utc};
use raylib::{math::Vector2, texture::Texture2D};
#[derive(Debug, Clone)]
use crate::utilities::anim_render::AnimatedSpriteSheet;
use self::collisions::modify_player_based_on_forces;
#[derive(Debug, Default)]
pub enum CharacterState {
#[default]
Running,
Jumping,
Dashing,
}
#[derive(Debug)]
pub struct MainCharacter {
pub position: Vector2,
pub velocity: Vector2,
pub size: Vector2,
pub sprite_sheet: AnimatedSpriteSheet,
pub current_state: CharacterState,
pub state_set_timestamp: DateTime<Utc>,
}
impl MainCharacter {
pub fn new(position: Vector2) -> Self {
pub fn new(position: Vector2, sprite_sheet: Texture2D) -> Self {
Self {
position,
size: Vector2::new(60.0, 80.0),
velocity: Vector2::new(20.0, 0.0),
size: Vector2::new(100.0, 130.0),
sprite_sheet: AnimatedSpriteSheet::new(
sprite_sheet,
Vector2::new(300.0, 300.0),
3,
8,
6,
),
current_state: CharacterState::default(),
state_set_timestamp: Utc::now(),
}
}
pub fn apply_force(&mut self, force: Vector2) {
self.position += force;
pub fn apply_force(&mut self, force: Vector2) -> Option<()> {
self.velocity = force;
modify_player_based_on_forces(self).unwrap();
// self.position = calculate_player_collisions(&self).unwrap();
Some(())
}
pub fn update_gravity(&mut self) {
modify_player_based_on_forces(self).unwrap();
}
pub fn set_state(&mut self, state: CharacterState) {
self.current_state = state;
self.state_set_timestamp = Utc::now();
}
}

View File

@ -1,15 +1,33 @@
use std::ops::{Div, Sub};
use chrono::Utc;
use raylib::prelude::*;
use tracing::log::trace;
use crate::utilities::non_ref_raylib::HackedRaylibHandle;
use crate::{utilities::non_ref_raylib::HackedRaylibHandle, GameConfig};
use super::MainCharacter;
pub fn render_character_in_camera_space(
raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>,
player: &MainCharacter,
config: &GameConfig,
) {
// Calculate the time since the start of the state
let time_since_state_change = Utc::now() - player.state_set_timestamp;
raylib.draw_rectangle_v(player.position.sub(player.size.div(2.0)), player.size, Color::WHITE);
// Calculate the number of frames since state change
let frames_since_state_change = ((time_since_state_change.num_milliseconds() as f64 / 1000.0) * config.animation_fps as f64) as f32;
trace!(
"Rendering player frame: {} ({})",
frames_since_state_change % player.sprite_sheet.sprite_count as f32,
frames_since_state_change
);
player.sprite_sheet.render(
raylib,
player.position.sub(player.size.div(2.0)),
Some(Vector2::new(player.size.y, player.size.y)),
Some((frames_since_state_change % player.sprite_sheet.sprite_count as f32).floor() as usize),
);
}

View File

@ -10,5 +10,8 @@ impl ScreenSpaceRender for InGameScreen {
) {
// Calculate the logo position
let screen_size = raylib.get_screen_size();
// Draw a thin glow box around the screen
raylib.draw_rectangle_lines(0, 0, screen_size.x as i32, screen_size.y as i32, config.colors.red);
}
}

View File

@ -1,11 +1,7 @@
use dirty_fsm::{Action, ActionFlag};
use raylib::prelude::*;
use crate::{
character::MainCharacter,
context::GameContext,
utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender},
};
use crate::{character::{CharacterState, MainCharacter}, context::GameContext, utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}};
use super::{Scenes, ScreenError};
use tracing::{debug, trace};
@ -22,7 +18,7 @@ pub struct InGameScreen {
impl InGameScreen {
/// Construct a new `InGameScreen`
pub fn new() -> Self {
pub fn new(player_sprite_sheet: Texture2D) -> Self {
Self {
camera: Camera2D {
offset: Vector2::zero(),
@ -30,7 +26,7 @@ impl InGameScreen {
rotation: 0.0,
zoom: 1.0,
},
player: MainCharacter::new(Vector2::new(0.0, -45.0)),
player: MainCharacter::new(Vector2::new(0.0, -80.0), player_sprite_sheet),
}
}
}
@ -44,6 +40,9 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
debug!("Running InGameScreen for the first time");
// Set the player to running
self.player.set_state(CharacterState::Running);
Ok(())
}

View File

@ -1,27 +1,32 @@
use std::ops::Div;
use super::InGameScreen;
use crate::{GameConfig, utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate}};
use crate::{
utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate},
GameConfig,
};
use chrono::Duration;
use raylib::prelude::*;
impl FrameUpdate for InGameScreen {
fn update(&mut self, raylib: &HackedRaylibHandle, delta_seconds: &Duration,
config: &GameConfig) {
fn update(
&mut self,
raylib: &HackedRaylibHandle,
delta_seconds: &Duration,
config: &GameConfig,
) {
// Set the camera's offset based on screen size
self.camera.offset = raylib.get_screen_size().div(Vector2::new(2.0, 1.25));
self.camera.target = Vector2::new(
self.player.position.x,
self.camera.target.y,
);
self.camera.offset = raylib.get_screen_size().div(Vector2::new(2.0, 1.05));
self.camera.target = Vector2::new(self.player.position.x, self.camera.target.y);
// Check the only possible keyboard inputs
let is_jump = raylib.is_key_down(KeyboardKey::KEY_SPACE);
let is_jump = raylib.is_key_pressed(KeyboardKey::KEY_SPACE);
let is_dash = raylib.is_key_down(KeyboardKey::KEY_LEFT_SHIFT);
let is_pause = raylib.is_key_down(KeyboardKey::KEY_ESCAPE);
if is_jump {
self.player.apply_force(Vector2::new(0.0, -1.0));
}
self.player.apply_force(Vector2::new(0.0, -30.0));
}
self.player.update_gravity();
}
}

View File

@ -1,25 +1,33 @@
use std::ops::Mul;
use super::InGameScreen;
use crate::{GameConfig, character::render::render_character_in_camera_space, utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::WorldSpaceRender}};
use crate::{
character::render::render_character_in_camera_space,
utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::WorldSpaceRender},
GameConfig,
};
use raylib::prelude::*;
impl WorldSpaceRender for InGameScreen {
fn render_world_space(&self, raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>,
config: &GameConfig) {
fn render_world_space(
&self,
raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>,
config: &GameConfig,
) {
// Render the player
render_character_in_camera_space(raylib, &self.player);
render_character_in_camera_space(raylib, &self.player, &config);
// Render the floor as a line
let screen_world_zero = raylib.get_screen_to_world2D(Vector2::zero(), self.camera);
let screen_world_size = raylib.get_screen_to_world2D(raylib.get_screen_size().mul(2.0), self.camera);
let screen_world_size =
raylib.get_screen_to_world2D(raylib.get_screen_size().mul(2.0), self.camera);
raylib.draw_rectangle(
screen_world_zero.x as i32,
0,
screen_world_size.x as i32,
5,
Color::WHITE,
config.colors.white,
);
}
}

View File

@ -4,10 +4,13 @@ use self::{
};
use crate::{
context::GameContext,
utilities::{datastore::ResourceLoadError, non_ref_raylib::HackedRaylibHandle},
utilities::{
datastore::{load_texture_from_internal_data, ResourceLoadError},
non_ref_raylib::HackedRaylibHandle,
},
};
use dirty_fsm::StateMachine;
use raylib::RaylibThread;
use raylib::{texture::Texture2D, RaylibThread};
pub mod fsm_error_screen;
pub mod ingame_scene;
@ -40,6 +43,11 @@ pub fn build_screen_state_machine(
StateMachine<Scenes, ScreenError, GameContext>,
ScreenError,
> {
// Load the various textures needed by the states
let player_sprite_sheet =
load_texture_from_internal_data(raylib_handle, thread, "character/player_run.png").unwrap();
// Set up the state machine
let mut machine = StateMachine::new();
machine.add_action(Scenes::FsmErrorScreen, FsmErrorScreen::new())?;
machine.add_action(
@ -47,6 +55,6 @@ pub fn build_screen_state_machine(
LoadingScreen::new(raylib_handle, thread)?,
)?;
machine.add_action(Scenes::MainMenuScreen, MainMenuScreen::new())?;
machine.add_action(Scenes::InGameScene, InGameScreen::new())?;
machine.add_action(Scenes::InGameScene, InGameScreen::new(player_sprite_sheet))?;
Ok(machine)
}

View File

@ -0,0 +1,104 @@
use raylib::{
color::Color,
math::{Rectangle, Vector2},
prelude::RaylibDraw,
texture::Texture2D,
};
#[derive(Debug)]
pub struct AnimatedSpriteSheet {
texture: Texture2D,
sprite_size: Vector2,
sheet_width: usize,
pub sprite_count: usize,
default_sprite_id: usize,
}
impl AnimatedSpriteSheet {
/// Construct a new AnimatedSpriteSheet
pub fn new(
texture: Texture2D,
sprite_size: Vector2,
sheet_width: usize,
sprite_count: usize,
default_sprite_id: usize,
) -> Self {
Self {
texture,
sprite_size,
sheet_width,
sprite_count,
default_sprite_id,
}
}
pub fn render<T>(
&self,
raylib: &mut T,
position: Vector2,
scaled_size: Option<Vector2>,
sprite_id: Option<usize>,
) where
T: RaylibDraw,
{
let sprite_id = sprite_id.unwrap_or(self.default_sprite_id);
let sprite_id = if sprite_id >= self.sprite_count {
self.default_sprite_id
} else {
sprite_id
};
let sprite_rect = Rectangle::new(
(sprite_id % self.sheet_width) as f32 * self.sprite_size.x,
(sprite_id / self.sheet_width) as f32 * self.sprite_size.y,
self.sprite_size.x,
self.sprite_size.y,
);
let scaled_size = scaled_size.unwrap_or(self.sprite_size);
raylib.draw_texture_pro(
&self.texture,
sprite_rect,
Rectangle::new(position.x, position.y, scaled_size.x, scaled_size.y),
Vector2::zero(),
0.0,
Color::WHITE,
);
}
// {
// let sprite_id = match sprite_id {
// Some(id) => {
// if id >= self.sprite_count {
// self.default_sprite_id
// } else {
// id
// }
// }
// None => self.default_sprite_id,
// };
// let sprite_x = sprite_id % self.sheet_width;
// let sprite_y = sprite_id / self.sheet_width;
// raylib.draw_texture_pro(
// &self.texture,
// Rectangle {
// x: sprite_x as f32,
// y: sprite_y as f32,
// width: self.sprite_size.x,
// height: self.sprite_size.y,
// },
// Rectangle {
// x: position.x,
// y: position.y,
// width: self.sprite_size.x,
// height: self.sprite_size.y,
// },
// Vector2::zero(),
// 0.0,
// Color::WHITE,
// );
// }
}

View File

@ -20,6 +20,7 @@ pub struct ColorTheme {
pub yellow: Color,
pub pink: Color,
pub background: Color,
pub white: Color,
}
#[derive(Debug, Clone, Deserialize)]
@ -28,6 +29,7 @@ pub struct GameConfig {
pub base_window_size: (i32, i32),
pub sentry_dsn: String,
pub colors: ColorTheme,
pub animation_fps: usize,
}
impl GameConfig {

View File

@ -6,3 +6,4 @@ pub mod math;
pub mod non_ref_raylib;
pub mod render_layer;
pub mod shaders;
pub mod anim_render;