Merge pull request #22 from Ewpratten/levels

Add POC level system [wip]
This commit is contained in:
Evan Pratten 2021-10-02 13:54:26 -07:00 committed by GitHub
commit c1a7125f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 204 additions and 23 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View 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
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,3 @@
[
"level_0"
]

View File

@ -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;

View File

@ -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();
}
}

View 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)
}

View 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>
}

View File

@ -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(())
}

View File

@ -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,);
}
}
}

View File

@ -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);

View File

@ -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)
}

View File

@ -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}")]