wip
This commit is contained in:
parent
71017a57be
commit
32ec6c43cc
2
.gitignore
vendored
2
.gitignore
vendored
@ -167,3 +167,5 @@ cython_debug/
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
/screenshot*.png
|
12
.vscode/tasks.json
vendored
12
.vscode/tasks.json
vendored
@ -18,6 +18,18 @@
|
||||
],
|
||||
"label": "Launch Game"
|
||||
},
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "run",
|
||||
"args": [
|
||||
"--",
|
||||
"--verbose"
|
||||
],
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"label": "Launch Game [DEBUG LOGS]"
|
||||
},
|
||||
{
|
||||
"type": "cargo",
|
||||
"command": "doc",
|
||||
|
15
game/dist/assets/anm/test/test_debugTexture/test_debugTexture.anim_meta.json
vendored
Normal file
15
game/dist/assets/anm/test/test_debugTexture/test_debugTexture.anim_meta.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"sheet_height": 64,
|
||||
"sheet_width": 64,
|
||||
"published_at": "2022-03-29 16:46:34",
|
||||
"published_by": "ewpratten",
|
||||
"fps": 24.0,
|
||||
"frames": [
|
||||
{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 64,
|
||||
"height": 64
|
||||
}
|
||||
]
|
||||
}
|
BIN
game/dist/assets/anm/test/test_debugTexture/test_debugTexture.png
vendored
Normal file
BIN
game/dist/assets/anm/test/test_debugTexture/test_debugTexture.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
4
game/dist/known-sprite-types.json
vendored
4
game/dist/known-sprite-types.json
vendored
@ -14,5 +14,9 @@
|
||||
{
|
||||
"short": "cut",
|
||||
"friendly": "Cutscene"
|
||||
},
|
||||
{
|
||||
"short": "test",
|
||||
"friendly": "Test"
|
||||
}
|
||||
]
|
@ -6,7 +6,10 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
raylib = { version = "3.7", path = "../../third_party/raylib-rs/raylib" }
|
||||
raylib = { version = "3.7", path = "../../third_party/raylib-rs/raylib", features = [
|
||||
"with_serde",
|
||||
"nalgebra_interop"
|
||||
] }
|
||||
sad_machine = { version = "1.0", path = "../../third_party/sm" }
|
||||
tokio = { version = "1.17.0", features = ["fs", "sync"] }
|
||||
log = "0.4.14"
|
||||
@ -21,3 +24,5 @@ thiserror = "1.0.30"
|
||||
# nalgebra = { version = "0.30.1", features = ["serde"] }
|
||||
approx = "0.5.1"
|
||||
poll-promise = { version = "0.1.0", features = ["tokio"] }
|
||||
tempfile = "3.3.0"
|
||||
nalgebra = "0.30.1"
|
@ -20,4 +20,8 @@
|
||||
mod datastore;
|
||||
pub use datastore::InternalData;
|
||||
mod json;
|
||||
pub use json::{InternalJsonLoadError, load_json_structure};
|
||||
pub use json::{InternalJsonLoadError, load_json_structure};
|
||||
mod sprite_types;
|
||||
pub use sprite_types::{KnownSpriteType, load_known_sprite_types};
|
||||
mod texture;
|
||||
pub use texture::{load_texture_from_internal_data, ResourceLoadError};
|
24
game/game_logic/src/asset_manager/sprite_types.rs
Normal file
24
game/game_logic/src/asset_manager/sprite_types.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::InternalData;
|
||||
|
||||
/// The structure backing the `dist/known-sprite-types.json` file
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct KnownSpriteType {
|
||||
/// Sprite short name (used in filenames)
|
||||
#[serde(rename = "short")]
|
||||
pub short_name: String,
|
||||
/// Sprite long name
|
||||
#[serde(rename = "friendly")]
|
||||
pub friendly_name: String,
|
||||
}
|
||||
|
||||
/// Loads a list of all known sprite types from the definitions file
|
||||
pub fn load_known_sprite_types() -> Result<Vec<KnownSpriteType>, serde_json::Error> {
|
||||
// Load the json file from the embedded data as a string
|
||||
let data = InternalData::get("known-sprite-types.json").unwrap().data;
|
||||
|
||||
// Deserialize the json string into a rust structure
|
||||
let json_structure: Vec<KnownSpriteType> = serde_json::from_slice(&data)?;
|
||||
Ok(json_structure)
|
||||
}
|
64
game/game_logic/src/asset_manager/texture.rs
Normal file
64
game/game_logic/src/asset_manager/texture.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! Code for loading textures from RAM to VRAM
|
||||
//!
|
||||
//! Largely coppied from last year: https://github.com/Ewpratten/ludum-dare-49/blob/master/game/src/utilities/datastore.rs
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use raylib::{texture::Texture2D, RaylibHandle, RaylibThread};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::asset_manager::InternalData;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ResourceLoadError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Could not load embedded asset: {0}")]
|
||||
AssetNotFound(String),
|
||||
#[error("Generic error: {0}")]
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
/// Loads an embedded texture into VRAM.
|
||||
///
|
||||
/// # Technical Info
|
||||
/// In this application, we are using `rust_embed` to embed static assets directly inside the executable.
|
||||
/// This has the limitation of none of the assets being "real files", which causes an issue with Raylib.
|
||||
/// Raylib requires a "real file" in order to load data into VRAM (without digging into `unsafe` dark magic).
|
||||
/// The solution is to temporarily write the assets to disk, and then load them from disk.
|
||||
/// We must also preserve the file extension, so the Raylib file loader can parse them correctly.
|
||||
pub fn load_texture_from_internal_data(
|
||||
raylib_handle: &mut RaylibHandle,
|
||||
thread: &RaylibThread,
|
||||
path: &str,
|
||||
) -> Result<Texture2D, ResourceLoadError> {
|
||||
// Create a temp file path to work with
|
||||
let temp_dir = tempdir()?;
|
||||
debug!(
|
||||
"Created temporary directory for passing embedded data to Raylib: {}",
|
||||
temp_dir.path().display()
|
||||
);
|
||||
let tmp_path = temp_dir.path().join(Path::new(path).file_name().unwrap());
|
||||
|
||||
// Unpack the raw image data to a real file on the local filesystem so raylib will read it correctly
|
||||
std::fs::write(
|
||||
&tmp_path,
|
||||
&InternalData::get(path)
|
||||
.ok_or(ResourceLoadError::AssetNotFound(path.to_string()))?
|
||||
.data,
|
||||
)?;
|
||||
|
||||
// Call through via FFI to re-load the file
|
||||
let texture = raylib_handle
|
||||
.load_texture(thread, tmp_path.to_str().unwrap())
|
||||
.map_err(ResourceLoadError::Generic)?;
|
||||
|
||||
// Close the file
|
||||
debug!(
|
||||
"Dropping temporary directory: {}",
|
||||
temp_dir.path().display()
|
||||
);
|
||||
temp_dir.close()?;
|
||||
|
||||
Ok(texture)
|
||||
}
|
@ -73,7 +73,7 @@ pub async fn entrypoint(force_recreate_savefiles: bool) {
|
||||
|builder| {
|
||||
builder
|
||||
.msaa_4x()
|
||||
.vsync()
|
||||
// .vsync()
|
||||
.title(project_constants.game_name.as_str())
|
||||
.height(project_constants.base_window_size.1 as i32)
|
||||
.width(project_constants.base_window_size.0 as i32);
|
||||
|
@ -15,6 +15,8 @@ use crate::rendering::core_renderer_sm::{PreloadState, RenderBackendStates};
|
||||
use crate::rendering::screens::sm_failure_screen;
|
||||
use crate::scenes::SceneRenderDelegate;
|
||||
use raylib::RaylibBuilder;
|
||||
use raylib::consts::KeyboardKey;
|
||||
use raylib::prelude::RaylibDraw;
|
||||
|
||||
/// Will begin rendering graphics. Returns when the window closes
|
||||
pub async fn handle_graphics_blocking<ConfigBuilder>(
|
||||
@ -45,7 +47,8 @@ pub async fn handle_graphics_blocking<ConfigBuilder>(
|
||||
let mut sm_failure_screen = sm_failure_screen::SmFailureScreen::new();
|
||||
|
||||
// Set up the main render delegate
|
||||
let mut render_delegate = SceneRenderDelegate::on_game_start();
|
||||
let mut render_delegate =
|
||||
SceneRenderDelegate::on_game_start(&mut raylib_handle, &raylib_thread);
|
||||
|
||||
// Handle loading the resources and rendering the loading screen
|
||||
log::trace!("Running event loop");
|
||||
@ -74,13 +77,16 @@ pub async fn handle_graphics_blocking<ConfigBuilder>(
|
||||
// Tell the profiler that we ended the frame
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
log::trace!("Finished loading game");
|
||||
log::info!("Finished loading game");
|
||||
|
||||
// Get access to the global resources
|
||||
let global_resources = loading_screen
|
||||
.resources
|
||||
.expect("Failed to get global resources");
|
||||
|
||||
// Tracker for if we are showing the FPS counter
|
||||
let mut show_fps_counter = false;
|
||||
|
||||
// Run the event loop
|
||||
while !raylib_handle.window_should_close() {
|
||||
// Handle state machine updates
|
||||
@ -106,6 +112,16 @@ pub async fn handle_graphics_blocking<ConfigBuilder>(
|
||||
_ => backend_sm = RenderBackendStates::sm_failed(),
|
||||
};
|
||||
|
||||
// Check for F3 being pressed
|
||||
if raylib_handle.is_key_pressed(KeyboardKey::KEY_F3) {
|
||||
show_fps_counter = !show_fps_counter;
|
||||
}
|
||||
|
||||
// Show the FPS counter
|
||||
if show_fps_counter {
|
||||
raylib_handle.begin_drawing(&raylib_thread).draw_fps(10, 10);
|
||||
}
|
||||
|
||||
// Tell the profiler that we ended the frame
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
|
@ -1,23 +1,157 @@
|
||||
//! This module handles the code for rendering framerate-locked animations from textures
|
||||
|
||||
use raylib::texture::Texture2D;
|
||||
use nalgebra::Vector2;
|
||||
use raylib::{
|
||||
color::Color,
|
||||
math::Rectangle,
|
||||
prelude::{RaylibDraw, RaylibDrawHandle},
|
||||
texture::Texture2D,
|
||||
RaylibHandle, RaylibThread,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::asset_manager::{
|
||||
load_json_structure, load_known_sprite_types, load_texture_from_internal_data,
|
||||
InternalJsonLoadError,
|
||||
};
|
||||
|
||||
/// Possible errors to be thrown during the animation texture loading process
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AnimatedTextureLoadError {
|
||||
#[error(transparent)]
|
||||
MetadataLoadError(#[from] InternalJsonLoadError),
|
||||
#[error(transparent)]
|
||||
KnownSpriteTypesLoadError(#[from] serde_json::Error),
|
||||
#[error("Invalid Sprite Type: {0}")]
|
||||
InvalidSpriteType(String),
|
||||
#[error(transparent)]
|
||||
TextureLoadError(#[from] crate::asset_manager::ResourceLoadError),
|
||||
}
|
||||
|
||||
/// Definition for the structure describing a frame's size and position in a texture
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct FrameTextureDescriptor {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Into<Rectangle> for FrameTextureDescriptor {
|
||||
fn into(self) -> Rectangle {
|
||||
Rectangle::new(self.x, self.y, self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition for the metadata structure attached to each spritesheet
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct AnimatedTextureMetadata {
|
||||
pub sheet_height: u64,
|
||||
pub sheet_width: u64,
|
||||
pub fps: f32,
|
||||
pub frames: Vec<FrameTextureDescriptor>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimatedTexture {
|
||||
texture: Texture2D,
|
||||
target_fps: f32,
|
||||
texture_metadata: AnimatedTextureMetadata,
|
||||
// a list of source rects to reduce memory allocation needs during render time
|
||||
texture_source_rects: Vec<Rectangle>,
|
||||
}
|
||||
|
||||
impl AnimatedTexture {
|
||||
/// Construct a new `AnimatedTexture`
|
||||
pub fn new(texture: Texture2D, target_frames_per_second: f32) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
target_fps: target_frames_per_second,
|
||||
#[profiling::function]
|
||||
pub fn new(
|
||||
raylib_handle: &mut RaylibHandle,
|
||||
thread: &RaylibThread,
|
||||
sprite_type: &str,
|
||||
sprite_name: &str,
|
||||
) -> Result<Self, AnimatedTextureLoadError> {
|
||||
// Try to convert the sprite type string to a real type
|
||||
let known_sprite_types = load_known_sprite_types()?;
|
||||
let sprite_type_obj = known_sprite_types.iter().find(|known_sprite_type| {
|
||||
known_sprite_type.short_name == sprite_type
|
||||
|| known_sprite_type.friendly_name == sprite_type
|
||||
});
|
||||
if let None = sprite_type_obj {
|
||||
error!("Invalid sprite type supplied: {}", sprite_type);
|
||||
return Err(AnimatedTextureLoadError::InvalidSpriteType(
|
||||
sprite_type.to_string(),
|
||||
));
|
||||
}
|
||||
let sprite_type_obj = sprite_type_obj.unwrap();
|
||||
|
||||
// Now, we can construct the paths to the texture and metadata
|
||||
let parent_dir_path = format!(
|
||||
"assets/anm/{}/{}_{}",
|
||||
sprite_type_obj.short_name, sprite_type_obj.short_name, sprite_name
|
||||
);
|
||||
let metadata_file_path = format!(
|
||||
"{}/{}_{}.anim_meta.json",
|
||||
parent_dir_path, sprite_type_obj.short_name, sprite_name
|
||||
);
|
||||
let texture_file_path = format!(
|
||||
"{}/{}_{}.png",
|
||||
parent_dir_path, sprite_type_obj.short_name, sprite_name
|
||||
);
|
||||
|
||||
// Attempt to load the metadata
|
||||
let texture_metadata: AnimatedTextureMetadata = load_json_structure(&metadata_file_path)?;
|
||||
let source_rects = texture_metadata
|
||||
.frames
|
||||
.iter()
|
||||
.map(|frame_descriptor| frame_descriptor.clone().into())
|
||||
.collect();
|
||||
|
||||
// Attempt to load the texture itself
|
||||
let texture = load_texture_from_internal_data(raylib_handle, thread, &texture_file_path)?;
|
||||
|
||||
Ok(Self {
|
||||
texture,
|
||||
texture_metadata,
|
||||
texture_source_rects: source_rects,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render_frame_by_index(&self, index: usize) {
|
||||
#[profiling::function]
|
||||
pub fn render_frame_by_index(
|
||||
&self,
|
||||
draw_handle: &mut RaylibDrawHandle,
|
||||
index: usize,
|
||||
position: Vector2<f32>,
|
||||
percent_scale: Option<Vector2<f32>>,
|
||||
origin: Option<Vector2<f32>>,
|
||||
rotation: Option<f32>,
|
||||
tint: Option<Color>,
|
||||
) {
|
||||
// Get the frame-specific metadata
|
||||
let metadata = &self.texture_metadata.frames[index];
|
||||
|
||||
// Build a source rectangle
|
||||
let source = self.texture_source_rects[index];
|
||||
|
||||
// Build a destination rectangle
|
||||
let scaler = percent_scale.unwrap_or(Vector2::new(1.0, 1.0));
|
||||
let destination = Rectangle::new(
|
||||
position.x,
|
||||
position.y,
|
||||
metadata.width * scaler.x,
|
||||
metadata.height * scaler.y,
|
||||
);
|
||||
let origin: raylib::core::math::Vector2 =
|
||||
origin.unwrap_or_else(|| Vector2::<f32>::zeros()).into();
|
||||
debug!("{:?} -> {:?}", source, destination);
|
||||
|
||||
// Render the frame
|
||||
draw_handle.draw_texture_pro(
|
||||
&self.texture,
|
||||
source,
|
||||
destination,
|
||||
origin,
|
||||
rotation.unwrap_or(0.0),
|
||||
tint.unwrap_or(Color::WHITE),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ pub struct SceneRenderDelegate {
|
||||
|
||||
impl SceneRenderDelegate {
|
||||
/// This is called when the game first loads
|
||||
pub fn on_game_start() -> Self {
|
||||
pub fn on_game_start(raylib: &mut RaylibHandle, rl_thread: &RaylibThread) -> Self {
|
||||
// TODO: Stick any init code you want here.
|
||||
|
||||
// Init some scenes
|
||||
let scene_test_fox = TestFoxScene::new();
|
||||
let scene_test_fox = TestFoxScene::new(raylib, rl_thread);
|
||||
|
||||
Self { scene_test_fox }
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
//! This "scene" is used only for testing animation and resource loading
|
||||
//! It should be removed once the game is being worked on
|
||||
|
||||
use raylib::{RaylibHandle, RaylibThread};
|
||||
use raylib::prelude::*;
|
||||
use nalgebra as na;
|
||||
|
||||
use crate::{
|
||||
discord::DiscordChannel, global_resource_package::GlobalResources,
|
||||
@ -15,9 +16,9 @@ pub struct TestFoxScene {
|
||||
|
||||
impl TestFoxScene {
|
||||
/// Construct a new `TestFoxScene`
|
||||
pub fn new() -> Self {
|
||||
pub fn new(raylib_handle: &mut RaylibHandle, thread: &RaylibThread) -> Self {
|
||||
// Load the fox texture
|
||||
let fox = AnimatedTexture::new();
|
||||
let fox = AnimatedTexture::new(raylib_handle, thread, "test", "debugTexture").unwrap();
|
||||
|
||||
Self { fox_animation: fox }
|
||||
}
|
||||
@ -30,5 +31,21 @@ impl TestFoxScene {
|
||||
discord: &DiscordChannel,
|
||||
global_resources: &GlobalResources,
|
||||
) {
|
||||
// Get a drawing handle
|
||||
let mut draw = raylib.begin_drawing(rl_thread);
|
||||
|
||||
// Clear the screen
|
||||
draw.clear_background(Color::WHITE);
|
||||
|
||||
// Render the fox
|
||||
self.fox_animation.render_frame_by_index(
|
||||
&mut draw,
|
||||
0,
|
||||
na::Vector2::new(0.0, 0.0),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
27
renderdoc_settings.cap
Normal file
27
renderdoc_settings.cap
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"rdocCaptureSettings": 1,
|
||||
"settings": {
|
||||
"autoStart": false,
|
||||
"commandLine": "--verbose",
|
||||
"environment": [
|
||||
],
|
||||
"executable": "/home/ewpratten/projects/ludum-dare-50/target/debug/desktop_wrapper",
|
||||
"inject": false,
|
||||
"numQueuedFrames": 0,
|
||||
"options": {
|
||||
"allowFullscreen": true,
|
||||
"allowVSync": true,
|
||||
"apiValidation": false,
|
||||
"captureAllCmdLists": false,
|
||||
"captureCallstacks": false,
|
||||
"captureCallstacksOnlyDraws": false,
|
||||
"debugOutputMute": true,
|
||||
"delayForDebugger": 0,
|
||||
"hookIntoChildren": false,
|
||||
"refAllResources": false,
|
||||
"verifyBufferAccess": false
|
||||
},
|
||||
"queuedFrameCap": 0,
|
||||
"workingDir": "/home/ewpratten/projects/ludum-dare-50"
|
||||
}
|
||||
}
|
2
third_party/raylib-rs
vendored
2
third_party/raylib-rs
vendored
@ -1 +1 @@
|
||||
Subproject commit 3aff138276b374f5e07187a652a71d9eb59e97d1
|
||||
Subproject commit abae275a63ee527cfe16d35a7d00d7532426d5a5
|
Reference in New Issue
Block a user