Merge pull request #19 from Ewpratten/player_physics
Implement the Player
This commit is contained in:
commit
17fdb4252e
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
/*.gif
|
||||
|
@ -1,2 +1,2 @@
|
||||
* @ewpratten
|
||||
game/ @ewpratten @hyperlisk @LuS404 @SNOWZ7Z
|
||||
game/ @ewpratten @hyperliskdev @LuS404 @SNOWZ7Z
|
||||
|
@ -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"
|
||||
|
BIN
game/assets/character/player_run.png
Normal file
BIN
game/assets/character/player_run.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 138 KiB |
@ -1,5 +1,53 @@
|
||||
{
|
||||
"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
|
||||
],
|
||||
"white": [
|
||||
188,
|
||||
188,
|
||||
188,
|
||||
188
|
||||
]
|
||||
},
|
||||
"animation_fps": 15
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
40
game/src/character/collisions.rs
Normal file
40
game/src/character/collisions.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use raylib::math::{Rectangle, Vector2};
|
||||
|
||||
use super::{CharacterState, MainCharacter};
|
||||
|
||||
const GRAVITY_PPS: f32 = 2.0;
|
||||
|
||||
pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), ()> {
|
||||
// Convert the player to a rectangle
|
||||
let predicted_player_position = player.position + player.velocity;
|
||||
let player_rect = Rectangle::new(
|
||||
predicted_player_position.x - (player.size.x / 2.0),
|
||||
predicted_player_position.y - (player.size.x / 2.0),
|
||||
player.size.x,
|
||||
player.size.y,
|
||||
);
|
||||
|
||||
// Calculate a generic "floor" to always collide with
|
||||
let floor_rect = Rectangle::new(f32::MIN, 0.0, f32::MAX, 1.0);
|
||||
|
||||
// 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
|
||||
{
|
||||
player.velocity.y = 0.0;
|
||||
|
||||
// Handle ending a jump
|
||||
if player.current_state == CharacterState::Jumping {
|
||||
player.set_state(CharacterState::Running);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Error out if colliding in the X direction
|
||||
|
||||
// Apply the force
|
||||
player.position += player.velocity;
|
||||
|
||||
// Apply gravity
|
||||
player.velocity.y += GRAVITY_PPS;
|
||||
Ok(())
|
||||
}
|
@ -1,19 +1,63 @@
|
||||
pub mod collisions;
|
||||
pub mod render;
|
||||
|
||||
use raylib::math::Vector2;
|
||||
use chrono::{DateTime, Utc};
|
||||
use raylib::{math::Vector2, texture::Texture2D};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
use crate::utilities::anim_render::AnimatedSpriteSheet;
|
||||
|
||||
use self::collisions::modify_player_based_on_forces;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub enum CharacterState {
|
||||
#[default]
|
||||
Running,
|
||||
Jumping,
|
||||
Dashing,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MainCharacter {
|
||||
pub position: Vector2,
|
||||
pub velocity: Vector2,
|
||||
pub size: Vector2,
|
||||
pub sprite_sheet: AnimatedSpriteSheet,
|
||||
pub current_state: CharacterState,
|
||||
pub state_set_timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl MainCharacter {
|
||||
|
||||
pub fn new(position: Vector2) -> Self {
|
||||
Self { position }
|
||||
pub fn new(position: Vector2, sprite_sheet: Texture2D) -> Self {
|
||||
Self {
|
||||
position,
|
||||
velocity: Vector2::new(20.0, 0.0),
|
||||
size: Vector2::new(100.0, 130.0),
|
||||
sprite_sheet: AnimatedSpriteSheet::new(
|
||||
sprite_sheet,
|
||||
Vector2::new(300.0, 300.0),
|
||||
3,
|
||||
8,
|
||||
6,
|
||||
),
|
||||
current_state: CharacterState::default(),
|
||||
state_set_timestamp: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_force(&mut self, force: Vector2) {
|
||||
self.position += force;
|
||||
pub fn apply_force(&mut self, force: Vector2) -> Option<()> {
|
||||
self.velocity = force;
|
||||
modify_player_based_on_forces(self).unwrap();
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn update_gravity(&mut self) {
|
||||
modify_player_based_on_forces(self).unwrap();
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: CharacterState) {
|
||||
if state != self.current_state {
|
||||
self.current_state = state;
|
||||
self.state_set_timestamp = Utc::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,40 @@
|
||||
use raylib::prelude::*;
|
||||
use std::ops::{Div, Sub};
|
||||
|
||||
use crate::utilities::non_ref_raylib::HackedRaylibHandle;
|
||||
use chrono::Utc;
|
||||
use raylib::prelude::*;
|
||||
use tracing::log::trace;
|
||||
|
||||
use crate::{utilities::non_ref_raylib::HackedRaylibHandle, GameConfig};
|
||||
|
||||
use super::MainCharacter;
|
||||
|
||||
pub fn render_character_in_camera_space(
|
||||
raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>,
|
||||
player: &MainCharacter,
|
||||
config: &GameConfig,
|
||||
) {
|
||||
// Calculate the time since the start of the state
|
||||
let time_since_state_change = Utc::now() - player.state_set_timestamp;
|
||||
|
||||
raylib.draw_rectangle_v(player.position, Vector2::new(10.0, 20.0), Color::WHITE);
|
||||
// Calculate the number of frames since state change
|
||||
let frames_since_state_change = ((time_since_state_change.num_milliseconds() as f64 / 1000.0)
|
||||
* config.animation_fps as f64) as f32;
|
||||
|
||||
// Calculate the frame ID to render
|
||||
let frame_id = match player.current_state {
|
||||
crate::character::CharacterState::Jumping => 4,
|
||||
_ => (frames_since_state_change % player.sprite_sheet.sprite_count as f32).floor() as usize,
|
||||
};
|
||||
|
||||
trace!(
|
||||
"Rendering player frame: {} ({})",
|
||||
frame_id,
|
||||
frames_since_state_change
|
||||
);
|
||||
player.sprite_sheet.render(
|
||||
raylib,
|
||||
player.position.sub(player.size.div(2.0)),
|
||||
Some(Vector2::new(player.size.y, player.size.y)),
|
||||
Some(frame_id),
|
||||
);
|
||||
}
|
||||
|
@ -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<HackedRaylibHandle>
|
||||
|
||||
pub renderer: RefCell<HackedRaylibHandle>,
|
||||
pub config: GameConfig
|
||||
}
|
||||
|
||||
impl GameContext {
|
||||
/// Construct a new game context.
|
||||
pub fn new(raylib: RefCell<HackedRaylibHandle>) -> Self {
|
||||
Self {
|
||||
renderer: raylib
|
||||
}
|
||||
}
|
||||
}
|
||||
// impl GameContext {
|
||||
// /// Construct a new game context.
|
||||
// pub fn new(raylib: RefCell<HackedRaylibHandle>) -> Self {
|
||||
// Self {
|
||||
// renderer: raylib
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -158,7 +158,10 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
raylib_thread = thread;
|
||||
|
||||
// Build the game context
|
||||
context = Box::new(GameContext::new(RefCell::new(rl.into())));
|
||||
context = Box::new(GameContext {
|
||||
renderer: RefCell::new(rl.into()),
|
||||
config: game_config.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
// Get the main state machine
|
||||
@ -183,12 +186,19 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
let mut pixel_shader = ShaderWrapper::new(
|
||||
None,
|
||||
Some(StaticGameData::get("shaders/pixelart.fs")).expect("Failed to load pixelart.fs"),
|
||||
vec!["viewport", "pixelScale", "warpFactor", "scanlineDarkness"],
|
||||
vec![
|
||||
"viewport",
|
||||
"pixelScale",
|
||||
"warpFactor",
|
||||
"scanlineDarkness",
|
||||
"bloomSamples",
|
||||
"bloomQuality",
|
||||
],
|
||||
&mut context.renderer.borrow_mut(),
|
||||
&raylib_thread,
|
||||
)?;
|
||||
|
||||
info!("Starting the render loop");
|
||||
|
||||
while !context.renderer.borrow().window_should_close() {
|
||||
// Profile the main game loop
|
||||
puffin::profile_scope!("main_loop");
|
||||
@ -219,6 +229,8 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
)?;
|
||||
pixel_shader.set_variable("warpFactor", pixel_shader_config.warp_factor)?;
|
||||
pixel_shader.set_variable("scanlineDarkness", pixel_shader_config.scanline_darkness)?;
|
||||
pixel_shader.set_variable("bloomSamples", pixel_shader_config.bloom_samples)?;
|
||||
pixel_shader.set_variable("bloomQuality", pixel_shader_config.bloom_quality)?;
|
||||
|
||||
// Render the game via the pixel shader
|
||||
render_to_texture(&mut dynamic_texture, || {
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use raylib::{color::Color, prelude::RaylibDraw};
|
||||
use tracing::{debug, trace};
|
||||
@ -7,6 +5,7 @@ use tracing::{debug, trace};
|
||||
use crate::{
|
||||
context::GameContext,
|
||||
utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::ScreenSpaceRender},
|
||||
GameConfig,
|
||||
};
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
@ -38,7 +37,7 @@ impl Action<Scenes, ScreenError, GameContext> for FsmErrorScreen {
|
||||
context: &GameContext,
|
||||
) -> Result<dirty_fsm::ActionFlag<Scenes>, 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<Scenes, ScreenError, GameContext> 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
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::utilities::render_layer::ScreenSpaceRender;
|
||||
use crate::{GameConfig, utilities::render_layer::ScreenSpaceRender};
|
||||
use raylib::prelude::*;
|
||||
use super::InGameScreen;
|
||||
|
||||
@ -6,8 +6,13 @@ impl ScreenSpaceRender for InGameScreen {
|
||||
fn render_screen_space(
|
||||
&self,
|
||||
raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
|
||||
config: &GameConfig
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
// Calculate the logo position
|
||||
let screen_size = raylib.get_screen_size();
|
||||
|
||||
// Draw a thin glow box around the screen
|
||||
raylib.draw_rectangle_lines(0, 0, screen_size.x as i32, screen_size.y as i32, config.colors.red);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{character::MainCharacter, context::GameContext, utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}};
|
||||
use crate::{character::{CharacterState, MainCharacter}, context::GameContext, utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}};
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, trace};
|
||||
@ -13,12 +13,12 @@ mod world;
|
||||
#[derive(Debug)]
|
||||
pub struct InGameScreen {
|
||||
camera: Camera2D,
|
||||
player: MainCharacter
|
||||
player: MainCharacter,
|
||||
}
|
||||
|
||||
impl InGameScreen {
|
||||
/// Construct a new `InGameScreen`
|
||||
pub fn new() -> Self {
|
||||
pub fn new(player_sprite_sheet: Texture2D) -> Self {
|
||||
Self {
|
||||
camera: Camera2D {
|
||||
offset: Vector2::zero(),
|
||||
@ -26,7 +26,7 @@ impl InGameScreen {
|
||||
rotation: 0.0,
|
||||
zoom: 1.0,
|
||||
},
|
||||
player: MainCharacter::new(Vector2::zero()),
|
||||
player: MainCharacter::new(Vector2::new(0.0, -80.0), player_sprite_sheet),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,6 +40,9 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
|
||||
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
|
||||
debug!("Running InGameScreen for the first time");
|
||||
|
||||
// Set the player to running
|
||||
self.player.set_state(CharacterState::Running);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -48,16 +51,17 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
|
||||
delta: &chrono::Duration,
|
||||
context: &GameContext,
|
||||
) -> Result<dirty_fsm::ActionFlag<Scenes>, ScreenError> {
|
||||
puffin::profile_function!();
|
||||
trace!("execute() called on InGameScreen");
|
||||
|
||||
// Grab exclusive access to the renderer
|
||||
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<Scenes, ScreenError, GameContext> 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)
|
||||
}
|
||||
|
@ -1,22 +1,42 @@
|
||||
use std::ops::Div;
|
||||
|
||||
use super::InGameScreen;
|
||||
use crate::utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate};
|
||||
use crate::{
|
||||
character::CharacterState,
|
||||
utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::FrameUpdate},
|
||||
GameConfig,
|
||||
};
|
||||
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,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
// 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.05));
|
||||
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);
|
||||
let is_dash = raylib.is_key_down(KeyboardKey::KEY_LEFT_SHIFT);
|
||||
let is_pause = raylib.is_key_down(KeyboardKey::KEY_ESCAPE);
|
||||
let is_jump = raylib.is_key_pressed(KeyboardKey::KEY_SPACE);
|
||||
let is_dash = raylib.is_key_pressed(KeyboardKey::KEY_LEFT_SHIFT);
|
||||
let is_pause = raylib.is_key_pressed(KeyboardKey::KEY_ESCAPE);
|
||||
|
||||
if is_jump {
|
||||
self.player.apply_force(Vector2::new(0.0, -1.0));
|
||||
self.player.apply_force(Vector2::new(0.0, -30.0));
|
||||
self.player.set_state(CharacterState::Jumping);
|
||||
} else if is_dash {
|
||||
self.player.apply_force(Vector2::new(40.0, -10.0));
|
||||
self.player.set_state(CharacterState::Dashing);
|
||||
} else {
|
||||
if self.player.current_state != CharacterState::Jumping {
|
||||
self.player.set_state(CharacterState::Running);
|
||||
}
|
||||
}
|
||||
self.player.update_gravity();
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,34 @@
|
||||
use std::ops::Mul;
|
||||
|
||||
use super::InGameScreen;
|
||||
use crate::{
|
||||
character::render::render_character_in_camera_space,
|
||||
utilities::{non_ref_raylib::HackedRaylibHandle, render_layer::WorldSpaceRender},
|
||||
GameConfig,
|
||||
};
|
||||
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,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
// Render the player
|
||||
render_character_in_camera_space(raylib, &self.player);
|
||||
render_character_in_camera_space(raylib, &self.player, &config);
|
||||
|
||||
// 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,
|
||||
config.colors.white,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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<Scenes, ScreenError, GameContext> for LoadingScreen {
|
||||
context: &GameContext,
|
||||
) -> Result<dirty_fsm::ActionFlag<Scenes>, 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,
|
||||
|
@ -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<Scenes, ScreenError, GameContext> for MainMenuScreen {
|
||||
context: &GameContext,
|
||||
) -> Result<dirty_fsm::ActionFlag<Scenes>, 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);
|
||||
|
@ -4,10 +4,13 @@ use self::{
|
||||
};
|
||||
use crate::{
|
||||
context::GameContext,
|
||||
utilities::{datastore::ResourceLoadError, non_ref_raylib::HackedRaylibHandle},
|
||||
utilities::{
|
||||
datastore::{load_texture_from_internal_data, ResourceLoadError},
|
||||
non_ref_raylib::HackedRaylibHandle,
|
||||
},
|
||||
};
|
||||
use dirty_fsm::StateMachine;
|
||||
use raylib::RaylibThread;
|
||||
use raylib::{texture::Texture2D, RaylibThread};
|
||||
|
||||
pub mod fsm_error_screen;
|
||||
pub mod ingame_scene;
|
||||
@ -40,6 +43,11 @@ pub fn build_screen_state_machine(
|
||||
StateMachine<Scenes, ScreenError, GameContext>,
|
||||
ScreenError,
|
||||
> {
|
||||
// Load the various textures needed by the states
|
||||
let player_sprite_sheet =
|
||||
load_texture_from_internal_data(raylib_handle, thread, "character/player_run.png").unwrap();
|
||||
|
||||
// Set up the state machine
|
||||
let mut machine = StateMachine::new();
|
||||
machine.add_action(Scenes::FsmErrorScreen, FsmErrorScreen::new())?;
|
||||
machine.add_action(
|
||||
@ -47,6 +55,6 @@ 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())?;
|
||||
machine.add_action(Scenes::InGameScene, InGameScreen::new(player_sprite_sheet))?;
|
||||
Ok(machine)
|
||||
}
|
||||
|
104
game/src/utilities/anim_render.rs
Normal file
104
game/src/utilities/anim_render.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use raylib::{
|
||||
color::Color,
|
||||
math::{Rectangle, Vector2},
|
||||
prelude::RaylibDraw,
|
||||
texture::Texture2D,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnimatedSpriteSheet {
|
||||
texture: Texture2D,
|
||||
sprite_size: Vector2,
|
||||
sheet_width: usize,
|
||||
pub sprite_count: usize,
|
||||
pub default_sprite_id: usize,
|
||||
}
|
||||
|
||||
impl AnimatedSpriteSheet {
|
||||
/// Construct a new AnimatedSpriteSheet
|
||||
pub fn new(
|
||||
texture: Texture2D,
|
||||
sprite_size: Vector2,
|
||||
sheet_width: usize,
|
||||
sprite_count: usize,
|
||||
default_sprite_id: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
texture,
|
||||
sprite_size,
|
||||
sheet_width,
|
||||
sprite_count,
|
||||
default_sprite_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<T>(
|
||||
&self,
|
||||
raylib: &mut T,
|
||||
position: Vector2,
|
||||
scaled_size: Option<Vector2>,
|
||||
sprite_id: Option<usize>,
|
||||
) where
|
||||
T: RaylibDraw,
|
||||
{
|
||||
let sprite_id = sprite_id.unwrap_or(self.default_sprite_id);
|
||||
let sprite_id = if sprite_id >= self.sprite_count {
|
||||
self.default_sprite_id
|
||||
} else {
|
||||
sprite_id
|
||||
};
|
||||
|
||||
let sprite_rect = Rectangle::new(
|
||||
(sprite_id % self.sheet_width) as f32 * self.sprite_size.x,
|
||||
(sprite_id / self.sheet_width) as f32 * self.sprite_size.y,
|
||||
self.sprite_size.x,
|
||||
self.sprite_size.y,
|
||||
);
|
||||
|
||||
let scaled_size = scaled_size.unwrap_or(self.sprite_size);
|
||||
|
||||
raylib.draw_texture_pro(
|
||||
&self.texture,
|
||||
sprite_rect,
|
||||
Rectangle::new(position.x, position.y, scaled_size.x, scaled_size.y),
|
||||
Vector2::zero(),
|
||||
0.0,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
|
||||
// {
|
||||
// let sprite_id = match sprite_id {
|
||||
// Some(id) => {
|
||||
// if id >= self.sprite_count {
|
||||
// self.default_sprite_id
|
||||
// } else {
|
||||
// id
|
||||
// }
|
||||
// }
|
||||
// None => self.default_sprite_id,
|
||||
// };
|
||||
|
||||
// let sprite_x = sprite_id % self.sheet_width;
|
||||
// let sprite_y = sprite_id / self.sheet_width;
|
||||
|
||||
// raylib.draw_texture_pro(
|
||||
// &self.texture,
|
||||
// Rectangle {
|
||||
// x: sprite_x as f32,
|
||||
// y: sprite_y as f32,
|
||||
// width: self.sprite_size.x,
|
||||
// height: self.sprite_size.y,
|
||||
// },
|
||||
// Rectangle {
|
||||
// x: position.x,
|
||||
// y: position.y,
|
||||
// width: self.sprite_size.x,
|
||||
// height: self.sprite_size.y,
|
||||
// },
|
||||
// Vector2::zero(),
|
||||
// 0.0,
|
||||
// Color::WHITE,
|
||||
// );
|
||||
// }
|
||||
}
|
@ -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,24 @@ pub struct Author {
|
||||
pub roles: Vec<String>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
pub white: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GameConfig {
|
||||
pub name: String,
|
||||
// pub authors: Vec<Author>,
|
||||
pub base_window_size: (i32, i32),
|
||||
pub sentry_dsn: String,
|
||||
pub colors: ColorTheme,
|
||||
pub animation_fps: usize,
|
||||
}
|
||||
|
||||
impl GameConfig {
|
||||
@ -31,6 +44,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 {
|
||||
|
@ -1,8 +1,9 @@
|
||||
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;
|
||||
pub mod anim_render;
|
||||
|
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user