Merge pull request #22 from Ewpratten/levels
Add POC level system [wip]
This commit is contained in:
commit
c1a7125f34
BIN
game/assets/levels/level_0/background.png
Normal file
BIN
game/assets/levels/level_0/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
32
game/assets/levels/level_0/colliders.json
Normal file
32
game/assets/levels/level_0/colliders.json
Normal file
@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"x": 322,
|
||||
"y": 926,
|
||||
"width": 610,
|
||||
"height": 73
|
||||
},
|
||||
{
|
||||
"x": 852,
|
||||
"y": 882,
|
||||
"width": 81,
|
||||
"height": 178
|
||||
},
|
||||
{
|
||||
"x": 852,
|
||||
"y": 882,
|
||||
"width": 258,
|
||||
"height": 33
|
||||
},
|
||||
{
|
||||
"x": 1599,
|
||||
"y": 699,
|
||||
"width": 918,
|
||||
"height": 63
|
||||
},
|
||||
{
|
||||
"x": 2733,
|
||||
"y": 789,
|
||||
"width": 108,
|
||||
"height": 211
|
||||
}
|
||||
]
|
BIN
game/assets/levels/level_0/platforms.png
Normal file
BIN
game/assets/levels/level_0/platforms.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
3
game/assets/levels/levels.json
Normal file
3
game/assets/levels/levels.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
"level_0"
|
||||
]
|
@ -2,11 +2,17 @@ use std::ops::Mul;
|
||||
|
||||
use raylib::math::{Rectangle, Vector2};
|
||||
|
||||
use crate::scenes::ingame_scene::world::WORLD_LEVEL_X_OFFSET;
|
||||
|
||||
use super::{CharacterState, MainCharacter};
|
||||
|
||||
pub const GRAVITY_PPS: f32 = 2.0;
|
||||
|
||||
pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), ()> {
|
||||
pub fn modify_player_based_on_forces(
|
||||
player: &mut MainCharacter,
|
||||
colliders: &Vec<Rectangle>,
|
||||
level_height_offset: f32,
|
||||
) -> Result<(), ()> {
|
||||
// Modify the player's velocity by the forces
|
||||
player.movement_force += player.base_velocity;
|
||||
player.velocity = player.movement_force;
|
||||
@ -14,7 +20,7 @@ pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), (
|
||||
// Predict the player's position next frame
|
||||
let predicted_player_position = player.position + player.velocity;
|
||||
|
||||
// Calculate a bounding rect around the player
|
||||
// Calculate a bounding rect around the player both now, and one frame in the future
|
||||
let player_rect = Rectangle::new(
|
||||
predicted_player_position.x - (player.size.x / 2.0),
|
||||
predicted_player_position.y - (player.size.x / 2.0),
|
||||
@ -25,10 +31,24 @@ pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), (
|
||||
// Calculate a generic "floor" to always collide with
|
||||
let floor_rect = Rectangle::new(f32::MIN, 0.0, f32::MAX, 1.0);
|
||||
|
||||
// Check collision conditions
|
||||
let check_player_colliding_with_floor = || floor_rect.check_collision_recs(&player_rect);
|
||||
let check_player_colliding_with_floor_next_frame =
|
||||
|| player_rect.y + player_rect.height > floor_rect.y;
|
||||
let check_player_colliding_with_colliders = || {
|
||||
colliders.iter().any(|rect| {
|
||||
let mut translated_rect = rect.clone();
|
||||
translated_rect.y += level_height_offset;
|
||||
translated_rect.x += WORLD_LEVEL_X_OFFSET;
|
||||
translated_rect.check_collision_recs(&player_rect)
|
||||
})
|
||||
};
|
||||
|
||||
// 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
|
||||
if (check_player_colliding_with_floor()
|
||||
|| check_player_colliding_with_floor_next_frame()
|
||||
|| check_player_colliding_with_colliders())
|
||||
&& player.velocity.y != 0.0
|
||||
{
|
||||
player.velocity.y = 0.0;
|
||||
|
||||
@ -36,11 +56,17 @@ pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), (
|
||||
if player.current_state == CharacterState::Jumping
|
||||
|| player.current_state == CharacterState::Dashing
|
||||
{
|
||||
player.update_player(Some(CharacterState::Running));
|
||||
player.update_player(
|
||||
Some(CharacterState::Running),
|
||||
colliders,
|
||||
level_height_offset,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Check sideways collisions
|
||||
|
||||
// Finally apply the velocity to the player
|
||||
player.position += player.velocity;
|
||||
|
||||
|
@ -2,7 +2,10 @@ pub mod collisions;
|
||||
pub mod render;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use raylib::{math::Vector2, texture::Texture2D};
|
||||
use raylib::{
|
||||
math::{Rectangle, Vector2},
|
||||
texture::Texture2D,
|
||||
};
|
||||
|
||||
use crate::utilities::anim_render::AnimatedSpriteSheet;
|
||||
|
||||
@ -48,7 +51,12 @@ impl MainCharacter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_player(&mut self, state: Option<CharacterState>) {
|
||||
pub fn update_player(
|
||||
&mut self,
|
||||
state: Option<CharacterState>,
|
||||
colliders: &Vec<Rectangle>,
|
||||
level_height_offset: f32,
|
||||
) {
|
||||
if let Some(state) = state {
|
||||
// Update the internal state
|
||||
if state != self.current_state {
|
||||
@ -65,6 +73,6 @@ impl MainCharacter {
|
||||
}
|
||||
|
||||
// Update the player based on the new velocity
|
||||
modify_player_based_on_forces(self).unwrap();
|
||||
modify_player_based_on_forces(self, colliders, level_height_offset).unwrap();
|
||||
}
|
||||
}
|
||||
|
53
game/src/scenes/ingame_scene/level/loader.rs
Normal file
53
game/src/scenes/ingame_scene/level/loader.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use raylib::{RaylibHandle, RaylibThread};
|
||||
|
||||
use crate::{
|
||||
utilities::datastore::{load_texture_from_internal_data, ResourceLoadError},
|
||||
StaticGameData,
|
||||
};
|
||||
|
||||
use super::Level;
|
||||
|
||||
pub fn load_all_levels(
|
||||
raylib_handle: &mut RaylibHandle,
|
||||
thread: &RaylibThread,
|
||||
) -> Result<Vec<Level>, ResourceLoadError> {
|
||||
// Get a listing of all levels we have
|
||||
let level_names: Vec<String> = serde_json::from_str(
|
||||
&String::from_utf8(
|
||||
StaticGameData::get("levels/levels.json")
|
||||
.expect("Could not load levels.json")
|
||||
.data
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
)?;
|
||||
|
||||
// Build a level list
|
||||
let mut levels = Vec::new();
|
||||
|
||||
for level_name in &level_names {
|
||||
levels.push(Level {
|
||||
name: level_name.to_string(),
|
||||
background_tex: load_texture_from_internal_data(
|
||||
raylib_handle,
|
||||
thread,
|
||||
&format!("levels/{}/background.png", level_name),
|
||||
)?,
|
||||
platform_tex: load_texture_from_internal_data(
|
||||
raylib_handle,
|
||||
thread,
|
||||
&format!("levels/{}/platforms.png", level_name),
|
||||
)?,
|
||||
colliders: serde_json::from_str(
|
||||
&String::from_utf8(
|
||||
StaticGameData::get(&format!("levels/{}/colliders.json", level_name))
|
||||
.unwrap()
|
||||
.data
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
)?,
|
||||
});
|
||||
}
|
||||
Ok(levels)
|
||||
}
|
11
game/src/scenes/ingame_scene/level/mod.rs
Normal file
11
game/src/scenes/ingame_scene/level/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use raylib::{math::Rectangle, texture::Texture2D};
|
||||
|
||||
pub mod loader;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Level {
|
||||
pub name: String,
|
||||
pub background_tex: Texture2D,
|
||||
pub platform_tex: Texture2D,
|
||||
pub colliders: Vec<Rectangle>
|
||||
}
|
@ -1,25 +1,41 @@
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{character::{CharacterState, MainCharacter}, context::GameContext, utilities::{render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}, world_paint_texture::WorldPaintTexture}};
|
||||
use crate::{
|
||||
character::{CharacterState, MainCharacter},
|
||||
context::GameContext,
|
||||
utilities::{
|
||||
render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender},
|
||||
world_paint_texture::WorldPaintTexture,
|
||||
},
|
||||
};
|
||||
|
||||
use self::level::Level;
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
mod hud;
|
||||
pub mod level;
|
||||
mod update;
|
||||
mod world;
|
||||
pub mod world;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InGameScreen {
|
||||
camera: Camera2D,
|
||||
player: MainCharacter,
|
||||
world_background: WorldPaintTexture
|
||||
world_background: WorldPaintTexture,
|
||||
levels: Vec<Level>,
|
||||
current_level_idx: usize,
|
||||
}
|
||||
|
||||
impl InGameScreen {
|
||||
/// Construct a new `InGameScreen`
|
||||
pub fn new(player_sprite_sheet: Texture2D, background_texture: Texture2D) -> Self {
|
||||
pub fn new(
|
||||
player_sprite_sheet: Texture2D,
|
||||
background_texture: Texture2D,
|
||||
levels: Vec<Level>,
|
||||
) -> Self {
|
||||
Self {
|
||||
camera: Camera2D {
|
||||
offset: Vector2::zero(),
|
||||
@ -27,8 +43,10 @@ impl InGameScreen {
|
||||
rotation: 0.0,
|
||||
zoom: 1.0,
|
||||
},
|
||||
player: MainCharacter::new(Vector2::new(0.0, -80.0), player_sprite_sheet),
|
||||
world_background: WorldPaintTexture::new(background_texture)
|
||||
player: MainCharacter::new(Vector2::new(0.0, -85.0), player_sprite_sheet),
|
||||
world_background: WorldPaintTexture::new(background_texture),
|
||||
levels,
|
||||
current_level_idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,7 +61,12 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
|
||||
debug!("Running InGameScreen for the first time");
|
||||
|
||||
// Set the player to running
|
||||
self.player.update_player(Some(CharacterState::Running));
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
self.player.update_player(
|
||||
Some(CharacterState::Running),
|
||||
&cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ impl FrameUpdate for InGameScreen {
|
||||
config: &GameConfig,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
// Get the current level
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
|
||||
|
||||
// Set the camera's offset based on screen size
|
||||
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);
|
||||
@ -28,16 +33,20 @@ impl FrameUpdate for InGameScreen {
|
||||
&& !(self.player.current_state == CharacterState::Dashing);
|
||||
|
||||
if is_jump {
|
||||
self.player.update_player(Some(CharacterState::Jumping));
|
||||
self.player.update_player(Some(CharacterState::Jumping), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else if is_dash {
|
||||
self.player.update_player(Some(CharacterState::Dashing));
|
||||
self.player.update_player(Some(CharacterState::Dashing), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else {
|
||||
if self.player.current_state != CharacterState::Jumping
|
||||
&& self.player.current_state != CharacterState::Dashing
|
||||
{
|
||||
self.player.update_player(Some(CharacterState::Running));
|
||||
self.player.update_player(Some(CharacterState::Running), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else {
|
||||
self.player.update_player(None);
|
||||
self.player.update_player(None, &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use crate::{
|
||||
};
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub const WORLD_LEVEL_X_OFFSET: f32 = 200.0;
|
||||
|
||||
impl WorldSpaceRender for InGameScreen {
|
||||
fn render_world_space(
|
||||
&self,
|
||||
@ -16,8 +18,14 @@ impl WorldSpaceRender for InGameScreen {
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
// Get the current level
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
|
||||
// Render the world background
|
||||
self.world_background.render(raylib, Vector2::new(0.0, -1080.0), &self.camera);
|
||||
// self.world_background.render(raylib, Vector2::new(0.0, -1080.0), &self.camera);
|
||||
|
||||
// Render the platform layer
|
||||
raylib.draw_texture_v(&cur_level.platform_tex, Vector2::new(WORLD_LEVEL_X_OFFSET, -cur_level.platform_tex.height as f32), Color::WHITE);
|
||||
|
||||
// Render the floor as a line
|
||||
let screen_world_zero = raylib.get_screen_to_world2D(Vector2::zero(), self.camera);
|
||||
|
@ -1,5 +1,7 @@
|
||||
use self::{
|
||||
fsm_error_screen::FsmErrorScreen, ingame_scene::InGameScreen, loading_screen::LoadingScreen,
|
||||
fsm_error_screen::FsmErrorScreen,
|
||||
ingame_scene::{level::loader::load_all_levels, InGameScreen},
|
||||
loading_screen::LoadingScreen,
|
||||
main_menu_screen::MainMenuScreen,
|
||||
};
|
||||
use crate::{
|
||||
@ -48,6 +50,7 @@ pub fn build_screen_state_machine(
|
||||
load_texture_from_internal_data(raylib_handle, thread, "character/player_run.png").unwrap();
|
||||
let world_background =
|
||||
load_texture_from_internal_data(raylib_handle, thread, "default-texture.png").unwrap();
|
||||
let levels = load_all_levels(raylib_handle, thread).unwrap();
|
||||
|
||||
// Set up the state machine
|
||||
let mut machine = StateMachine::new();
|
||||
@ -57,6 +60,9 @@ 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(player_sprite_sheet, world_background))?;
|
||||
machine.add_action(
|
||||
Scenes::InGameScene,
|
||||
InGameScreen::new(player_sprite_sheet, world_background, levels),
|
||||
)?;
|
||||
Ok(machine)
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ pub struct StaticGameData;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ResourceLoadError {
|
||||
#[error(transparent)]
|
||||
JsonDeser(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Could not load embedded asset: {0}")]
|
||||
|
Reference in New Issue
Block a user