Merge remote-tracking branch 'origin/player_cleanup' into ouchies

This commit is contained in:
Evan Pratten 2021-04-24 15:47:19 -04:00
commit 79bae68f71
15 changed files with 291 additions and 92 deletions

1
assets/img/map/cave.json Normal file

File diff suppressed because one or more lines are too long

BIN
assets/img/map/cave.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

View File

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 894 B

View File

@ -3,6 +3,10 @@
"x": 10000.0, "x": 10000.0,
"y": 10000.0 "y": 10000.0
}, },
"player_spawn": {
"x": 220.0,
"y": 50.0
},
"fish": [ "fish": [
{ {
"x": 500.0, "x": 500.0,

View File

@ -31,10 +31,10 @@ impl fmt::Display for GameState {
#[derive(Debug, Serialize, Deserialize, Default)] #[derive(Debug, Serialize, Deserialize, Default)]
pub struct GameProgress { pub struct GameProgress {
coins: u32, pub coins: u32,
max_depth: f32, pub max_depth: f32,
fastest_time: Option<f64>, pub fastest_time: Option<f64>,
inventory: Vec<ShopItems>, pub inventory: Vec<ShopItems>,
} }
impl GameProgress { impl GameProgress {
@ -44,6 +44,7 @@ impl GameProgress {
} }
} }
pub fn from_file(file: String) -> Result<Self, Error> { pub fn from_file(file: String) -> Result<Self, Error> {
// Load the file // Load the file
let file = File::open(file)?; let file = File::open(file)?;
@ -107,6 +108,7 @@ impl GameCore {
world: World, world: World,
progress: GameProgress, progress: GameProgress,
) -> Self { ) -> Self {
let player = Player::new(&world.player_spawn);
Self { Self {
state: GameState::Loading, state: GameState::Loading,
last_state: GameState::Loading, last_state: GameState::Loading,
@ -117,13 +119,13 @@ impl GameCore {
.expect("Failed to load game assets. Can not launch!"), .expect("Failed to load game assets. Can not launch!"),
master_camera: Camera2D { master_camera: Camera2D {
offset: Vector2::zero(), offset: Vector2::zero(),
target: Vector2::zero(), target: world.player_spawn,
rotation: 0.0, rotation: 0.0,
zoom: 2.0, zoom: 2.0,
}, },
show_simple_debug_info: false, show_simple_debug_info: false,
world: world, world: world,
player: Player::new(), player,
progress: progress, progress: progress,
} }
} }

View File

@ -1,3 +1,4 @@
use raylib::math::Vector2;
use serde_json::json; use serde_json::json;
use serialstudio::{ use serialstudio::{
data::{DataGroup, DataSet, TelemetryFrame}, data::{DataGroup, DataSet, TelemetryFrame},
@ -21,7 +22,8 @@ pub struct ProfilerData {
// Player // Player
pub player_coins: u32, pub player_coins: u32,
pub player_boost_percent: f32, pub player_boost_percent: f32,
pub player_breath_percent: f32 pub player_breath_percent: f32,
pub player_pose: Vector2
} }
/// The development profiler /// The development profiler
@ -147,6 +149,20 @@ impl GameProfiler {
unit: Some("%".to_string()), unit: Some("%".to_string()),
w_type: None, w_type: None,
}, },
DataSet {
title: Some("X".to_string()),
value: json!(self.data.player_pose.x),
graph: Some(false),
unit: Some("pixels".to_string()),
w_type: None,
},
DataSet {
title: Some("Y".to_string()),
value: json!(self.data.player_pose.y),
graph: Some(false),
unit: Some("pixels".to_string()),
w_type: None,
},
], ],
}, },
], ],

View File

@ -8,13 +8,7 @@ pub fn render_hud(
window_center: Vector2, window_center: Vector2,
) { ) {
// Get the relevant data // Get the relevant data
let dist_from_player_to_end = game_core let progress = game_core.player.calculate_depth_percent(&game_core.world);
.player
.position
.distance_to(game_core.world.end_position);
let dist_from_start_to_end = Vector2::zero().distance_to(game_core.world.end_position);
let progress = ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end)
.clamp(0.0, 1.0);
// Determine the progress slider position // Determine the progress slider position
let slider_bound_height = 20.0; let slider_bound_height = 20.0;

View File

