diff --git a/game/Cargo.toml b/game/Cargo.toml index 6bc0386..27126cb 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -16,8 +16,8 @@ serde_json = "1.0.64" thiserror = "1.0" chrono = "0.4" rust-embed = "6.2.0" -raylib = { version = "3.5", git = "https://github.com/ewpratten/raylib-rs", rev = "2399e17d7bf299f34c8e618a9ab140b274639cfb", features = [ - "with_serde" +raylib = { version = "3.5", git = "https://github.com/ewpratten/raylib-rs", rev = "2ae949cb3488dd1bb052ece71d61021c8dd6e910", features = [ + "serde" ] } puffin = "0.9" puffin_http = "0.6" @@ -31,6 +31,7 @@ pkg-version = "1.0" cfg-if = "1.0" num-derive = "0.3" num = "0.4" +tiled = { version ="0.9.5", default-features = false } [dev-dependencies] puffin_viewer = "0.6" diff --git a/game/assets/configs/application.json b/game/assets/configs/application.json index f69c9c0..0bab565 100644 --- a/game/assets/configs/application.json +++ b/game/assets/configs/application.json @@ -1,5 +1,46 @@ { "name": "Unnamed game", - "base_window_size": [1080, 720], - "sentry_dsn": "https://d5d94e75f08841388287fa0c23606ac7@o398481.ingest.sentry.io/5985679" + "base_window_size": [ + 1080, + 720 + ], + "sentry_dsn": "https://d5d94e75f08841388287fa0c23606ac7@o398481.ingest.sentry.io/5985679", + "colors": { + "red": [ + 240, + 70, + 53, + 255 + ], + "blue": [ + 101, + 75, + 250, + 255 + ], + "green": [ + 61, + 227, + 161, + 255 + ], + "yellow": [ + 250, + 235, + 55, + 255 + ], + "pink": [ + 240, + 246, + 227, + 255 + ], + "background": [ + 20, + 20, + 20, + 255 + ] + } } diff --git a/game/assets/configs/final_shader.json b/game/assets/configs/final_shader.json index d9b2530..f652f87 100644 --- a/game/assets/configs/final_shader.json +++ b/game/assets/configs/final_shader.json @@ -1,5 +1,7 @@ { "pixel_scale": 1.0, - "warp_factor": 0.5, - "scanline_darkness": 0.5 + "warp_factor": 0.65, + "scanline_darkness": 0.55, + "bloom_samples": 5.0, + "bloom_quality": 2.5 } diff --git a/game/assets/shaders/pixelart.fs b/game/assets/shaders/pixelart.fs index 33ee27b..c7b4846 100644 --- a/game/assets/shaders/pixelart.fs +++ b/game/assets/shaders/pixelart.fs @@ -1,5 +1,6 @@ /** - * This shader is the last piece of the graphics pipeline. EVERYTHING is passed through it. + * This shader is the last piece of the graphics pipeline. EVERYTHING is passed + * through it. */ #version 330 @@ -9,6 +10,7 @@ in vec2 fragTexCoord; // Whole input texture uniform sampler2D texture0; +uniform vec4 colDiffuse; // Viewport size uniform vec2 viewport; @@ -23,6 +25,10 @@ uniform vec2 pixelScale; uniform float warpFactor; uniform float scanlineDarkness; +// Bloom parameters +uniform float bloomSamples; +uniform float bloomQuality; + void main() { // Calculate the distance to merge pixels float dx = pixelScale.x * (1.0 / viewport.x); @@ -38,20 +44,43 @@ void main() { // Calculate a UV for this new blocky pixel vec2 pixelatedUV = vec2(dx * floor(baseUV.x / dx), dy * floor(baseUV.y / dy)); + // --- BEGIN CRT SHADER --- + // Warp the UVs of the pixelated texture vec2 warpedUV = pixelatedUV; - warpedUV.x -= 0.5; warpedUV.x *= 1.0+(dist_center_sq.y * (0.3 * warpFactor)); warpedUV.x += 0.5; - warpedUV.y -= 0.5; warpedUV.y *= 1.0+(dist_center_sq.x * (0.4 * warpFactor)); warpedUV.y += 0.5; + warpedUV.x -= 0.5; + warpedUV.x *= 1.0 + (dist_center_sq.y * (0.3 * warpFactor)); + warpedUV.x += 0.5; + warpedUV.y -= 0.5; + warpedUV.y *= 1.0 + (dist_center_sq.x * (0.4 * warpFactor)); + warpedUV.y += 0.5; // If the UV is outside the texture, return black - if (warpedUV.x < 0.0 || warpedUV.x > 1.0 || warpedUV.y < 0.0 || warpedUV.y > 1.0) { + if (warpedUV.x < 0.0 || warpedUV.x > 1.0 || warpedUV.y < 0.0 || + warpedUV.y > 1.0) { finalColor = vec4(0.0, 0.0, 0.0, 1.0); return; } + // --- BEGIN BLOOM EFFECT --- + + vec2 sizeFactor = vec2(1) / viewport * bloomQuality; + vec4 textureSum = vec4(0); + + const int range = 2; + for (int x = -range; x <= range; x++) { + for (int y = -range; y <= range; y++) { + textureSum += texture(texture0, warpedUV + vec2(x, y) * sizeFactor); + } + } + // Determine factor of if we are rendering on a scanline - float scanlineFactor = abs(sin(fragTexCoord.y * viewport.y) * 0.5 * scanlineDarkness); + float scanlineFactor = + abs(sin(fragTexCoord.y * viewport.y) * 0.5 * scanlineDarkness); // Build the final pixel - finalColor = vec4(mix(texture(texture0, warpedUV).rgb, vec3(0.0), scanlineFactor), 1.0); + vec4 texWithBloom = + ((textureSum / (bloomSamples * bloomSamples)) + texture(texture0, warpedUV)) * colDiffuse; + finalColor = vec4( + mix(texWithBloom.rgb, vec3(0.0), scanlineFactor), 1.0); } diff --git a/game/src/character/mod.rs b/game/src/character/mod.rs index 2535864..cd893f7 100644 --- a/game/src/character/mod.rs +++ b/game/src/character/mod.rs @@ -5,12 +5,15 @@ use raylib::math::Vector2; #[derive(Debug, Clone)] pub struct MainCharacter { pub position: Vector2, + pub size: Vector2, } impl MainCharacter { - pub fn new(position: Vector2) -> Self { - Self { position } + Self { + position, + size: Vector2::new(60.0, 80.0), + } } pub fn apply_force(&mut self, force: Vector2) { diff --git a/game/src/character/render.rs b/game/src/character/render.rs index bf29cf8..5b7d241 100644 --- a/game/src/character/render.rs +++ b/game/src/character/render.rs @@ -1,3 +1,5 @@ +use std::ops::{Div, Sub}; + use raylib::prelude::*; use crate::utilities::non_ref_raylib::HackedRaylibHandle; @@ -9,5 +11,5 @@ pub fn render_character_in_camera_space( player: &MainCharacter, ) { - raylib.draw_rectangle_v(player.position, Vector2::new(10.0, 20.0), Color::WHITE); + raylib.draw_rectangle_v(player.position.sub(player.size.div(2.0)), player.size, Color::WHITE); } diff --git a/game/src/context.rs b/game/src/context.rs index 54e1d4f..27fe726 100644 --- a/game/src/context.rs +++ b/game/src/context.rs @@ -1,19 +1,19 @@ use std::cell::RefCell; -use crate::utilities::non_ref_raylib::HackedRaylibHandle; +use crate::{GameConfig, utilities::non_ref_raylib::HackedRaylibHandle}; #[derive(Debug)] pub struct GameContext { - pub renderer: RefCell - + pub renderer: RefCell, + pub config: GameConfig } -impl GameContext { - /// Construct a new game context. - pub fn new(raylib: RefCell) -> Self { - Self { - renderer: raylib - } - } -} +// impl GameContext { +// /// Construct a new game context. +// pub fn new(raylib: RefCell) -> Self { +// Self { +// renderer: raylib +// } +// } +// } diff --git a/game/src/lib.rs b/game/src/lib.rs index 65ecea6..96fc5a2 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -158,7 +158,10 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box Result<(), Box Result<(), Box for FsmErrorScreen { context: &GameContext, ) -> Result, ScreenError> { trace!("execute() called on FsmErrorScreen"); - self.render_screen_space(&mut context.renderer.borrow_mut()); + self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); Ok(ActionFlag::Continue) } @@ -49,7 +48,7 @@ impl Action for FsmErrorScreen { } impl ScreenSpaceRender for FsmErrorScreen { - fn render_screen_space(&self, raylib: &mut HackedRaylibHandle) { + fn render_screen_space(&self, raylib: &mut HackedRaylibHandle, config: &GameConfig) { raylib.clear_background(Color::RED); // Render a warning message diff --git a/game/src/scenes/ingame_scene/hud.rs b/game/src/scenes/ingame_scene/hud.rs index 60dc8eb..51caa68 100644 --- a/game/src/scenes/ingame_scene/hud.rs +++ b/game/src/scenes/ingame_scene/hud.rs @@ -1,4 +1,4 @@ -use crate::utilities::render_layer::ScreenSpaceRender; +use crate::{GameConfig, utilities::render_layer::ScreenSpaceRender}; use raylib::prelude::*; use super::InGameScreen; @@ -6,6 +6,7 @@ impl ScreenSpaceRender for InGameScreen { fn render_screen_space( &self, raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle, + config: &GameConfig ) { // Calculate the logo position let screen_size = raylib.get_screen_size(); diff --git a/game/src/scenes/ingame_scene/mod.rs b/game/src/scenes/ingame_scene/mod.rs index 934785b..3d70c6f 100644 --- a/game/src/scenes/ingame_scene/mod.rs +++ b/game/src/scenes/ingame_scene/mod.rs @@ -1,7 +1,11 @@ use dirty_fsm::{Action, ActionFlag}; use raylib::prelude::*; -use crate::{character::MainCharacter, context::GameContext, utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}}; +use crate::{ + character::MainCharacter, + context::GameContext, + utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}, +}; use super::{Scenes, ScreenError}; use tracing::{debug, trace}; @@ -13,7 +17,7 @@ mod world; #[derive(Debug)] pub struct InGameScreen { camera: Camera2D, - player: MainCharacter + player: MainCharacter, } impl InGameScreen { @@ -26,7 +30,7 @@ impl InGameScreen { rotation: 0.0, zoom: 1.0, }, - player: MainCharacter::new(Vector2::zero()), + player: MainCharacter::new(Vector2::new(0.0, -45.0)), } } } @@ -54,10 +58,10 @@ impl Action for InGameScreen { let mut renderer = context.renderer.borrow_mut(); // Update the inputs and checking logic - self.update(&mut renderer, delta); + self.update(&mut renderer, delta, &context.config); // Wipe the background - renderer.clear_background(Color::BLACK); + renderer.clear_background(context.config.colors.background); // Render the world { @@ -65,11 +69,11 @@ impl Action for InGameScreen { let mut raylib_camera_space = renderer.begin_mode2D(self.camera); // Render in world space - self.render_world_space(&mut raylib_camera_space); + self.render_world_space(&mut raylib_camera_space, &context.config); } // Render the HUD - self.render_screen_space(&mut renderer); + self.render_screen_space(&mut renderer, &context.config); Ok(ActionFlag::Continue) } diff --git a/game/src/scenes/ingame_scene/update.rs b/game/src/scenes/ingame_scene/update.rs index 278da4b..0670b3a 100644 --- a/game/src/scenes/ingame_scene/update.rs +++ b/game/src/scenes/ingame_scene/update.rs @@ -1,14 +1,19 @@ use std::ops::Div; use super::InGameScreen; -use crate::utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate}; +use crate::{GameConfig, utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate}}; use chrono::Duration; use raylib::prelude::*; impl FrameUpdate for InGameScreen { - fn update(&mut self, raylib: &HackedRaylibHandle, delta_seconds: &Duration) { + 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(2.0); + 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, + ); // Check the only possible keyboard inputs let is_jump = raylib.is_key_down(KeyboardKey::KEY_SPACE); diff --git a/game/src/scenes/ingame_scene/world.rs b/game/src/scenes/ingame_scene/world.rs index 84b8c85..a722b8b 100644 --- a/game/src/scenes/ingame_scene/world.rs +++ b/game/src/scenes/ingame_scene/world.rs @@ -1,13 +1,25 @@ +use std::ops::Mul; + use super::InGameScreen; -use crate::{ - character::render::render_character_in_camera_space, - utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::WorldSpaceRender}, -}; +use crate::{GameConfig, character::render::render_character_in_camera_space, utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::WorldSpaceRender}}; use raylib::prelude::*; impl WorldSpaceRender for InGameScreen { - fn render_world_space(&self, raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>) { + fn render_world_space(&self, raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>, + config: &GameConfig) { // Render the player render_character_in_camera_space(raylib, &self.player); + + // 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); + + raylib.draw_rectangle( + screen_world_zero.x as i32, + 0, + screen_world_size.x as i32, + 5, + Color::WHITE, + ); } } diff --git a/game/src/scenes/loading_screen.rs b/game/src/scenes/loading_screen.rs index 1c1409f..0ccc70b 100644 --- a/game/src/scenes/loading_screen.rs +++ b/game/src/scenes/loading_screen.rs @@ -1,18 +1,16 @@ use std::ops::{Div, Sub}; +use cfg_if::cfg_if; use chrono::{DateTime, Utc}; use dirty_fsm::{Action, ActionFlag}; use raylib::prelude::*; -use crate::{ - context::GameContext, - utilities::{ +use crate::{GameConfig, context::GameContext, utilities::{ datastore::{load_texture_from_internal_data, ResourceLoadError}, math::interpolate_exp, non_ref_raylib::HackedRaylibHandle, render_layer::ScreenSpaceRender, - }, -}; + }}; use super::{Scenes, ScreenError}; use tracing::{debug, info, trace}; @@ -66,12 +64,22 @@ impl Action for LoadingScreen { context: &GameContext, ) -> Result, ScreenError> { trace!("execute() called on LoadingScreen"); - self.render_screen_space(&mut context.renderer.borrow_mut()); + self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); + + // Check for a quick skip button in debug builds + cfg_if! { + if #[cfg(debug_assertions)] { + let debug_skip_screen = context.renderer.borrow_mut().is_key_pressed(KeyboardKey::KEY_ESCAPE); + } else { + let debug_skip_screen = false; + } + } // Keep rendering until we pass the loading screen duration if let Some(start_timestamp) = self.start_timestamp { let duration = Utc::now().signed_duration_since(start_timestamp); - if duration.num_seconds() >= LOADING_SCREEN_DURATION_SECONDS as i64 { + if duration.num_seconds() >= LOADING_SCREEN_DURATION_SECONDS as i64 || debug_skip_screen + { info!("LoadingScreen duration reached, moving to next screen"); Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen)) } else { @@ -96,6 +104,7 @@ impl ScreenSpaceRender for LoadingScreen { fn render_screen_space( &self, raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle, + config: &GameConfig ) { // Calculate the loading screen fade in/out value // This makes the loading screen fade in/out over the duration of the loading screen @@ -128,7 +137,11 @@ impl ScreenSpaceRender for LoadingScreen { // Only in debug mode, render a debug message #[cfg(debug_assertions)] { - raylib.draw_rectangle_v(Vector2::zero(), Vector2::new(screen_size.x, 40.0), Color::RED); + raylib.draw_rectangle_v( + Vector2::zero(), + Vector2::new(screen_size.x, 40.0), + Color::RED, + ); raylib.draw_text( "Game in DEBUG MODE. Do not redistribute!", 10, diff --git a/game/src/scenes/main_menu_screen.rs b/game/src/scenes/main_menu_screen.rs index 1c68f9f..cc0f68b 100644 --- a/game/src/scenes/main_menu_screen.rs +++ b/game/src/scenes/main_menu_screen.rs @@ -5,16 +5,13 @@ use dirty_fsm::{Action, ActionFlag}; use pkg_version::pkg_version_major; use raylib::prelude::*; -use crate::{ - context::GameContext, - utilities::{ +use crate::{GameConfig, context::GameContext, utilities::{ datastore::{load_texture_from_internal_data, ResourceLoadError}, game_version::get_version_string, math::interpolate_exp, non_ref_raylib::HackedRaylibHandle, render_layer::ScreenSpaceRender, - }, -}; + }}; use super::{Scenes, ScreenError}; use tracing::{debug, info, trace}; @@ -47,7 +44,7 @@ impl Action for MainMenuScreen { context: &GameContext, ) -> Result, ScreenError> { trace!("execute() called on MainMenuScreen"); - self.render_screen_space(&mut context.renderer.borrow_mut()); + self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config); // TODO: TEMP if context.renderer.borrow_mut().is_key_pressed(KeyboardKey::KEY_SPACE) { @@ -67,6 +64,7 @@ impl ScreenSpaceRender for MainMenuScreen { fn render_screen_space( &self, raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle, + config: &GameConfig ) { // Render the background raylib.clear_background(Color::BLACK); diff --git a/game/src/utilities/game_config.rs b/game/src/utilities/game_config.rs index 25306be..8fc9147 100644 --- a/game/src/utilities/game_config.rs +++ b/game/src/utilities/game_config.rs @@ -1,6 +1,7 @@ //! Contains the general configuration data for the game //! This data is immutable, and should only be edited by hand +use raylib::color::Color; use rust_embed::EmbeddedFile; /// Defines one of the game's authors @@ -11,12 +12,22 @@ pub struct Author { pub roles: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ColorTheme { + pub red: Color, + pub blue: Color, + pub green: Color, + pub yellow: Color, + pub pink: Color, + pub background: Color, +} + #[derive(Debug, Clone, Deserialize)] pub struct GameConfig { pub name: String, - // pub authors: Vec, pub base_window_size: (i32, i32), pub sentry_dsn: String, + pub colors: ColorTheme, } impl GameConfig { @@ -31,6 +42,8 @@ pub struct FinalShaderConfig { pub pixel_scale: f32, pub warp_factor: f32, pub scanline_darkness: f32, + pub bloom_samples: f32, + pub bloom_quality: f32, } impl FinalShaderConfig { diff --git a/game/src/utilities/mod.rs b/game/src/utilities/mod.rs index 1f7c03a..a8e4323 100644 --- a/game/src/utilities/mod.rs +++ b/game/src/utilities/mod.rs @@ -1,8 +1,8 @@ -pub mod discord; pub mod datastore; +pub mod discord; pub mod game_config; +pub mod game_version; pub mod math; -pub mod shaders; pub mod non_ref_raylib; pub mod render_layer; -pub mod game_version; +pub mod shaders; diff --git a/game/src/utilities/render_layer.rs b/game/src/utilities/render_layer.rs index 963bc26..8b5710e 100644 --- a/game/src/utilities/render_layer.rs +++ b/game/src/utilities/render_layer.rs @@ -1,15 +1,15 @@ use raylib::{prelude::RaylibMode2D, RaylibHandle}; -use crate::utilities::non_ref_raylib::HackedRaylibHandle; +use crate::{GameConfig, context::GameContext, utilities::non_ref_raylib::HackedRaylibHandle}; pub trait FrameUpdate { - fn update(&mut self, raylib: &HackedRaylibHandle, delta_seconds: &chrono::Duration); + fn update(&mut self, raylib: &HackedRaylibHandle, delta_seconds: &chrono::Duration, config: &GameConfig); } pub trait ScreenSpaceRender { - fn render_screen_space(&self, raylib: &mut HackedRaylibHandle); + fn render_screen_space(&self, raylib: &mut HackedRaylibHandle, config: &GameConfig); } pub trait WorldSpaceRender { - fn render_world_space(&self, raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>); + fn render_world_space(&self, raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>, config: &GameConfig); }