player texture and animation
This commit is contained in:
parent
f1c341fdbc
commit
6ef9655a2b
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
/*.gif
|
||||
|
BIN
game/assets/character/player_run.png
Normal file
BIN
game/assets/character/player_run.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
@ -41,6 +41,13 @@
|
||||
20,
|
||||
20,
|
||||
255
|
||||
],
|
||||
"white": [
|
||||
188,
|
||||
188,
|
||||
188,
|
||||
188
|
||||
]
|
||||
}
|
||||
},
|
||||
"animation_fps": 15
|
||||
}
|
||||
|
35
game/src/character/collisions.rs
Normal file
35
game/src/character/collisions.rs
Normal 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(())
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
104
game/src/utilities/anim_render.rs
Normal file
104
game/src/utilities/anim_render.rs
Normal 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,
|
||||
// );
|
||||
// }
|
||||
}
|
@ -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 {
|
||||
|
@ -6,3 +6,4 @@ pub mod math;
|
||||
pub mod non_ref_raylib;
|
||||
pub mod render_layer;
|
||||
pub mod shaders;
|
||||
pub mod anim_render;
|
||||
|
Reference in New Issue
Block a user