@ -6,6 +6,7 @@ use raylib::prelude::*;
use crate::{ use crate::{
gamecore::{GameCore, GameState}, gamecore::{GameCore, GameState},
lib::wrappers::audio::player::AudioPlayer, lib::wrappers::audio::player::AudioPlayer,
pallette::{SKY, WATER},
}; };
use super::screen::Screen; use super::screen::Screen;
@ -31,7 +32,48 @@ impl InGameScreen {
context_2d: &mut RaylibMode2D<RaylibDrawHandle>, context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
game_core: &mut GameCore, game_core: &mut GameCore,
) { ) {
context_2d.draw_circle(0, 0, 10.0, Color::BLACK); // Build source bounds
let source_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: game_core.resources.cave_mid_layer.width as f32,
height: game_core.resources.cave_mid_layer.height as f32,
};
let world_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: game_core.resources.cave_mid_layer.width as f32,
height: game_core.resources.cave_mid_layer.height as f32,
};
// Clear the background
context_2d.draw_rectangle_rec(world_bounds, WATER);
// Render the world texture
context_2d.draw_texture_rec(
&game_core.resources.cave_mid_layer,
source_bounds,
Vector2 {
x: world_bounds.x,
y: world_bounds.y,
},
Color::WHITE,
);
}
fn render_colliders(
&mut self,
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
game_core: &mut GameCore,
) {
// Render every collider
for collider in game_core.world.colliders.iter() {
context_2d.draw_rectangle_lines_ex(
collider,
1,
Color::RED,
);
}
} }
} }
@ -47,7 +89,7 @@ impl Screen for InGameScreen {
let dt = draw_handle.get_time() - game_core.last_frame_time; let dt = draw_handle.get_time() - game_core.last_frame_time;
// Clear frame // Clear frame
draw_handle.clear_background(Color::BLUE); draw_handle.clear_background(Color::BLACK);
// Handle the pause menu being opened // Handle the pause menu being opened
if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) { if draw_handle.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
@ -72,6 +114,9 @@ impl Screen for InGameScreen {
// Render the world // Render the world
self.render_world(&mut context_2d, game_core); self.render_world(&mut context_2d, game_core);
if game_core.show_simple_debug_info{
self.render_colliders(&mut context_2d, game_core);
}
// Render entities // Render entities
let fish_clone = game_core.world.fish.clone(); let fish_clone = game_core.world.fish.clone();
@ -81,7 +126,8 @@ impl Screen for InGameScreen {
} }
// Render Player // Render Player
playerlogic::render_player(&mut context_2d, game_core); // playerlogic::render_player(&mut context_2d, game_core);
game_core.player.render(&mut context_2d, &mut game_core.resources);
} }
// Render the hud // Render the hud

View File

@ -129,8 +129,20 @@ pub fn update_player_movement(
// Only do this if the mouse is far enough away // Only do this if the mouse is far enough away
let player_real_movement = game_core.player.direction * speed_multiplier; let player_real_movement = game_core.player.direction * speed_multiplier;
if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 { if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 {
game_core.player.position += player_real_movement;
game_core.player.is_moving = true; game_core.player.is_moving = true;
game_core.player.position += player_real_movement;
// Check for any collisions
for collider in game_core.world.colliders.iter() {
if game_core.player.collides_with_rec(collider) {
game_core.player.is_moving = false;
break;
}
}
if !game_core.player.is_moving {
game_core.player.position -= player_real_movement;
}
} else { } else {
game_core.player.is_moving = false; game_core.player.is_moving = false;
} }
@ -142,65 +154,18 @@ pub fn update_player_movement(
draw_handle.get_world_to_screen2D(game_core.player.position, game_core.master_camera); draw_handle.get_world_to_screen2D(game_core.player.position, game_core.master_camera);
// Camera only moves if you get close to the edge of the screen // Camera only moves if you get close to the edge of the screen
if player_screen_position.distance_to(window_center).abs() > (window_center.y - 40.0) { if player_screen_position.distance_to(window_center).abs() > 100.0 {
game_core.master_camera.target += player_real_movement; game_core.master_camera.target += player_real_movement;
} }
}
pub fn render_player(context_2d: &mut RaylibMode2D<RaylibDrawHandle>, game_core: &mut GameCore) { // If the player is not on screen, snap the camera to them
// Get the player if player_screen_position.distance_to(window_center).abs() > window_center.y {
let player = &game_core.player; game_core.master_camera.target = game_core.player.position - (window_center / 2.0);
// Convert the player direction to a rotation
let player_rotation = Vector2::zero().angle_to(player.direction);
// Render the player's boost ring
// This functions both as a breath meter, and as a boost meter
let boost_ring_max_radius = player.size.x + 5.0;
context_2d.draw_circle(
player.position.x as i32,
player.position.y as i32,
boost_ring_max_radius * player.boost_percent,
TRANSLUCENT_WHITE_64,
);
context_2d.draw_ring(
Vector2 {
x: player.position.x as i32 as f32,
y: player.position.y as i32 as f32,
},
boost_ring_max_radius,
boost_ring_max_radius + 1.0,
0,
(360.0 * player.breath_percent) as i32,
0,
TRANSLUCENT_WHITE_96,
);
// Render the player based on what is happening
if player.is_boost_charging {
game_core.resources.player_animation_boost_charge.draw(
context_2d,
player.position,
player_rotation.to_degrees() - 90.0,
);
} else if player.is_boosting {
game_core.resources.player_animation_boost.draw(
context_2d,
player.position,
player_rotation.to_degrees() - 90.0,
);
} else if player.is_moving {
game_core.resources.player_animation_regular.draw(
context_2d,
player.position,
player_rotation.to_degrees() - 90.0,
);
} else {
game_core.resources.player_animation_regular.draw_frame(
context_2d,
player.position,
player_rotation.to_degrees() - 90.0,
0,
);
} }
// // Clamp camera target y to 0
// if game_core.master_camera.target.y < -100.0 {
// game_core.master_camera.target.y = -100.0;
// }
} }

