From 8f950e038141c03def851eeca22bfa5ffe04d2ba Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Tue, 22 Mar 2022 12:45:28 -0400 Subject: [PATCH] wwip --- game/dist/project-constants.json | 14 ++++- game/dist/shaders/texture_render.fs | 36 +++++++++++++ game/game_logic/Cargo.toml | 2 +- .../game_logic/src/global_resource_package.rs | 29 ++++++++++ game/game_logic/src/lib.rs | 35 +++++++----- game/game_logic/src/persistent/settings.rs | 5 +- game/game_logic/src/project_constants.rs | 19 ++++++- .../src/rendering/core_renderer_sm.rs | 10 ++-- game/game_logic/src/rendering/event_loop.rs | 54 +++++++++++++++++-- .../rendering/screens/sm_failure_screen.rs | 40 +++++++++++--- game/game_logic/src/scenes/mod.rs | 22 ++++++++ 11 files changed, 231 insertions(+), 35 deletions(-) create mode 100644 game/game_logic/src/global_resource_package.rs create mode 100644 game/game_logic/src/scenes/mod.rs diff --git a/game/dist/project-constants.json b/game/dist/project-constants.json index 1ddcdee6..3064e0eb 100644 --- a/game/dist/project-constants.json +++ b/game/dist/project-constants.json @@ -4,6 +4,16 @@ 1080, 720 ], - "discord_app_id": 954413081918857276, - "target_fps": 60 + "target_fps": 60, + "discord": { + "app_id": 954413081918857276, + "artwork": { + "logo": "ld50-logo" + }, + "strings": { + "details.loading": "Watching the game load", + "details.sm_failure": "Game went FUBAR", + "details.main_menu": "In the main menu" + } + } } \ No newline at end of file diff --git a/game/dist/shaders/texture_render.fs b/game/dist/shaders/texture_render.fs index e69de29b..f677e2d0 100644 --- a/game/dist/shaders/texture_render.fs +++ b/game/dist/shaders/texture_render.fs @@ -0,0 +1,36 @@ +#version 330 + +// TODO: for now this is just a scanline shader for testing + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// NOTE: Add here your custom variables + +// NOTE: Render size values must be passed from code +const float renderWidth = 800; +const float renderHeight = 450; +float offset = 0.0; + +uniform float time; + +void main() +{ + float frequency = renderHeight/3.0; + // Scanlines method 2 + float globalPos = (fragTexCoord.y + offset) * frequency; + float wavePos = cos((fract(globalPos) - 0.5)*3.14); + + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord); + + finalColor = mix(vec4(0.0, 0.3, 0.0, 0.0), texelColor, wavePos); +} \ No newline at end of file diff --git a/game/game_logic/Cargo.toml b/game/game_logic/Cargo.toml index 48e58dd7..874f4503 100644 --- a/game/game_logic/Cargo.toml +++ b/game/game_logic/Cargo.toml @@ -20,4 +20,4 @@ rust-embed = { version = "6.2.0", features = ["compression"] } thiserror = "1.0.30" # nalgebra = { version = "0.30.1", features = ["serde"] } approx = "0.5.1" - +poll-promise = { version = "0.1.0", features = ["tokio"] } diff --git a/game/game_logic/src/global_resource_package.rs b/game/game_logic/src/global_resource_package.rs new file mode 100644 index 00000000..4a9c6ed8 --- /dev/null +++ b/game/game_logic/src/global_resource_package.rs @@ -0,0 +1,29 @@ +//! Global resources +//! +//! ## Overview +//! +//! This module contains a structure for all resources that are needed through the whole game (sounds, fonts, etc.). +//! These are automatically loaded during the first loading screen, and are then passed around the game as needed. +//! +//! ## How this is loaded +//! +//! The resources are loaded via [`asset_manager`](./asset_manager/index.html) in their own thread so we do not block the renderer. + +use poll_promise::Promise; +use raylib::{RaylibHandle, RaylibThread}; + +/// Global resource package +#[derive(Debug)] +pub struct GlobalResources {} + +impl GlobalResources { + /// Load the resources (**blocking**) + /// + /// This should not be called more than once. + pub async fn load( + raylib: &mut RaylibHandle, + rl_thread: &RaylibThread, + ) -> Self { + Self {} + } +} diff --git a/game/game_logic/src/lib.rs b/game/game_logic/src/lib.rs index 18ad31a4..a4685174 100644 --- a/game/game_logic/src/lib.rs +++ b/game/game_logic/src/lib.rs @@ -5,6 +5,20 @@ //! The main function in this module is `entrypoint()`. This is called from `desktop_wrapper` to start the game. //! //! This module also includes all the other sub-modules of the game. If you are viewing this document from the web, click on the modules below to see more info. +//! +//! ## Programming Guide +//! +//! The game code is split into two parts: the core code, and the actual game logic. +//! +//! [@ewpratten](https://github.com/ewpratten) has written most of the core code to bootstrap the game, and provide convenience functions. +//! This stuff probably won't need to be touched. +//! Most of the game logic is expected to live in `src/scenes` and `src/model` (rendering and data). +//! +//! ## Important Functions and Files +//! +//! - If you are wanting to write rendering code, check out [`process_ingame_frame`](scenes/fn.process_ingame_frame.html). +//! - If you want to have something load at the start of the game and stay in memory, check out [`GlobalResources`](global_resource_package/struct.GlobalResources.html). +//! - If you want to add data to the save state file or settings file, check out the [`persistent`](persistent/index.html) module. #![doc(issue_tracker_base_url = "https://github.com/Ewpratten/ludum-dare-50/issues/")] use crate::{ @@ -20,9 +34,11 @@ extern crate log; // For the `info!`, `warn!`, etc. macros pub(crate) mod asset_manager; pub(crate) mod discord; +pub(crate) mod global_resource_package; pub(crate) mod persistent; pub(crate) mod project_constants; pub(crate) mod rendering; +pub(crate) mod scenes; /// This is the game logic entrypoint. Despite being async, /// this is expected to block the main thread for rendering and stuff. @@ -46,23 +62,12 @@ pub async fn entrypoint(force_recreate_savefiles: bool) { .expect("Failed to parse game save state from disk. Possibly corrupt file?"); // Connect to Discord - let discord = DiscordRpcThreadHandle::new(project_constants.discord_app_id) + let discord = DiscordRpcThreadHandle::new(project_constants.discord.app_id) .await .expect("Failed to connect to Discord RPC"); let event_loop_discord_tx = discord.get_channel(); let discord_task_handle = discord.begin_thread_non_blocking(); - // Set a base activity to show in Discord - { - event_loop_discord_tx - .send(DiscordRpcSignal::ChangeDetails { - details: "Probably loading something IDK.".to_string(), - party_status: None, - }) - .await - .expect("Failed to send Discord RPC event"); - } - // Blocking call to the graphics rendering loop. rendering::event_loop::handle_graphics_blocking( |builder| { @@ -73,9 +78,11 @@ pub async fn entrypoint(force_recreate_savefiles: bool) { .height(project_constants.base_window_size.1 as i32) .width(project_constants.base_window_size.0 as i32); }, - settings.target_fps, + project_constants.target_fps, + &project_constants, event_loop_discord_tx, - ); + ) + .await; // Clean up any resources settings diff --git a/game/game_logic/src/persistent/settings.rs b/game/game_logic/src/persistent/settings.rs index 4b588d20..252bdec7 100644 --- a/game/game_logic/src/persistent/settings.rs +++ b/game/game_logic/src/persistent/settings.rs @@ -9,14 +9,13 @@ use serde::{Deserialize, Serialize}; /// Please don't add anything relating to gameplay though (no coins, health, etc.). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentGameSettings { - /// The target framerate for the game - pub target_fps: u32, + // TODO: Add data here. } // Add any default values here. impl Default for PersistentGameSettings { fn default() -> Self { - Self { target_fps: 60 } + Self {} } } diff --git a/game/game_logic/src/project_constants.rs b/game/game_logic/src/project_constants.rs index da392c85..4350a297 100644 --- a/game/game_logic/src/project_constants.rs +++ b/game/game_logic/src/project_constants.rs @@ -11,8 +11,23 @@ //! Somewhere in `lib.rs`, a call is made to load this through the `asset_manager`. //! Its all already set up, so you shouldn't have to worry about the logistics. +use std::collections::HashMap; + use serde::Deserialize; +/// Constants relating to Discord +#[derive(Debug, Deserialize)] +pub struct DiscordConstants { + /// The Discord application ID + pub app_id: i64, + + /// Artwork name mapping + pub artwork: HashMap, + + /// Strings + pub strings: HashMap, +} + /// This structure is filled with the contents of `dist/project-constants.json` at runtime #[derive(Debug, Deserialize)] pub struct ProjectConstants { @@ -22,8 +37,8 @@ pub struct ProjectConstants { /// The window size to use on launch pub base_window_size: (u32, u32), - /// The Discord application ID - pub discord_app_id: i64, + /// The Discord constants + pub discord: DiscordConstants, /// The target framerate of the game pub target_fps: u32, diff --git a/game/game_logic/src/rendering/core_renderer_sm.rs b/game/game_logic/src/rendering/core_renderer_sm.rs index d430ad6d..9758f8f4 100644 --- a/game/game_logic/src/rendering/core_renderer_sm.rs +++ b/game/game_logic/src/rendering/core_renderer_sm.rs @@ -3,14 +3,18 @@ sad_machine::state_machine! { RenderBackendStates { InitialStates { - Preload + Preload, SmFailed } FinishPreload { Preload => Loading } FinishLoading { - // TODO: Make this hand off to the main render code - Loading => SmFailed + Loading => RenderGame + } + ForceSmFailure { + Preload => SmFailed, + Loading => SmFailed, + RenderGame => SmFailed } } } diff --git a/game/game_logic/src/rendering/event_loop.rs b/game/game_logic/src/rendering/event_loop.rs index c754bb84..bef7c185 100644 --- a/game/game_logic/src/rendering/event_loop.rs +++ b/game/game_logic/src/rendering/event_loop.rs @@ -7,15 +7,20 @@ //! //! You can think of this as a bit of bootstrap code for the game. All that happens directly here is rendering of the loading screen and a bit of error handling. +use std::cell::RefCell; + use crate::discord::DiscordChannel; +use crate::project_constants::ProjectConstants; use crate::rendering::core_renderer_sm::{PreloadState, RenderBackendStates}; use crate::rendering::screens::sm_failure_screen; +use crate::scenes::process_ingame_frame; use raylib::RaylibBuilder; /// Will begin rendering graphics. Returns when the window closes -pub fn handle_graphics_blocking( +pub async fn handle_graphics_blocking( config: ConfigBuilder, target_frames_per_second: u32, + constants: &ProjectConstants, discord_signaling: DiscordChannel, ) where ConfigBuilder: FnOnce(&mut RaylibBuilder), @@ -39,7 +44,7 @@ pub fn handle_graphics_blocking( let mut loading_screen = crate::rendering::screens::loading_screen::LoadingScreen::new(); let mut sm_failure_screen = sm_failure_screen::SmFailureScreen::new(); - // Run the event loop + // Handle loading the resources and rendering the loading screen log::trace!("Running event loop"); while !raylib_handle.window_should_close() { // Handle state machine updates @@ -48,13 +53,54 @@ pub fn handle_graphics_blocking( backend_sm = m.finish_preload(); } RenderBackendStates::Loading(ref m) => { - if loading_screen.render(&mut raylib_handle, &raylib_thread, &discord_signaling) { + if loading_screen + .render( + &mut raylib_handle, + &raylib_thread, + &discord_signaling, + &constants, + ) + .await + { backend_sm = m.finish_loading(); } } + _ => break, + }; + + // Tell the profiler that we ended the frame + profiling::finish_frame!(); + } + log::trace!("Finished loading game"); + + // Get access to the global resources + let global_resources = loading_screen + .resources + .expect("Failed to get global resources"); + + // Run the event loop + while !raylib_handle.window_should_close() { + // Handle state machine updates + match backend_sm { RenderBackendStates::SmFailed(ref m) => { - sm_failure_screen.render(&mut raylib_handle, &raylib_thread, &discord_signaling); + sm_failure_screen + .render( + &mut raylib_handle, + &raylib_thread, + &discord_signaling, + &constants, + ) + .await; } + RenderBackendStates::RenderGame(ref m) => { + process_ingame_frame( + &mut raylib_handle, + &raylib_thread, + &discord_signaling, + &global_resources, + ); + } + _ => backend_sm = RenderBackendStates::sm_failed(), }; // Tell the profiler that we ended the frame diff --git a/game/game_logic/src/rendering/screens/sm_failure_screen.rs b/game/game_logic/src/rendering/screens/sm_failure_screen.rs index e0475d56..8611eafa 100644 --- a/game/game_logic/src/rendering/screens/sm_failure_screen.rs +++ b/game/game_logic/src/rendering/screens/sm_failure_screen.rs @@ -1,26 +1,54 @@ use raylib::prelude::*; -use crate::discord::DiscordChannel; +use crate::{discord::{DiscordChannel, DiscordRpcSignal}, project_constants::ProjectConstants}; #[derive(Debug)] -pub struct SmFailureScreen {} +pub struct SmFailureScreen { + has_updated_discord_status: bool, +} impl SmFailureScreen { /// Construct a new `SmFailureScreen` pub fn new() -> Self { - Self {} + Self { + has_updated_discord_status: false, + } } - pub fn render( + pub async fn render( &mut self, raylib: &mut RaylibHandle, rl_thread: &RaylibThread, discord: &DiscordChannel, + constants: &ProjectConstants, ) -> bool { - let mut d = raylib.begin_drawing(&rl_thread); + // Handle updating the Discord status + if !self.has_updated_discord_status { + discord + .send(DiscordRpcSignal::ChangeDetails { + details: constants + .discord + .strings + .get("details.sm_failure") + .unwrap() + .to_owned(), + party_status: None, + }) + .await + .unwrap(); + self.has_updated_discord_status = true; + } + // Render the error message + let mut d = raylib.begin_drawing(&rl_thread); d.clear_background(raylib::color::Color::RED); - d.draw_text("Backend Rendering Broke.\nYou should not be seeing this!", 10, 10, 40, raylib::color::Color::WHITE); + d.draw_text( + "Backend Rendering Broke.\nYou should not be seeing this!", + 10, + 10, + 40, + raylib::color::Color::WHITE, + ); false } diff --git a/game/game_logic/src/scenes/mod.rs b/game/game_logic/src/scenes/mod.rs new file mode 100644 index 00000000..6a4a063c --- /dev/null +++ b/game/game_logic/src/scenes/mod.rs @@ -0,0 +1,22 @@ +//! The render code for various scenes +//! +//! ## Overview +//! +//! This will probably become a messy module over time. Stick your rendering code here +use raylib::prelude::*; + +use crate::{discord::DiscordChannel, global_resource_package::GlobalResources}; + +/// This is called every frame once the game has started. +/// +/// Keep in mind everything you do here will block the main thread (no loading files plz) +pub fn process_ingame_frame( + raylib: &mut RaylibHandle, + rl_thread: &RaylibThread, + discord: &DiscordChannel, + global_resources: &GlobalResources, +) { + let mut d = raylib.begin_drawing(&rl_thread); + + d.clear_background(raylib::color::Color::WHITE); +}