Merge branch 'main_menu_screen' of https://github.com/Ewpratten/ludum-dare-49 into main_menu_screen
9
.vscode/tasks.json
vendored
@ -45,6 +45,15 @@
|
||||
"env": {
|
||||
"RUST_LOG": "trace"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cargo",
|
||||
"subcommand": "build",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build",
|
||||
"label": "Rust: cargo build - ludum-dare-49"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# ludum-dare-49 [WIP]
|
||||
# [data::loss]
|
||||
[](https://github.com/Ewpratten/ludum-dare-49/actions/workflows/build.yml)
|
||||
[](https://github.com/Ewpratten/ludum-dare-49/actions/workflows/clippy.yml)
|
||||
[](https://ldjam.com/events/ludum-dare/49/$261521)
|
||||
|
@ -32,6 +32,7 @@ cfg-if = "1.0"
|
||||
num-derive = "0.3"
|
||||
num = "0.4"
|
||||
tiled = { version ="0.9.5", default-features = false }
|
||||
async-trait = "0.1.51"
|
||||
|
||||
[dev-dependencies]
|
||||
puffin_viewer = "0.6"
|
||||
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 46 KiB |
BIN
game/assets/character/player_run_old.png
Normal file
After Width: | Height: | Size: 138 KiB |
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Unnamed game",
|
||||
"name": "data::loss",
|
||||
"base_window_size": [
|
||||
1080,
|
||||
720
|
||||
|
BIN
game/assets/default-texture.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
game/assets/levels/level_0/background.png
Normal file
After Width: | Height: | Size: 46 KiB |
32
game/assets/levels/level_0/colliders.json
Normal file
@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"x": 322,
|
||||
"y": 926,
|
||||
"width": 610,
|
||||
"height": 73
|
||||
},
|
||||
{
|
||||
"x": 852,
|
||||
"y": 882,
|
||||
"width": 81,
|
||||
"height": 178
|
||||
},
|
||||
{
|
||||
"x": 852,
|
||||
"y": 882,
|
||||
"width": 258,
|
||||
"height": 33
|
||||
},
|
||||
{
|
||||
"x": 1599,
|
||||
"y": 699,
|
||||
"width": 918,
|
||||
"height": 63
|
||||
},
|
||||
{
|
||||
"x": 2733,
|
||||
"y": 789,
|
||||
"width": 108,
|
||||
"height": 211
|
||||
}
|
||||
]
|
BIN
game/assets/levels/level_0/platforms.png
Normal file
After Width: | Height: | Size: 16 KiB |
3
game/assets/levels/levels.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
"level_0"
|
||||
]
|
BIN
game/assets/logos/game-logo-small.png
Normal file
After Width: | Height: | Size: 63 KiB |
@ -1,12 +1,26 @@
|
||||
use std::ops::Mul;
|
||||
|
||||
use raylib::math::{Rectangle, Vector2};
|
||||
|
||||
use crate::scenes::ingame_scene::world::WORLD_LEVEL_X_OFFSET;
|
||||
|
||||
use super::{CharacterState, MainCharacter};
|
||||
|
||||
const GRAVITY_PPS: f32 = 2.0;
|
||||
pub const GRAVITY_PPS: f32 = 2.0;
|
||||
|
||||
pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), ()> {
|
||||
// Convert the player to a rectangle
|
||||
pub fn modify_player_based_on_forces(
|
||||
player: &mut MainCharacter,
|
||||
colliders: &Vec<Rectangle>,
|
||||
level_height_offset: f32,
|
||||
) -> Result<(), ()> {
|
||||
// Modify the player's velocity by the forces
|
||||
player.movement_force += player.base_velocity;
|
||||
player.velocity = player.movement_force;
|
||||
|
||||
// Predict the player's position next frame
|
||||
let predicted_player_position = player.position + player.velocity;
|
||||
|
||||
// Calculate a bounding rect around the player both now, and one frame in the future
|
||||
let player_rect = Rectangle::new(
|
||||
predicted_player_position.x - (player.size.x / 2.0),
|
||||
predicted_player_position.y - (player.size.x / 2.0),
|
||||
@ -17,24 +31,44 @@ pub fn modify_player_based_on_forces(player: &mut MainCharacter) -> Result<(), (
|
||||
// Calculate a generic "floor" to always collide with
|
||||
let floor_rect = Rectangle::new(f32::MIN, 0.0, f32::MAX, 1.0);
|
||||
|
||||
// Check collision conditions
|
||||
let check_player_colliding_with_floor = || floor_rect.check_collision_recs(&player_rect);
|
||||
let check_player_colliding_with_floor_next_frame =
|
||||
|| player_rect.y + player_rect.height > floor_rect.y;
|
||||
let check_player_colliding_with_colliders = || {
|
||||
colliders.iter().any(|rect| {
|
||||
let mut translated_rect = rect.clone();
|
||||
translated_rect.y += level_height_offset;
|
||||
translated_rect.x += WORLD_LEVEL_X_OFFSET;
|
||||
translated_rect.check_collision_recs(&player_rect)
|
||||
})
|
||||
};
|
||||
|
||||
// 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
|
||||
if (check_player_colliding_with_floor()
|
||||
|| check_player_colliding_with_floor_next_frame()
|
||||
|| check_player_colliding_with_colliders())
|
||||
&& 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);
|
||||
if player.current_state == CharacterState::Jumping
|
||||
|| player.current_state == CharacterState::Dashing
|
||||
{
|
||||
player.update_player(
|
||||
Some(CharacterState::Running),
|
||||
colliders,
|
||||
level_height_offset,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Error out if colliding in the X direction
|
||||
// Check sideways collisions
|
||||
|
||||
// Apply the force
|
||||
// Finally apply the velocity to the player
|
||||
player.position += player.velocity;
|
||||
|
||||
// Apply gravity
|
||||
player.velocity.y += GRAVITY_PPS;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2,13 +2,16 @@ pub mod collisions;
|
||||
pub mod render;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use raylib::{math::Vector2, texture::Texture2D};
|
||||
use raylib::{
|
||||
math::{Rectangle, Vector2},
|
||||
texture::Texture2D,
|
||||
};
|
||||
|
||||
use crate::utilities::anim_render::AnimatedSpriteSheet;
|
||||
|
||||
use self::collisions::modify_player_based_on_forces;
|
||||
use self::collisions::{modify_player_based_on_forces, GRAVITY_PPS};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub enum CharacterState {
|
||||
#[default]
|
||||
Running,
|
||||
@ -19,6 +22,8 @@ pub enum CharacterState {
|
||||
#[derive(Debug)]
|
||||
pub struct MainCharacter {
|
||||
pub position: Vector2,
|
||||
pub movement_force: Vector2,
|
||||
pub base_velocity: Vector2,
|
||||
pub velocity: Vector2,
|
||||
pub size: Vector2,
|
||||
pub sprite_sheet: AnimatedSpriteSheet,
|
||||
@ -30,8 +35,10 @@ impl MainCharacter {
|
||||
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),
|
||||
movement_force: Vector2::zero(),
|
||||
velocity: Vector2::zero(),
|
||||
base_velocity: Vector2::new(0.0, GRAVITY_PPS),
|
||||
size: Vector2::new(100.0, 100.0),
|
||||
sprite_sheet: AnimatedSpriteSheet::new(
|
||||
sprite_sheet,
|
||||
Vector2::new(300.0, 300.0),
|
||||
@ -44,20 +51,28 @@ impl MainCharacter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_force(&mut self, force: Vector2) -> Option<()> {
|
||||
self.velocity = force;
|
||||
modify_player_based_on_forces(self).unwrap();
|
||||
Some(())
|
||||
}
|
||||
pub fn update_player(
|
||||
&mut self,
|
||||
state: Option<CharacterState>,
|
||||
colliders: &Vec<Rectangle>,
|
||||
level_height_offset: f32,
|
||||
) {
|
||||
if let Some(state) = state {
|
||||
// Update the internal state
|
||||
if state != self.current_state {
|
||||
self.current_state = state.clone();
|
||||
self.state_set_timestamp = Utc::now();
|
||||
}
|
||||
|
||||
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();
|
||||
// Handle extra external forces based on the character state
|
||||
self.movement_force = match state {
|
||||
CharacterState::Running => Vector2::new(12.0, 0.0),
|
||||
CharacterState::Jumping => Vector2::new(12.0, -30.0),
|
||||
CharacterState::Dashing => Vector2::new(30.0, -20.0),
|
||||
};
|
||||
}
|
||||
|
||||
// Update the player based on the new velocity
|
||||
modify_player_based_on_forces(self, colliders, level_height_offset).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::ops::{Div, Sub};
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
|
||||
use chrono::Utc;
|
||||
use raylib::prelude::*;
|
||||
@ -37,4 +37,16 @@ pub fn render_character_in_camera_space(
|
||||
Some(Vector2::new(player.size.y, player.size.y)),
|
||||
Some(frame_id),
|
||||
);
|
||||
|
||||
// Possibly render a debug vector
|
||||
if config.debug_view {
|
||||
raylib.draw_line_v(
|
||||
player.position.sub(player.size.div(2.0)),
|
||||
player
|
||||
.position
|
||||
.sub(player.size.div(2.0))
|
||||
.add(player.velocity.mul(10.0).add(Vector2::new(0.0, 100.0))),
|
||||
Color::RED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, sync::mpsc::Sender};
|
||||
|
||||
use discord_sdk::activity::ActivityBuilder;
|
||||
|
||||
use crate::{GameConfig, utilities::non_ref_raylib::HackedRaylibHandle};
|
||||
|
||||
@ -6,7 +8,8 @@ use crate::{GameConfig, utilities::non_ref_raylib::HackedRaylibHandle};
|
||||
#[derive(Debug)]
|
||||
pub struct GameContext {
|
||||
pub renderer: RefCell<HackedRaylibHandle>,
|
||||
pub config: GameConfig
|
||||
pub config: GameConfig,
|
||||
pub discord_rpc_send: Sender<Option<ActivityBuilder>>
|
||||
}
|
||||
|
||||
// impl GameContext {
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![feature(derive_default_enum)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
#![feature(async_await)]
|
||||
#![feature(c_variadic)]
|
||||
#![deny(unsafe_code)]
|
||||
#![warn(
|
||||
@ -69,7 +70,7 @@
|
||||
)]
|
||||
#![clippy::msrv = "1.57.0"]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, sync::mpsc::TryRecvError};
|
||||
|
||||
use discord_sdk::activity::ActivityBuilder;
|
||||
use raylib::prelude::*;
|
||||
@ -97,6 +98,8 @@ extern crate serde;
|
||||
extern crate approx;
|
||||
#[macro_use]
|
||||
extern crate num_derive;
|
||||
#[macro_use]
|
||||
extern crate async_trait;
|
||||
|
||||
mod context;
|
||||
mod discord_rpc;
|
||||
@ -106,7 +109,7 @@ pub use utilities::{datastore::StaticGameData, game_config::GameConfig};
|
||||
mod character;
|
||||
|
||||
/// The game entrypoint
|
||||
pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub async fn game_begin(game_config: &mut GameConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set up profiling
|
||||
#[cfg(debug_assertions)]
|
||||
let _puffin_server =
|
||||
@ -133,11 +136,14 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
};
|
||||
maybe_set_discord_presence(
|
||||
&discord_rpc,
|
||||
ActivityBuilder::default().details("Testing..."),
|
||||
ActivityBuilder::default().details("Game starting"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Build an MPSC for the game to send rich presence data to discord
|
||||
let (send_discord_rpc, recv_discord_rpc) = std::sync::mpsc::channel();
|
||||
|
||||
let context;
|
||||
let raylib_thread;
|
||||
{
|
||||
@ -148,7 +154,7 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
game_config.base_window_size.0,
|
||||
game_config.base_window_size.1,
|
||||
)
|
||||
.title(&game_config.name)
|
||||
.title(&format!("[{}]", game_config.name))
|
||||
.vsync()
|
||||
.msaa_4x()
|
||||
.resizable()
|
||||
@ -161,6 +167,7 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
context = Box::new(GameContext {
|
||||
renderer: RefCell::new(rl.into()),
|
||||
config: game_config.clone(),
|
||||
discord_rpc_send: send_discord_rpc,
|
||||
});
|
||||
}
|
||||
|
||||
@ -198,7 +205,6 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
&raylib_thread,
|
||||
)?;
|
||||
|
||||
|
||||
while !context.renderer.borrow().window_should_close() {
|
||||
// Profile the main game loop
|
||||
puffin::profile_scope!("main_loop");
|
||||
@ -209,6 +215,27 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
.update(&mut context.renderer.borrow_mut(), &raylib_thread)
|
||||
.unwrap();
|
||||
|
||||
// If in dev mode, allow a debug key
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if context
|
||||
.renderer
|
||||
.borrow()
|
||||
.is_key_pressed(KeyboardKey::KEY_F3)
|
||||
{
|
||||
game_config.debug_view = !game_config.debug_view;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle fullscreen shortcut
|
||||
if context
|
||||
.renderer
|
||||
.borrow()
|
||||
.is_key_pressed(KeyboardKey::KEY_F11)
|
||||
{
|
||||
context.renderer.borrow_mut().toggle_fullscreen();
|
||||
}
|
||||
|
||||
// Switch into draw mode the unsafe way (using unsafe code here to avoid borrow checker hell)
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
@ -260,6 +287,22 @@ pub async fn game_begin(game_config: &GameConfig) -> Result<(), Box<dyn std::err
|
||||
unsafe {
|
||||
raylib::ffi::EndDrawing();
|
||||
}
|
||||
|
||||
// Try to update discord
|
||||
match recv_discord_rpc.try_recv() {
|
||||
Ok(activity) => {
|
||||
if let Some(activity) = activity {
|
||||
if let Err(e) = maybe_set_discord_presence(&discord_rpc, activity).await {
|
||||
error!("Failed to update discord presence: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(TryRecvError::Empty) => {}
|
||||
Err(TryRecvError::Disconnected) => {
|
||||
error!("Discord RPC channel disconnected");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use discord_sdk::activity::{ActivityBuilder, Assets};
|
||||
use raylib::{color::Color, prelude::RaylibDraw};
|
||||
use tracing::{debug, trace};
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use crate::{
|
||||
context::GameContext,
|
||||
@ -26,8 +27,20 @@ impl Action<Scenes, ScreenError, GameContext> for FsmErrorScreen {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
|
||||
fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
|
||||
debug!("Running FsmErrorScreen for the first time");
|
||||
|
||||
// Update discord
|
||||
if let Err(e) = context.discord_rpc_send.send(Some(
|
||||
ActivityBuilder::default()
|
||||
.details("IT FUCKING DIED")
|
||||
.assets(
|
||||
Assets::default().large("game-logo-small", Some(context.config.name.clone())),
|
||||
),
|
||||
)) {
|
||||
error!("Failed to update discord: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
53
game/src/scenes/ingame_scene/level/loader.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use raylib::{RaylibHandle, RaylibThread};
|
||||
|
||||
use crate::{
|
||||
utilities::datastore::{load_texture_from_internal_data, ResourceLoadError},
|
||||
StaticGameData,
|
||||
};
|
||||
|
||||
use super::Level;
|
||||
|
||||
pub fn load_all_levels(
|
||||
raylib_handle: &mut RaylibHandle,
|
||||
thread: &RaylibThread,
|
||||
) -> Result<Vec<Level>, ResourceLoadError> {
|
||||
// Get a listing of all levels we have
|
||||
let level_names: Vec<String> = serde_json::from_str(
|
||||
&String::from_utf8(
|
||||
StaticGameData::get("levels/levels.json")
|
||||
.expect("Could not load levels.json")
|
||||
.data
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
)?;
|
||||
|
||||
// Build a level list
|
||||
let mut levels = Vec::new();
|
||||
|
||||
for level_name in &level_names {
|
||||
levels.push(Level {
|
||||
name: level_name.to_string(),
|
||||
background_tex: load_texture_from_internal_data(
|
||||
raylib_handle,
|
||||
thread,
|
||||
&format!("levels/{}/background.png", level_name),
|
||||
)?,
|
||||
platform_tex: load_texture_from_internal_data(
|
||||
raylib_handle,
|
||||
thread,
|
||||
&format!("levels/{}/platforms.png", level_name),
|
||||
)?,
|
||||
colliders: serde_json::from_str(
|
||||
&String::from_utf8(
|
||||
StaticGameData::get(&format!("levels/{}/colliders.json", level_name))
|
||||
.unwrap()
|
||||
.data
|
||||
.into(),
|
||||
)
|
||||
.unwrap(),
|
||||
)?,
|
||||
});
|
||||
}
|
||||
Ok(levels)
|
||||
}
|
11
game/src/scenes/ingame_scene/level/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use raylib::{math::Rectangle, texture::Texture2D};
|
||||
|
||||
pub mod loader;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Level {
|
||||
pub name: String,
|
||||
pub background_tex: Texture2D,
|
||||
pub platform_tex: Texture2D,
|
||||
pub colliders: Vec<Rectangle>
|
||||
}
|
@ -1,24 +1,42 @@
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use discord_sdk::activity::{ActivityBuilder, Assets};
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{character::{CharacterState, MainCharacter}, context::GameContext, utilities::render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender}};
|
||||
use crate::{
|
||||
character::{CharacterState, MainCharacter},
|
||||
context::GameContext,
|
||||
utilities::{
|
||||
render_layer::{FrameUpdate, ScreenSpaceRender, WorldSpaceRender},
|
||||
world_paint_texture::WorldPaintTexture,
|
||||
},
|
||||
};
|
||||
|
||||
use self::level::Level;
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, trace};
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
mod hud;
|
||||
pub mod level;
|
||||
mod update;
|
||||
mod world;
|
||||
pub mod world;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InGameScreen {
|
||||
camera: Camera2D,
|
||||
player: MainCharacter,
|
||||
world_background: WorldPaintTexture,
|
||||
levels: Vec<Level>,
|
||||
current_level_idx: usize,
|
||||
}
|
||||
|
||||
impl InGameScreen {
|
||||
/// Construct a new `InGameScreen`
|
||||
pub fn new(player_sprite_sheet: Texture2D) -> Self {
|
||||
pub fn new(
|
||||
player_sprite_sheet: Texture2D,
|
||||
background_texture: Texture2D,
|
||||
levels: Vec<Level>,
|
||||
) -> Self {
|
||||
Self {
|
||||
camera: Camera2D {
|
||||
offset: Vector2::zero(),
|
||||
@ -26,7 +44,10 @@ impl InGameScreen {
|
||||
rotation: 0.0,
|
||||
zoom: 1.0,
|
||||
},
|
||||
player: MainCharacter::new(Vector2::new(0.0, -80.0), player_sprite_sheet),
|
||||
player: MainCharacter::new(Vector2::new(0.0, -85.0), player_sprite_sheet),
|
||||
world_background: WorldPaintTexture::new(background_texture),
|
||||
levels,
|
||||
current_level_idx: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,11 +58,25 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
|
||||
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);
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
self.player.update_player(
|
||||
Some(CharacterState::Running),
|
||||
&cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,
|
||||
);
|
||||
|
||||
// Update discord
|
||||
if let Err(e) = context.discord_rpc_send.send(Some(
|
||||
ActivityBuilder::default().details("in game").assets(
|
||||
Assets::default().large("game-logo-small", Some(context.config.name.clone())),
|
||||
),
|
||||
)) {
|
||||
error!("Failed to update discord: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -75,7 +110,11 @@ impl Action<Scenes, ScreenError, GameContext> for InGameScreen {
|
||||
// Render the HUD
|
||||
self.render_screen_space(&mut renderer, &context.config);
|
||||
|
||||
Ok(ActionFlag::Continue)
|
||||
if renderer.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
Ok(ActionFlag::SwitchState(Scenes::PauseScreen))
|
||||
} else {
|
||||
Ok(ActionFlag::Continue)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_finish(&mut self, _interrupted: bool) -> Result<(), ScreenError> {
|
||||
|
@ -17,26 +17,37 @@ impl FrameUpdate for InGameScreen {
|
||||
config: &GameConfig,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
|
||||
// Get the current level
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
|
||||
|
||||
// Set the camera's offset based on screen size
|
||||
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_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);
|
||||
let is_jump = raylib.is_key_pressed(KeyboardKey::KEY_SPACE)
|
||||
&& !(self.player.current_state == CharacterState::Jumping);
|
||||
let is_dash = raylib.is_key_pressed(KeyboardKey::KEY_LEFT_SHIFT)
|
||||
&& !(self.player.current_state == CharacterState::Dashing);
|
||||
|
||||
if is_jump {
|
||||
self.player.apply_force(Vector2::new(0.0, -30.0));
|
||||
self.player.set_state(CharacterState::Jumping);
|
||||
self.player.update_player(Some(CharacterState::Jumping), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else if is_dash {
|
||||
self.player.apply_force(Vector2::new(40.0, -10.0));
|
||||
self.player.set_state(CharacterState::Dashing);
|
||||
self.player.update_player(Some(CharacterState::Dashing), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else {
|
||||
if self.player.current_state != CharacterState::Jumping {
|
||||
self.player.set_state(CharacterState::Running);
|
||||
if self.player.current_state != CharacterState::Jumping
|
||||
&& self.player.current_state != CharacterState::Dashing
|
||||
{
|
||||
self.player.update_player(Some(CharacterState::Running), &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
} else {
|
||||
self.player.update_player(None, &cur_level.colliders,
|
||||
-cur_level.platform_tex.height as f32,);
|
||||
}
|
||||
}
|
||||
self.player.update_gravity();
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use crate::{
|
||||
};
|
||||
use raylib::prelude::*;
|
||||
|
||||
pub const WORLD_LEVEL_X_OFFSET: f32 = 200.0;
|
||||
|
||||
impl WorldSpaceRender for InGameScreen {
|
||||
fn render_world_space(
|
||||
&mut self,
|
||||
@ -15,8 +17,15 @@ impl WorldSpaceRender for InGameScreen {
|
||||
config: &GameConfig,
|
||||
) {
|
||||
puffin::profile_function!();
|
||||
// Render the player
|
||||
render_character_in_camera_space(raylib, &self.player, &config);
|
||||
|
||||
// Get the current level
|
||||
let cur_level = self.levels.get(self.current_level_idx).unwrap();
|
||||
|
||||
// Render the world background
|
||||
// self.world_background.render(raylib, Vector2::new(0.0, -1080.0), &self.camera);
|
||||
|
||||
// Render the platform layer
|
||||
raylib.draw_texture_v(&cur_level.platform_tex, Vector2::new(WORLD_LEVEL_X_OFFSET, -cur_level.platform_tex.height as f32), Color::WHITE);
|
||||
|
||||
// Render the floor as a line
|
||||
let screen_world_zero = raylib.get_screen_to_world2D(Vector2::zero(), self.camera);
|
||||
@ -30,5 +39,9 @@ impl WorldSpaceRender for InGameScreen {
|
||||
5,
|
||||
config.colors.white,
|
||||
);
|
||||
|
||||
|
||||
// Render the player
|
||||
render_character_in_camera_space(raylib, &self.player, &config);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use std::ops::{Div, Sub};
|
||||
use cfg_if::cfg_if;
|
||||
use chrono::{DateTime, Utc};
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use discord_sdk::activity::{ActivityBuilder, Assets};
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{GameConfig, context::GameContext, utilities::{
|
||||
@ -13,7 +14,7 @@ use crate::{GameConfig, context::GameContext, utilities::{
|
||||
}};
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, info, trace};
|
||||
use tracing::{debug, info, error, trace};
|
||||
|
||||
/// Defines how long the loading screen should be displayed.
|
||||
const LOADING_SCREEN_DURATION_SECONDS: u8 = 3;
|
||||
@ -49,9 +50,18 @@ impl Action<Scenes, ScreenError, GameContext> for LoadingScreen {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
|
||||
fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
|
||||
debug!("Running LoadingScreen for the first time");
|
||||
|
||||
// Update discord
|
||||
if let Err(e) = context.discord_rpc_send.send(Some(
|
||||
ActivityBuilder::default().details("loading...").assets(
|
||||
Assets::default().large("game-logo-small", Some(context.config.name.clone())),
|
||||
),
|
||||
)) {
|
||||
error!("Failed to update discord: {}", e);
|
||||
}
|
||||
|
||||
// Keep track of when this screen is opened
|
||||
self.start_timestamp = Some(Utc::now());
|
||||
|
||||
|
@ -2,19 +2,24 @@ use std::ops::{Div, Sub};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use discord_sdk::activity::{ActivityBuilder, Assets};
|
||||
use pkg_version::pkg_version_major;
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{GameConfig, context::GameContext, utilities::{
|
||||
use crate::{
|
||||
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,
|
||||
}};
|
||||
},
|
||||
GameConfig,
|
||||
};
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, info, trace};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MainMenuScreen {
|
||||
@ -44,9 +49,18 @@ impl Action<Scenes, ScreenError, GameContext> for MainMenuScreen {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_first_run(&mut self, _context: &GameContext) -> Result<(), ScreenError> {
|
||||
fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
|
||||
debug!("Running MainMenuScreen for the first time");
|
||||
|
||||
// Update discord
|
||||
if let Err(e) = context.discord_rpc_send.send(Some(
|
||||
ActivityBuilder::default().details("main menu").assets(
|
||||
Assets::default().large("game-logo-small", Some(context.config.name.clone())),
|
||||
),
|
||||
)) {
|
||||
error!("Failed to update discord: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -89,7 +103,7 @@ impl ScreenSpaceRender for MainMenuScreen {
|
||||
fn render_screen_space(
|
||||
&mut self,
|
||||
raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
|
||||
config: &GameConfig
|
||||
config: &GameConfig,
|
||||
) {
|
||||
// Render the background
|
||||
raylib.clear_background(Color::BLACK);
|
||||
|
@ -1,4 +1,10 @@
|
||||
use self::{fsm_error_screen::FsmErrorScreen, options_screen::OptionsScreen, how_to_play_screen::HowToPlayScreen, ingame_scene::InGameScreen, loading_screen::LoadingScreen, main_menu_screen::MainMenuScreen};
|
||||
use self::{
|
||||
pause_screen::PauseScreen,
|
||||
fsm_error_screen::FsmErrorScreen,
|
||||
ingame_scene::{level::loader::load_all_levels, InGameScreen},
|
||||
loading_screen::LoadingScreen,
|
||||
main_menu_screen::MainMenuScreen, options_screen::OptionsScreen, how_to_play_screen::HowToPlayScreen,
|
||||
};
|
||||
use crate::{
|
||||
context::GameContext,
|
||||
utilities::{
|
||||
@ -15,6 +21,7 @@ pub mod loading_screen;
|
||||
pub mod main_menu_screen;
|
||||
pub mod how_to_play_screen;
|
||||
pub mod options_screen;
|
||||
pub mod pause_screen;
|
||||
|
||||
/// Defines all scenes
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
|
||||
@ -26,6 +33,7 @@ pub enum Scenes {
|
||||
InGameScene,
|
||||
HowToPlayScreen,
|
||||
OptionsScreen,
|
||||
PauseScreen,
|
||||
}
|
||||
|
||||
/// Contains any possible errors thrown while rendering
|
||||
@ -47,6 +55,9 @@ pub fn build_screen_state_machine(
|
||||
// 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();
|
||||
let world_background =
|
||||
load_texture_from_internal_data(raylib_handle, thread, "default-texture.png").unwrap();
|
||||
let levels = load_all_levels(raylib_handle, thread).unwrap();
|
||||
|
||||
// Set up the state machine
|
||||
let mut machine = StateMachine::new();
|
||||
@ -56,9 +67,13 @@ 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(player_sprite_sheet))?;
|
||||
machine.add_action(Scenes::HowToPlayScreen, HowToPlayScreen::new())?;
|
||||
machine.add_action(Scenes::OptionsScreen, OptionsScreen::new())?;
|
||||
machine.add_action(Scenes::PauseScreen, PauseScreen::new())?;
|
||||
machine.add_action(
|
||||
Scenes::InGameScene,
|
||||
InGameScreen::new(player_sprite_sheet, world_background, levels),
|
||||
)?;
|
||||
Ok(machine)
|
||||
|
||||
}
|
||||
|
202
game/src/scenes/pause_screen.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use std::ops::{Div, Sub};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use dirty_fsm::{Action, ActionFlag};
|
||||
use discord_sdk::activity::{ActivityBuilder, Assets};
|
||||
use pkg_version::pkg_version_major;
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
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,
|
||||
},
|
||||
GameConfig,
|
||||
};
|
||||
|
||||
use super::{Scenes, ScreenError};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PauseScreen {}
|
||||
|
||||
impl PauseScreen {
|
||||
/// Construct a new `PauseScreen`
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Action<Scenes, ScreenError, GameContext> for PauseScreen {
|
||||
fn on_register(&mut self) -> Result<(), ScreenError> {
|
||||
debug!("Registered");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_first_run(&mut self, context: &GameContext) -> Result<(), ScreenError> {
|
||||
debug!("Running PauseScreen for the first time");
|
||||
|
||||
// Update discord
|
||||
if let Err(e) = context.discord_rpc_send.send(Some(
|
||||
ActivityBuilder::default().details("paused").assets(
|
||||
Assets::default().large("game-logo-small", Some(context.config.name.clone())),
|
||||
),
|
||||
)) {
|
||||
error!("Failed to update discord: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_delta: &chrono::Duration,
|
||||
context: &GameContext,
|
||||
) -> Result<dirty_fsm::ActionFlag<Scenes>, ScreenError> {
|
||||
trace!("execute() called on PauseScreen");
|
||||
self.render_screen_space(&mut context.renderer.borrow_mut(), &context.config);
|
||||
|
||||
//Mouse Position
|
||||
let mouse_position: Vector2 = context.renderer.borrow_mut().get_mouse_position();
|
||||
//Mouse Input
|
||||
let is_left_click = context
|
||||
.renderer
|
||||
.borrow_mut()
|
||||
.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
|
||||
|
||||
//"Hitboxes" for the resume and Main menu buttons
|
||||
if is_left_click
|
||||
&& Rectangle::new(322.0, 321.0, 435.0, 80.0).check_collision_point_rec(mouse_position)
|
||||
{
|
||||
return Ok(ActionFlag::SwitchState(Scenes::InGameScene));
|
||||
}
|
||||
|
||||
if is_left_click
|
||||
&& Rectangle::new(390.0, 464.0, 200.0, 50.0).check_collision_point_rec(mouse_position)
|
||||
{
|
||||
return Ok(ActionFlag::SwitchState(Scenes::MainMenuScreen));
|
||||
}
|
||||
|
||||
if context
|
||||
.renderer
|
||||
.borrow_mut()
|
||||
.is_key_pressed(KeyboardKey::KEY_ESCAPE)
|
||||
{
|
||||
Ok(ActionFlag::SwitchState(Scenes::InGameScene))
|
||||
} else {
|
||||
Ok(ActionFlag::Continue)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_finish(&mut self, _interrupted: bool) -> Result<(), ScreenError> {
|
||||
debug!("Finished PauseScreen");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenSpaceRender for PauseScreen {
|
||||
fn render_screen_space(
|
||||
&mut self,
|
||||
raylib: &mut crate::utilities::non_ref_raylib::HackedRaylibHandle,
|
||||
config: &GameConfig,
|
||||
) {
|
||||
let screen_size = raylib.get_screen_size();
|
||||
|
||||
// Render the background
|
||||
raylib.clear_background(Color::BLACK.fade(50.0));
|
||||
|
||||
//Mouse Position
|
||||
let mouse_position: Vector2 = raylib.get_mouse_position();
|
||||
//Mouse Input
|
||||
let is_left_click = raylib.is_mouse_button_down(MouseButton::MOUSE_LEFT_BUTTON);
|
||||
|
||||
//Pause Menu Texts With Glitchy Effect
|
||||
raylib.draw_text(
|
||||
"Paused",
|
||||
(screen_size.x as i32 / 2) - 223,
|
||||
(screen_size.y as i32 / 2) - 40,
|
||||
120,
|
||||
Color::RED,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Paused",
|
||||
(screen_size.x as i32 / 2) - 217,
|
||||
(screen_size.y as i32 / 2) - 40,
|
||||
120,
|
||||
Color::BLUE,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Paused",
|
||||
(screen_size.x as i32 / 2) - 220,
|
||||
(screen_size.y as i32 / 2) - 40,
|
||||
120,
|
||||
Color::WHITE,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Click To Resume",
|
||||
(screen_size.x as i32 / 2) - 80,
|
||||
(screen_size.y as i32 / 2) + 60,
|
||||
20,
|
||||
Color::RED,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Click To Resume",
|
||||
(screen_size.x as i32 / 2) - 80,
|
||||
(screen_size.y as i32 / 2) + 60,
|
||||
20,
|
||||
Color::BLUE,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Click To Resume",
|
||||
(screen_size.x as i32 / 2) - 80,
|
||||
(screen_size.y as i32 / 2) + 60,
|
||||
20,
|
||||
Color::WHITE,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Main Menu",
|
||||
(screen_size.x as i32 / 2) - 123,
|
||||
(screen_size.y as i32 / 2) + 100,
|
||||
50,
|
||||
Color::RED,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Main Menu",
|
||||
(screen_size.x as i32 / 2) - 117,
|
||||
(screen_size.y as i32 / 2) + 100,
|
||||
50,
|
||||
Color::BLUE,
|
||||
);
|
||||
raylib.draw_text(
|
||||
"Main Menu",
|
||||
(screen_size.x as i32 / 2) - 120,
|
||||
(screen_size.y as i32 / 2) + 100,
|
||||
50,
|
||||
Color::WHITE,
|
||||
);
|
||||
|
||||
if Rectangle::new(390.0, 464.0, 200.0, 50.0).check_collision_point_rec(mouse_position) {
|
||||
raylib.draw_text(
|
||||
"Main Menu",
|
||||
(screen_size.x as i32 / 2) - 120,
|
||||
(screen_size.y as i32 / 2) + 100,
|
||||
50,
|
||||
Color::YELLOW,
|
||||
);
|
||||
}
|
||||
|
||||
if Rectangle::new(322.0, 321.0, 435.0, 80.0).check_collision_point_rec(mouse_position) {
|
||||
raylib.draw_text(
|
||||
"Paused",
|
||||
(screen_size.x as i32 / 2) - 220,
|
||||
(screen_size.y as i32 / 2) - 40,
|
||||
120,
|
||||
Color::DARKBLUE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,8 @@ pub struct StaticGameData;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ResourceLoadError {
|
||||
#[error(transparent)]
|
||||
JsonDeser(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Could not load embedded asset: {0}")]
|
||||
|
@ -30,6 +30,9 @@ pub struct GameConfig {
|
||||
pub sentry_dsn: String,
|
||||
pub colors: ColorTheme,
|
||||
pub animation_fps: usize,
|
||||
|
||||
#[serde(skip)]
|
||||
pub debug_view: bool
|
||||
}
|
||||
|
||||
impl GameConfig {
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod anim_render;
|
||||
pub mod datastore;
|
||||
pub mod discord;
|
||||
pub mod game_config;
|
||||
@ -6,4 +7,4 @@ pub mod math;
|
||||
pub mod non_ref_raylib;
|
||||
pub mod render_layer;
|
||||
pub mod shaders;
|
||||
pub mod anim_render;
|
||||
pub mod world_paint_texture;
|
||||
|
57
game/src/utilities/world_paint_texture.rs
Normal file
@ -0,0 +1,57 @@
|
||||
//! Defines a texture that tiles across the whole screen in world space
|
||||
|
||||
use raylib::{
|
||||
camera::Camera2D,
|
||||
color::Color,
|
||||
math::Vector2,
|
||||
prelude::{RaylibDraw, RaylibMode2D},
|
||||
texture::Texture2D,
|
||||
RaylibHandle,
|
||||
};
|
||||
|
||||
use super::non_ref_raylib::HackedRaylibHandle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorldPaintTexture {
|
||||
texture: Texture2D,
|
||||
}
|
||||
|
||||
impl WorldPaintTexture {
|
||||
/// Construct a new world paint texture
|
||||
pub fn new(texture: Texture2D) -> Self {
|
||||
Self { texture }
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&self,
|
||||
raylib: &mut RaylibMode2D<'_, HackedRaylibHandle>,
|
||||
origin: Vector2,
|
||||
camera: &Camera2D,
|
||||
) {
|
||||
// Convert the screen edges to world space
|
||||
let top_left = raylib.get_screen_to_world2D(Vector2::new(0.0, 0.0), camera);
|
||||
let bottom_right = raylib.get_screen_to_world2D(raylib.get_screen_size(), camera);
|
||||
|
||||
// Calculate the distance between the edges and the origin
|
||||
let left_edge_distance = top_left.x - origin.x;
|
||||
let right_edge_distance = bottom_right.x - origin.x;
|
||||
|
||||
// Calculate the x position to draw the tile in order for there always to be a tile covering the edges
|
||||
let left_tile_x =
|
||||
(left_edge_distance / self.texture.width as f32).floor() * self.texture.width as f32;
|
||||
let right_tile_x =
|
||||
left_tile_x + self.texture.width as f32;
|
||||
|
||||
// Render the tiles
|
||||
raylib.draw_texture_v(
|
||||
&self.texture,
|
||||
Vector2::new(left_tile_x, origin.y),
|
||||
Color::WHITE,
|
||||
);
|
||||
raylib.draw_texture_v(
|
||||
&self.texture,
|
||||
Vector2::new(right_tile_x, origin.y),
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ async fn main() {
|
||||
|
||||
// Load the general config for the game
|
||||
// This happens here so we can properly track sentry events
|
||||
let game_config = GameConfig::load(
|
||||
let mut game_config = GameConfig::load(
|
||||
StaticGameData::get("configs/application.json").expect("Failed to load application.json"),
|
||||
).unwrap();
|
||||
|
||||
@ -22,5 +22,5 @@ async fn main() {
|
||||
));
|
||||
|
||||
// Start the game
|
||||
game_begin(&game_config).await.unwrap();
|
||||
game_begin(&mut game_config).await.unwrap();
|
||||
}
|
||||
|