View File

@ -7,7 +7,7 @@ use crate::{
use super::screen::Screen; use super::screen::Screen;
const SCREEN_PANEL_SIZE: Vector2 = Vector2 { x: 300.0, y: 300.0 }; const SCREEN_PANEL_SIZE: Vector2 = Vector2 { x: 300.0, y: 380.0 };
pub struct PauseMenuScreen {} pub struct PauseMenuScreen {}
@ -118,6 +118,15 @@ impl Screen for PauseMenuScreen {
} }
} }
// Render credits
draw_handle.draw_text(
"Credits:\n\t- @ewpratten\n\t- @rsninja722\n\t- @wm-c\n\t- @catarinaburghi",
(win_width / 2) - (SCREEN_PANEL_SIZE.x as i32 / 2) + 10,
(win_height / 2) - (SCREEN_PANEL_SIZE.y as i32 / 2) + 120,
20,
Color::BLACK,
);
// Close and quit buttons // Close and quit buttons
let bottom_left_button_dimensions = Rectangle { let bottom_left_button_dimensions = Rectangle {
x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0, x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0,

View File

@ -13,7 +13,7 @@ use lib::{utils::profiler::GameProfiler, wrappers::audio::player::AudioPlayer};
use log::info; use log::info;
use logic::{gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen}; use logic::{gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen};
use raylib::prelude::*; use raylib::prelude::*;
use world::World; use world::{World, load_world_colliders};
// Game Launch Configuration // Game Launch Configuration
const DEFAULT_WINDOW_DIMENSIONS: Vector2 = Vector2 { const DEFAULT_WINDOW_DIMENSIONS: Vector2 = Vector2 {
@ -32,7 +32,7 @@ fn main() {
.size( .size(
DEFAULT_WINDOW_DIMENSIONS.x as i32, DEFAULT_WINDOW_DIMENSIONS.x as i32,
DEFAULT_WINDOW_DIMENSIONS.y as i32, DEFAULT_WINDOW_DIMENSIONS.y as i32,
) ).msaa_4x()
.title(WINDOW_TITLE) .title(WINDOW_TITLE)
.build(); .build();
raylib.set_target_fps(MAX_FPS); raylib.set_target_fps(MAX_FPS);
@ -41,7 +41,8 @@ fn main() {
raylib.set_exit_key(None); raylib.set_exit_key(None);
// Load the world // Load the world
let world = World::load_from_json("./assets/worlds/mainworld.json".to_string()).expect("Failed to load main world JSON"); let world_colliders = load_world_colliders("./assets/img/map/cave.json".to_string()).expect("Failed to load world colliders");
let world = World::load_from_json("./assets/worlds/mainworld.json".to_string(), world_colliders).expect("Failed to load main world JSON");
// Load the game progress // Load the game progress
let game_progress = GameProgress::try_from_file("./assets/savestate.json".to_string()); let game_progress = GameProgress::try_from_file("./assets/savestate.json".to_string());
@ -133,6 +134,7 @@ fn main() {
profiler.data.player_coins = game_core.player.coins; profiler.data.player_coins = game_core.player.coins;
profiler.data.player_boost_percent = game_core.player.boost_percent; profiler.data.player_boost_percent = game_core.player.boost_percent;
profiler.data.player_breath_percent = game_core.player.breath_percent; profiler.data.player_breath_percent = game_core.player.breath_percent;
profiler.data.player_pose = game_core.player.position;
// Send telemetry data // Send telemetry data
profiler.update(); profiler.update();

View File

@ -20,3 +20,17 @@ pub const TRANSLUCENT_WHITE_64: Color = Color {
b: 255, b: 255,
a: 64, a: 64,
}; };
pub const SKY: Color = Color {
r: 15,
g: 193,
b: 217,
a: 255
};
pub const WATER: Color = Color {
r: 24,
g: 66,
b: 143,
a: 255
};

View File

@ -1,6 +1,13 @@
use raylib::math::Vector2; use raylib::prelude::*;
use crate::{
gamecore::{GameCore, GameProgress},
items::ShopItems,
lib::utils::triangles::rotate_vector,
pallette::{TRANSLUCENT_WHITE_64, TRANSLUCENT_WHITE_96},
resources::GlobalResources,
world::World,
};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Player { pub struct Player {
@ -12,20 +19,127 @@ pub struct Player {
pub breath_percent: f32, pub breath_percent: f32,
pub is_moving: bool, pub is_moving: bool,
pub is_boosting: bool, pub is_boosting: bool,
pub is_boost_charging: bool pub is_boost_charging: bool,
pub inventory: Vec<ShopItems>,
} }
impl Player { impl Player {
pub fn new() -> Self { pub fn new(spawn: &Vector2) -> Self {
Self { Self {
boost_percent: 1.0, boost_percent: 1.0,
size: Vector2 { size: Vector2 { x: 11.0, y: 21.0 },
x: 11.0,
y: 21.0
},
breath_percent: 1.0, breath_percent: 1.0,
position: spawn.clone(),
..Default::default() ..Default::default()
}
}
pub fn collides_with_rec(&self, rectangle: &Rectangle) -> bool {
// // Build a bounding box of the player by their corners
// let top_left_corner = self.position - (self.size / 2.0);
// let bottom_right_corner = self.position + (self.size / 2.0);
// let top_right_corner = Vector2 {
// x: bottom_right_corner.x,
// y: top_left_corner.y,
// };
// let bottom_left_corner = Vector2 {
// x: top_left_corner.x,
// y: bottom_right_corner.y,
// };
// // Get the rotation
// let rotation = Vector2::zero().angle_to(self.direction);
// // Rotate the bounds
// let top_left_corner = rotate_vector(top_left_corner, rotation);
// let bottom_right_corner = rotate_vector(bottom_right_corner, rotation);
// let top_right_corner = rotate_vector(top_right_corner, rotation);
// let bottom_left_corner = rotate_vector(bottom_left_corner, rotation);
// // Check for collisions
// return rectangle.check_collision_point_rec(top_left_corner)
// || rectangle.check_collision_point_rec(bottom_right_corner)
// || rectangle.check_collision_point_rec(top_right_corner)
// || rectangle.check_collision_point_rec(bottom_left_corner);
return rectangle.check_collision_circle_rec(self.position, (self.size.y * 0.5) / 2.0);
}
/// Calculate how far the player is
pub fn calculate_depth_percent(&self, world: &World) -> f32 {
let dist_from_player_to_end = self.position.distance_to(world.end_position);
let dist_from_start_to_end = Vector2::zero().distance_to(world.end_position);
return ((dist_from_start_to_end - dist_from_player_to_end) / dist_from_start_to_end)
.clamp(0.0, 1.0);
}
/// Create GameProgress from the current life
pub fn create_statistics(&self, game_core: &GameCore, current_time: f64) -> GameProgress {
GameProgress {
coins: self.coins,
inventory: self.inventory.clone(),
max_depth: self.calculate_depth_percent(&game_core.world),
fastest_time: Some(current_time - game_core.last_state_change_time),
}
}
/// Render the player
pub fn render(
&self,
context_2d: &mut RaylibMode2D<RaylibDrawHandle>,
resources: &mut GlobalResources,
) {
// Convert the player direction to a rotation
let player_rotation = Vector2::zero().angle_to(self.direction);
// Render the player's boost ring
// This functions both as a breath meter, and as a boost meter
let boost_ring_max_radius = self.size.x + 5.0;
context_2d.draw_circle(
self.position.x as i32,
self.position.y as i32,
boost_ring_max_radius * self.boost_percent,
TRANSLUCENT_WHITE_64,
);
context_2d.draw_ring(
Vector2 {
x: self.position.x as i32 as f32,
y: self.position.y as i32 as f32,
},
boost_ring_max_radius,
boost_ring_max_radius + 1.0,
0,
(360.0 * self.breath_percent) as i32,
0,
TRANSLUCENT_WHITE_96,
);
// Render the player based on what is happening
if self.is_boost_charging {
resources.player_animation_boost_charge.draw(
context_2d,
self.position,
player_rotation.to_degrees() - 90.0,
);
} else if self.is_boosting {
resources.player_animation_boost.draw(
context_2d,
self.position,
player_rotation.to_degrees() - 90.0,
);
} else if self.is_moving {
resources.player_animation_regular.draw(
context_2d,
self.position,
player_rotation.to_degrees() - 90.0,
);
} else {
resources.player_animation_regular.draw_frame(
context_2d,
self.position,
player_rotation.to_degrees() - 90.0,
0,
);
} }
} }
} }

View File

@ -16,6 +16,9 @@ pub struct GlobalResources {
pub player_animation_regular: FrameAnimationWrapper, pub player_animation_regular: FrameAnimationWrapper,
pub player_animation_boost_charge: FrameAnimationWrapper, pub player_animation_boost_charge: FrameAnimationWrapper,
pub player_animation_boost: FrameAnimationWrapper, pub player_animation_boost: FrameAnimationWrapper,
// Cave
pub cave_mid_layer: Texture2D
} }
impl GlobalResources { impl GlobalResources {
@ -56,6 +59,10 @@ impl GlobalResources {
21, 21,
30, 30,
), ),
cave_mid_layer: raylib.load_texture_from_image(
&thread,
&Image::load_image("./assets/img/map/cave.png")?,
)?,
}) })
} }
} }

