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 raylib::math::{Rectangle, Vector2};
|
||||||
|
|
||||||
|
use crate::scenes::ingame_scene::world::WORLD_LEVEL_X_OFFSET;
|
||||||
|
|
||||||
use super::{CharacterState, MainCharacter};
|
use super::{CharacterState, MainCharacter};
|
||||||
|
|
||||||
pub const GRAVITY_PPS: f32 = 2.0;
|
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
|
// Modify the player's velocity by the forces
|
||||||
player.movement_force += player.base_velocity;
|
player.movement_force += player.base_velocity;
|
||||||
player.velocity = player.movement_force;
|
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
|
// Predict the player's position next frame
|
||||||
let predicted_player_position = player.position + player.velocity;
|
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(
|
let player_rect = Rectangle::new(
|
||||||
predicted_player_position.x - (player.size.x / 2.0),
|
predicted_player_position.x - (player.size.x / 2.0),
|
||||||
predicted_player_position.y - (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
|
// Calculate a generic "floor" to always collide with
|
||||||
let floor_rect = Rectangle::new(f32::MIN, 0.0, f32::MAX, 1.0);
|
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 the player is colliding, only apply the x force
|
||||||
if (floor_rect.check_collision_recs(&player_rect)
|
if (check_player_colliding_with_floor()
|
||||||
|| player_rect.y + player_rect.height > floor_rect.y)
|
|| check_player_colliding_with_floor_next_frame()
|
||||||
&& player.velocity.y > 0.0
|
|| check_player_colliding_with_colliders())
|
||||||
|
&& player.velocity.y != 0.0
|
||||||
{
|
{
|
||||||
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
|
if player.current_state == CharacterState::Jumping
|
||||||
|| player.current_state == CharacterState::Dashing
|
|| player.current_state == CharacterState::Dashing
|
||||||
{
|
{
|
||||||
player.update_player(Some(CharacterState::Running));
|
player.update_player(
|
||||||
|
Some(CharacterState::Running),
|
||||||
|
colliders,
|
||||||
|
level_height_offset,
|
||||||
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check sideways collisions
|
||||||
|
|
||||||
// Finally apply the velocity to the player
|
// Finally apply the velocity to the player
|
||||||
player.position += player.velocity;
|
player.position += player.velocity;
|
||||||
|
|
||||||
|
@ -2,7 +2,10 @@ pub mod collisions;
|
|||||||
pub mod render;
|
pub mod render;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use raylib::{math::Vector2, texture::Texture2D};
|
use raylib::{
|
||||||
|
math::{Rectangle, Vector2},
|
||||||
|
texture::Texture2D,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::utilities::anim_render::AnimatedSpriteSheet;
|
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 {
|
if let Some(state) = state {
|
||||||
// Update the internal state
|
// Update the internal state
|
||||||
if state != self.current_state {
|
if state != self.current_state {
|
||||||
@ -65,6 +73,6 @@ impl MainCharacter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the player based on the new velocity
|
// 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 dirty_fsm::{Action, ActionFlag};
|
||||||
use raylib::prelude::*;
|
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 super::{Scenes, ScreenError};
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
mod hud;
|
mod hud;
|
||||||
|
pub mod level;
|
||||||
mod update;
|
mod update;
|
||||||
mod world;
|
pub mod world;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InGameScreen {
|
pub struct InGameScreen {
|
||||||
camera: Camera2D,
|
camera: Camera2D,
|
||||||
player: MainCharacter,
|
player: MainCharacter,
|
||||||
world_background: WorldPaintTexture
|
world_background: WorldPaintTexture,
|
||||||
|
levels: Vec<Level>,
|
||||||
|
current_level_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InGameScreen {
|
impl InGameScreen {
|
||||||
/// Construct a new `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 {
|
Self {
|
||||||
camera: Camera2D {
|
camera: Camera2D {
|
||||||
offset: Vector2::zero(),
|
offset: Vector2::zero(),
|
||||||
@ -27,8 +43,10 @@ impl InGameScreen {
|
|||||||
rotation: 0.0,
|
rotation: 0.0,
|
||||||
zoom: 1.0,
|
zoom: 1.0,
|
||||||
},
|
},
|
||||||
player: MainCharacter::new(Vector2::new(0.0, -80.0), player_sprite_sheet),
|
player: MainCharacter::new(Vector2::new(0.0, -85.0), player_sprite_sheet),
|
||||||
world_background: WorldPaintTexture::new(background_texture)
|
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");
|
debug!("Running InGameScreen for the first time");
|
||||||
|
|
||||||
// Set the player to running
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,11 @@ impl FrameUpdate for InGameScreen {
|
|||||||
config: &GameConfig,
|
config: &GameConfig,
|
||||||
) {
|
) {
|
||||||
puffin::profile_function!();
|
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
|
// 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.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);
|
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);
|
&& !(self.player.current_state == CharacterState::Dashing);
|
||||||
|
|
||||||
if is_jump {
|
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 {
|
} 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 {
|
} else {
|
||||||
if self.player.current_state != CharacterState::Jumping
|
if self.player.current_state != CharacterState::Jumping
|
||||||
&& self.player.current_state != CharacterState::Dashing
|
&& 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 {
|
} 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::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
|
pub const WORLD_LEVEL_X_OFFSET: f32 = 200.0;
|
||||||
|
|
||||||
impl WorldSpaceRender for InGameScreen {
|
impl WorldSpaceRender for InGameScreen {
|
||||||
fn render_world_space(
|
fn render_world_space(
|
||||||
&self,
|
&self,
|
||||||
@ -16,8 +18,14 @@ impl WorldSpaceRender for InGameScreen {
|
|||||||
) {
|
) {
|
||||||
puffin::profile_function!();
|
puffin::profile_function!();
|
||||||
|
|
||||||
|
// Get the current level
|
||||||
|
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||||
|
|
||||||
// Render the world background
|
// 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
|
// Render the floor as a line
|
||||||
let screen_world_zero = raylib.get_screen_to_world2D(Vector2::zero(), self.camera);
|
let screen_world_zero = raylib.get_screen_to_world2D(Vector2::zero(), self.camera);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use self::{
|
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,
|
main_menu_screen::MainMenuScreen,
|
||||||
};
|
};
|
||||||
use crate::{
|
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();
|
load_texture_from_internal_data(raylib_handle, thread, "character/player_run.png").unwrap();
|
||||||
let world_background =
|
let world_background =
|
||||||
load_texture_from_internal_data(raylib_handle, thread, "default-texture.png").unwrap();
|
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
|
// Set up the state machine
|
||||||
let mut machine = StateMachine::new();
|
let mut machine = StateMachine::new();
|
||||||
@ -57,6 +60,9 @@ pub fn build_screen_state_machine(
|
|||||||
LoadingScreen::new(raylib_handle, thread)?,
|
LoadingScreen::new(raylib_handle, thread)?,
|
||||||
)?;
|
)?;
|
||||||
machine.add_action(Scenes::MainMenuScreen, MainMenuScreen::new())?;
|
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)
|
Ok(machine)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ pub struct StaticGameData;
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ResourceLoadError {
|
pub enum ResourceLoadError {
|
||||||
|
#[error(transparent)]
|
||||||
|
JsonDeser(#[from] serde_json::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("Could not load embedded asset: {0}")]
|
#[error("Could not load embedded asset: {0}")]
|
||||||
|
Reference in New Issue
Block a user