View File

@ -1,6 +1,6 @@
use std::{fs::File, io::BufReader}; use std::{fs::File, io::BufReader};
use raylib::math::Vector2; use raylib::math::{Rectangle, Vector2};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::Read; use std::io::Read;
use failure::Error; use failure::Error;
@ -10,16 +10,20 @@ use crate::entities::fish::FishEntity;
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct World { pub struct World {
pub end_position: Vector2, pub end_position: Vector2,
pub player_spawn: Vector2,
#[serde(rename = "fish")] #[serde(rename = "fish")]
pub fish_positions: Vec<Vector2>, pub fish_positions: Vec<Vector2>,
#[serde(skip)] #[serde(skip)]
pub fish: Vec<FishEntity> pub fish: Vec<FishEntity>,
#[serde(skip)]
pub colliders: Vec<Rectangle>
} }
impl World { impl World {
pub fn load_from_json(file: String) -> Result<Self, Error> { pub fn load_from_json(file: String, colliders: Vec<Rectangle>) -> Result<Self, Error> {
// Load the file // Load the file
let file = File::open(file)?; let file = File::open(file)?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
@ -30,6 +34,17 @@ impl World {
// Init all fish // Init all fish
result.fish = FishEntity::new_from_positions(&result.fish_positions); result.fish = FishEntity::new_from_positions(&result.fish_positions);
// Init colliders
result.colliders = Vec::new();
for collider in colliders.iter(){
result.colliders.push(Rectangle {
x: collider.x - (collider.width / 2.0),
y: collider.y - (collider.height / 2.0),
width: collider.width,
height: collider.height,
});
}
Ok(result) Ok(result)
} }
@ -45,3 +60,13 @@ impl World {
} }
} }
} }
pub fn load_world_colliders(file: String) -> Result<Vec<Rectangle>, Error> {
// Load the file
let file = File::open(file)?;
let reader = BufReader::new(file);
// Deserialize
Ok(serde_json::from_reader(reader)?)
}