diff --git a/assets/ewpratten/env_testObject/env_testObject.xcf b/assets/ewpratten/env_testObject/env_testObject.xcf
new file mode 100644
index 00000000..23a45173
Binary files /dev/null and b/assets/ewpratten/env_testObject/env_testObject.xcf differ
diff --git a/game/dist/assets/audio/gameSoundtrack.mp3 b/game/dist/assets/audio/gameSoundtrack.mp3
new file mode 100644
index 00000000..ed58fae0
Binary files /dev/null and b/game/dist/assets/audio/gameSoundtrack.mp3 differ
diff --git a/game/dist/assets/env/env_beachTile/env_beachTile.png b/game/dist/assets/env/env_beachTile/env_beachTile.png
index fafdd41a..30d77e1b 100644
Binary files a/game/dist/assets/env/env_beachTile/env_beachTile.png and b/game/dist/assets/env/env_beachTile/env_beachTile.png differ
diff --git a/game/dist/assets/env/env_beachTile/env_beachTileWet.png b/game/dist/assets/env/env_beachTile/env_beachTileWet.png
new file mode 100644
index 00000000..ce190b53
Binary files /dev/null and b/game/dist/assets/env/env_beachTile/env_beachTileWet.png differ
diff --git a/game/dist/assets/env/env_boardwalkTile/env_boardwalkTile.png b/game/dist/assets/env/env_boardwalkTile/env_boardwalkTile.png
new file mode 100644
index 00000000..78020589
Binary files /dev/null and b/game/dist/assets/env/env_boardwalkTile/env_boardwalkTile.png differ
diff --git a/game/dist/assets/env/env_grassTile/env_grassTile.png b/game/dist/assets/env/env_grassTile/env_grassTile.png
new file mode 100644
index 00000000..41cf27a1
Binary files /dev/null and b/game/dist/assets/env/env_grassTile/env_grassTile.png differ
diff --git a/game/dist/assets/env/env_pavementTile/env_pavementTile.png b/game/dist/assets/env/env_pavementTile/env_pavementTile.png
new file mode 100644
index 00000000..70c509b2
Binary files /dev/null and b/game/dist/assets/env/env_pavementTile/env_pavementTile.png differ
diff --git a/game/dist/assets/env/env_pavementTile/env_pavementTileLine.png b/game/dist/assets/env/env_pavementTile/env_pavementTileLine.png
new file mode 100644
index 00000000..fa014c5c
Binary files /dev/null and b/game/dist/assets/env/env_pavementTile/env_pavementTileLine.png differ
diff --git a/game/dist/assets/env/env_roadTile/env_roadTile.png b/game/dist/assets/env/env_roadTile/env_roadTile.png
new file mode 100644
index 00000000..598e4c56
Binary files /dev/null and b/game/dist/assets/env/env_roadTile/env_roadTile.png differ
diff --git a/game/dist/assets/env/env_sidewalkTile/env_sidewalkTile.png b/game/dist/assets/env/env_sidewalkTile/env_sidewalkTile.png
new file mode 100644
index 00000000..07a592b7
Binary files /dev/null and b/game/dist/assets/env/env_sidewalkTile/env_sidewalkTile.png differ
diff --git a/game/dist/assets/env/env_testObject/env_testObject.json b/game/dist/assets/env/env_testObject/env_testObject.json
new file mode 100644
index 00000000..0655307a
--- /dev/null
+++ b/game/dist/assets/env/env_testObject/env_testObject.json
@@ -0,0 +1,23 @@
+{
+ "name": "env_testObject",
+ "bottom_texture": {
+ "file_path": "assets/env/env_testObject/env_testObjectBottom.png"
+ },
+ "top_texture": {
+ "file_path": "assets/env/env_testObject/env_testObjectTop.png"
+ },
+ "footprint_radius": 128.0,
+ "physics_colliders": [
+ {
+ "position": [
+ -118,
+ -60
+ ],
+ "size": [
+ 230,
+ 127
+ ]
+ }
+ ],
+ "temperature": 5.0
+}
\ No newline at end of file
diff --git a/game/dist/assets/env/env_testObject/env_testObjectBottom.png b/game/dist/assets/env/env_testObject/env_testObjectBottom.png
new file mode 100644
index 00000000..a3805038
Binary files /dev/null and b/game/dist/assets/env/env_testObject/env_testObjectBottom.png differ
diff --git a/game/dist/assets/env/env_testObject/env_testObjectTop.png b/game/dist/assets/env/env_testObject/env_testObjectTop.png
new file mode 100644
index 00000000..47f07118
Binary files /dev/null and b/game/dist/assets/env/env_testObject/env_testObjectTop.png differ
diff --git a/game/dist/map_gameMap.objects.json b/game/dist/map_gameMap.objects.json
new file mode 100644
index 00000000..e5bb13c2
--- /dev/null
+++ b/game/dist/map_gameMap.objects.json
@@ -0,0 +1,8 @@
+[
+ {
+ "type":"env",
+ "name":"env_testObject",
+ "position": [0,0],
+ "rotation_radians": 0.5
+ }
+]
\ No newline at end of file
diff --git a/game/dist/map_gameMap.tmx b/game/dist/map_gameMap.tmx
index f3384c58..a133aa5d 100644
--- a/game/dist/map_gameMap.tmx
+++ b/game/dist/map_gameMap.tmx
@@ -1,42 +1,194 @@
-
-
+
+
diff --git a/game/dist/project-constants.json b/game/dist/project-constants.json
index 3064e0eb..46f8c02e 100644
--- a/game/dist/project-constants.json
+++ b/game/dist/project-constants.json
@@ -5,6 +5,7 @@
720
],
"target_fps": 60,
+ "tile_size": 128,
"discord": {
"app_id": 954413081918857276,
"artwork": {
@@ -15,5 +16,11 @@
"details.sm_failure": "Game went FUBAR",
"details.main_menu": "In the main menu"
}
+ },
+ "player": {
+ "max_velocity": 3,
+ "acceleration": 2,
+ "deceleration": 1,
+ "start_size": 0.8
}
-}
\ No newline at end of file
+}
diff --git a/game/game_logic/Cargo.toml b/game/game_logic/Cargo.toml
index e285ec99..535b6535 100644
--- a/game/game_logic/Cargo.toml
+++ b/game/game_logic/Cargo.toml
@@ -25,5 +25,5 @@ thiserror = "1.0.30"
approx = "0.5.1"
poll-promise = { version = "0.1.0", features = ["tokio"] }
tempfile = "3.3.0"
-nalgebra = "0.30.1"
tiled = { version = "0.10.1", path = "../../third_party/rs-tiled" }
+nalgebra = { version = "0.30.1", features=["serde-serialize"]}
diff --git a/game/game_logic/src/asset_manager/mod.rs b/game/game_logic/src/asset_manager/mod.rs
index 2b5a023f..d9755643 100644
--- a/game/game_logic/src/asset_manager/mod.rs
+++ b/game/game_logic/src/asset_manager/mod.rs
@@ -24,4 +24,4 @@ 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};
\ No newline at end of file
+pub use texture::{load_texture_from_internal_data, ResourceLoadError, load_music_from_internal_data, load_sound_from_internal_data};
\ No newline at end of file
diff --git a/game/game_logic/src/asset_manager/texture.rs b/game/game_logic/src/asset_manager/texture.rs
index 36a819d8..975f2787 100644
--- a/game/game_logic/src/asset_manager/texture.rs
+++ b/game/game_logic/src/asset_manager/texture.rs
@@ -4,7 +4,11 @@
use std::path::Path;
-use raylib::{texture::Texture2D, RaylibHandle, RaylibThread};
+use raylib::{
+ audio::{Music, RaylibAudio, Sound},
+ texture::Texture2D,
+ RaylibHandle, RaylibThread,
+};
use tempfile::tempdir;
use crate::asset_manager::InternalData;
@@ -62,3 +66,70 @@ pub fn load_texture_from_internal_data(
Ok(texture)
}
+
+pub fn load_music_from_internal_data(
+ thread: &RaylibThread,
+ path: &str,
+) -> Result {
+ // 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 sound 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 = Music::load_music_stream(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)
+}
+
+pub fn load_sound_from_internal_data(
+ path: &str,
+) -> Result {
+ // 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 sound 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 =
+ Sound::load_sound(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)
+}
\ No newline at end of file
diff --git a/game/game_logic/src/model/mod.rs b/game/game_logic/src/model/mod.rs
index d44230b1..dbd3fca7 100644
--- a/game/game_logic/src/model/mod.rs
+++ b/game/game_logic/src/model/mod.rs
@@ -1 +1,3 @@
-pub mod player;
\ No newline at end of file
+pub mod player;
+pub mod world_object;
+pub mod world_object_package;
\ No newline at end of file
diff --git a/game/game_logic/src/model/player.rs b/game/game_logic/src/model/player.rs
index 2bb87f3e..69a7fe9f 100644
--- a/game/game_logic/src/model/player.rs
+++ b/game/game_logic/src/model/player.rs
@@ -5,6 +5,7 @@ use nalgebra as na;
pub struct Player {
pub position: na::Vector2,
pub velocity: na::Vector2,
+ pub size: f32,
}
impl Player {
@@ -14,7 +15,8 @@ impl Player {
Self {
position,
velocity: na::Vector2::zeros(),
+ size: 1.0,
}
}
-}
\ No newline at end of file
+}
diff --git a/game/game_logic/src/model/world_object.rs b/game/game_logic/src/model/world_object.rs
new file mode 100644
index 00000000..2a540451
--- /dev/null
+++ b/game/game_logic/src/model/world_object.rs
@@ -0,0 +1,64 @@
+use std::collections::HashMap;
+
+use nalgebra as na;
+use serde::Deserialize;
+
+use crate::{
+ asset_manager::{load_json_structure, InternalJsonLoadError},
+ rendering::utilities::anim_texture::AnimatedTexture,
+};
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct PossiblyAnimatedTexture {
+ /// Signal if the texture is animated or static
+ pub animated: Option,
+ /// Relative file path from `dist` to the texture
+ pub file_path: String,
+}
+
+/// Defines a collider in object space.
+#[derive(Debug, Clone, Deserialize)]
+pub struct ObjectCollider {
+ /// Position, relative to the object's center (north east is 1,1 south west is -1,-1)
+ pub position: na::Vector2,
+ /// Possible sizing
+ pub size: Option>,
+ /// Possible radius
+ pub radius: Option,
+}
+
+/// Definition of an object. Only one of these should exist *per object*, and they will be GPU instanced.
+#[derive(Debug, Clone, Deserialize)]
+pub struct WorldObject {
+ /// Object name. Must match the name of the texture
+ pub name: String,
+ /// The object's bottom texture
+ pub bottom_texture: PossiblyAnimatedTexture,
+ /// The object's top texture
+ pub top_texture: Option,
+ /// colliders describing the object's footprint
+ // pub footprint: Vec,
+ pub footprint_radius: Option,
+ /// Colliders for physics
+ pub physics_colliders: Vec,
+ /// Temperature
+ pub temperature: Option,
+ /// Friction
+ pub friction: Option,
+}
+
+/// Used to reference an object in the world definition
+#[derive(Debug, Clone, Deserialize)]
+pub struct WorldObjectRef {
+ /// Object type
+ #[serde(rename = "type")]
+ pub kind: String,
+ /// Object name
+ pub name: String,
+ /// Object position (tile-space *not* pixel-space). 1,1 being up and to the right
+ pub position: na::Vector2,
+ /// Object rotation, positive is clockwise
+ pub rotation_radians: f32,
+}
+
+
diff --git a/game/game_logic/src/model/world_object_package.rs b/game/game_logic/src/model/world_object_package.rs
new file mode 100644
index 00000000..8f8cd245
--- /dev/null
+++ b/game/game_logic/src/model/world_object_package.rs
@@ -0,0 +1,111 @@
+use std::collections::HashMap;
+
+use raylib::{texture::Texture2D, RaylibHandle, RaylibThread};
+
+use crate::{
+ asset_manager::{load_json_structure, load_texture_from_internal_data},
+ rendering::utilities::anim_texture::AnimatedTexture,
+};
+
+use super::world_object::{WorldObject, WorldObjectRef};
+
+#[derive(Debug, thiserror::Error)]
+pub enum WorldObjectPackageLoadError {
+ #[error(transparent)]
+ JsonError(#[from] crate::asset_manager::InternalJsonLoadError),
+ #[error(transparent)]
+ ResourceError(#[from] crate::asset_manager::ResourceLoadError),
+}
+
+/// A simply interface for the madness
+#[derive(Debug)]
+pub struct WorldObjectPackage {
+ /// The object definitions
+ pub object_definitions: HashMap,
+ /// The object references
+ pub object_references: Vec,
+ /// Bottom static textures
+ pub bottom_static_textures: HashMap,
+ /// Top static textures
+ pub top_static_textures: HashMap,
+ /// Bottom animated textures
+ pub bottom_animated_textures: HashMap,
+ /// Top animated textures
+ pub top_animated_textures: HashMap,
+}
+
+impl WorldObjectPackage {
+ pub fn load(
+ raylib_handle: &mut RaylibHandle,
+ thread: &RaylibThread,
+ map_objects_file_path: &str,
+ ) -> Result {
+ // Attempt to load the object reference list
+ let object_references: Vec = load_json_structure(map_objects_file_path)?;
+
+ // We also need to load the object definitions
+ let mut object_definitions = HashMap::new();
+ let mut bottom_static_textures = HashMap::new();
+ let mut top_static_textures = HashMap::new();
+ let mut bottom_animated_textures = HashMap::new();
+ let mut top_animated_textures = HashMap::new();
+ for reference in &object_references {
+ // If this is a new object, load it.
+ let object_key = format!("{}:{}", reference.kind, reference.name);
+ if !object_definitions.contains_key(object_key.as_str()) {
+ // Construct the file path from the data we know about the reference
+ let path = format!(
+ "assets/{}/{}/{}.json",
+ reference.kind, reference.name, reference.name
+ );
+
+ // Attempt to load the object definition
+ let object_definition: WorldObject = load_json_structure(&path)?;
+
+ // If this object has a static bottom texture, load it
+ if object_definition.bottom_texture.animated.unwrap_or(false) {
+ panic!("Animated bottom textures are not supported yet")
+ } else {
+ // Load the bottom texture and save it
+ bottom_static_textures.insert(
+ object_key.to_string(),
+ load_texture_from_internal_data(
+ raylib_handle,
+ thread,
+ &object_definition.bottom_texture.file_path,
+ )?,
+ );
+ }
+
+ // If there is a top texture, load it
+ if let Some(top_texture) = &object_definition.top_texture {
+ if top_texture.animated.unwrap_or(false) {
+ panic!("Animated top textures are not supported yet")
+ } else {
+ // Load the top texture and save it
+ top_static_textures.insert(
+ object_key.to_string(),
+ load_texture_from_internal_data(
+ raylib_handle,
+ thread,
+ &top_texture.file_path,
+ )?,
+ );
+ }
+ }
+
+ // Store the object definition
+ object_definitions.insert(object_key.to_string(), object_definition);
+ }
+ }
+
+ Ok(Self {
+ object_definitions,
+ object_references,
+ bottom_static_textures,
+ top_static_textures,
+ bottom_animated_textures,
+ top_animated_textures,
+ })
+ }
+}
diff --git a/game/game_logic/src/project_constants.rs b/game/game_logic/src/project_constants.rs
index 4350a297..6df497d3 100644
--- a/game/game_logic/src/project_constants.rs
+++ b/game/game_logic/src/project_constants.rs
@@ -28,6 +28,23 @@ pub struct DiscordConstants {
pub strings: HashMap,
}
+/// Constants relating to the Player
+#[derive(Debug, Deserialize)]
+pub struct PlayerConstants {
+
+ /// Maximum velocity, tiles per second
+ pub max_velocity: u32,
+
+ /// Acceleration, tiles per second per second
+ pub acceleration: u32,
+
+ /// Deceleration, tiles per second per second
+ pub deceleration: u32,
+
+ /// Starting size of player in tiles
+ pub start_size: f32,
+}
+
/// This structure is filled with the contents of `dist/project-constants.json` at runtime
#[derive(Debug, Deserialize)]
pub struct ProjectConstants {
@@ -40,6 +57,12 @@ pub struct ProjectConstants {
/// The Discord constants
pub discord: DiscordConstants,
+ /// The Player constants
+ pub player: PlayerConstants,
+
/// The target framerate of the game
pub target_fps: u32,
+
+ /// The size of the game tiles
+ pub tile_size: u32,
}
diff --git a/game/game_logic/src/rendering/event_loop.rs b/game/game_logic/src/rendering/event_loop.rs
index 3b9dc90a..cf9860ba 100644
--- a/game/game_logic/src/rendering/event_loop.rs
+++ b/game/game_logic/src/rendering/event_loop.rs
@@ -111,6 +111,11 @@ pub async fn handle_graphics_blocking(
constants,
)
.await;
+
+ // Handle exiting the game
+ if render_delegate.needs_exit {
+ break;
+ }
}
_ => backend_sm = RenderBackendStates::sm_failed(),
};
diff --git a/game/game_logic/src/rendering/utilities/anim_texture.rs b/game/game_logic/src/rendering/utilities/anim_texture.rs
index bbe593de..42bc7dc7 100644
--- a/game/game_logic/src/rendering/utilities/anim_texture.rs
+++ b/game/game_logic/src/rendering/utilities/anim_texture.rs
@@ -5,7 +5,7 @@ use nalgebra::Vector2;
use raylib::{
color::Color,
math::Rectangle,
- prelude::{RaylibDraw, RaylibDrawHandle},
+ prelude::{RaylibDraw, RaylibDrawHandle, RaylibMode2D},
texture::Texture2D,
RaylibHandle, RaylibThread,
};
@@ -127,7 +127,7 @@ impl AnimatedTexture {
#[profiling::function]
pub fn render_frame_by_index(
&self,
- draw_handle: &mut RaylibDrawHandle,
+ draw_handle: &mut RaylibMode2D,
index: usize,
position: Vector2,
percent_scale: Option>,
@@ -186,7 +186,7 @@ impl AnimatedTexture {
/// Render the animation based on timestamp
pub fn render_automatic(
&mut self,
- draw_handle: &mut RaylibDrawHandle,
+ draw_handle: &mut RaylibMode2D,
position: Vector2,
percent_scale: Option>,
origin: Option>,
@@ -216,4 +216,11 @@ impl AnimatedTexture {
warn!("We somehow got a frame index of None");
}
}
+
+ pub fn size(&self) -> Vector2{
+ Vector2::new(
+ self.texture.width as f32,
+ self.texture.height as f32,
+ )
+ }
}
diff --git a/game/game_logic/src/rendering/utilities/map_render.rs b/game/game_logic/src/rendering/utilities/map_render.rs
index b9822917..4f3cc3b2 100644
--- a/game/game_logic/src/rendering/utilities/map_render.rs
+++ b/game/game_logic/src/rendering/utilities/map_render.rs
@@ -1,16 +1,19 @@
use std::{collections::HashMap, path::PathBuf, sync::Arc};
-use crate::asset_manager::{load_texture_from_internal_data, InternalData};
+use crate::{
+ asset_manager::{load_texture_from_internal_data, InternalData},
+ model::world_object_package::WorldObjectPackage,
+};
use nalgebra as na;
use raylib::{
camera::Camera2D,
color::Color,
- math::Vector2,
+ math::{Rectangle, Vector2},
prelude::{RaylibDraw, RaylibDrawHandle, RaylibMode2D},
texture::Texture2D,
RaylibHandle, RaylibThread,
};
-use tiled::{Loader, Map, ResourceCache, ResourcePath, ResourcePathBuf, Tileset};
+use tiled::{Loader, Map, PropertyValue, ResourceCache, ResourcePath, ResourcePathBuf, Tileset};
/// Possible errors generated by the map loading process
#[derive(Debug, thiserror::Error)]
@@ -75,12 +78,14 @@ impl ResourceCache for ProgramDataTileCache {
pub struct MapRenderer {
map: Map,
tile_textures: HashMap,
+ world_objects: WorldObjectPackage,
}
impl MapRenderer {
/// Construct a new MapRenderer.
pub fn new(
tmx_path: &str,
+ objects_path: &str,
raylib: &mut RaylibHandle,
raylib_thread: &RaylibThread,
) -> Result {
@@ -116,18 +121,133 @@ impl MapRenderer {
}
}
- Ok(Self { map, tile_textures })
+ // Load the world objects
+ let world_objects = WorldObjectPackage::load(raylib, raylib_thread, objects_path).unwrap();
+
+ Ok(Self {
+ map,
+ tile_textures,
+ world_objects,
+ })
}
- pub fn sample_friction_at(&self, position: na::Vector2) -> f32 {
- todo!()
+ pub fn sample_friction_at(&self, world_position: na::Vector2) -> Option {
+ // Convert to a tile position
+ let tile_position = na::Vector2::new(
+ (world_position.x / 128.0).floor() as i32,
+ (world_position.y / 128.0).floor() as i32,
+ );
+
+ // If there is an object here, let it override the output
+ for obj_ref in &self.world_objects.object_references {
+ if obj_ref.position.x == tile_position.x as f32
+ && obj_ref.position.y == tile_position.y as f32
+ {
+ // Get access to the actual object definition
+ let object_key = format!("{}:{}", obj_ref.kind, obj_ref.name);
+ let obj_def = self
+ .world_objects
+ .object_definitions
+ .get(&object_key)
+ .unwrap();
+
+ // Check if there is a friction property
+ if let Some(friction) = obj_def.friction {
+ return Some(friction);
+ }
+ }
+ }
+
+ // Get the first layer
+ let layer = self.map.layers().next().unwrap();
+
+ // Handle the layer type
+ match layer.layer_type() {
+ tiled::LayerType::TileLayer(layer) => {
+ // Get the tile
+ if let Some(tile) = layer.get_tile(tile_position.x, tile_position.y) {
+ if let Some(tile) = tile.get_tile() {
+ if let Some(data) = tile.data.properties.get("friction") {
+ match data {
+ PropertyValue::FloatValue(f) => Some(*f),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
}
- pub fn sample_temperature_at(&self, position: na::Vector2) -> f32 {
- todo!()
+ pub fn sample_temperature_at(&self, world_position: na::Vector2) -> Option {
+ // Convert to a tile position
+ let tile_position = na::Vector2::new(
+ (world_position.x / 128.0).floor() as i32,
+ (world_position.y / 128.0).floor() as i32,
+ );
+
+ // If there is an object here, let it override the output
+ for obj_ref in &self.world_objects.object_references {
+ if obj_ref.position.x == tile_position.x as f32
+ && obj_ref.position.y == tile_position.y as f32
+ {
+ // Get access to the actual object definition
+ let object_key = format!("{}:{}", obj_ref.kind, obj_ref.name);
+ let obj_def = self
+ .world_objects
+ .object_definitions
+ .get(&object_key)
+ .unwrap();
+
+ // Check if there is a temperature property
+ if let Some(temperature) = obj_def.temperature {
+ return Some(temperature);
+ }
+ }
+ }
+
+ // Get the first layer
+ let layer = self.map.layers().next().unwrap();
+
+ // Handle the layer type
+ match layer.layer_type() {
+ tiled::LayerType::TileLayer(layer) => {
+ // Get the tile
+ if let Some(tile) = layer.get_tile(tile_position.x, tile_position.y) {
+ if let Some(tile) = tile.get_tile() {
+ if let Some(data) = tile.data.properties.get("temperature") {
+ match data {
+ PropertyValue::FloatValue(f) => Some(*f),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+ _ => None,
+ }
}
- pub fn render_map(&self, draw_handle: &mut RaylibMode2D, camera: &Camera2D, show_debug_grid:bool) {
+ pub fn render_map(
+ &mut self,
+ draw_handle: &mut RaylibMode2D,
+ camera: &Camera2D,
+ show_debug_grid: bool,
+ player_position: na::Vector2,
+ ) {
// Get the window corners in world space
let screen_width = draw_handle.get_screen_width();
let screen_height = draw_handle.get_screen_height();
@@ -187,8 +307,138 @@ impl MapRenderer {
tile_y * tile_height as i32,
Color::WHITE,
);
- }
-
+ }
+
+ // Check if there is an object at this tile
+ for obj_ref in &self.world_objects.object_references {
+ if obj_ref.position.x == sampler_x as f32
+ && obj_ref.position.y == sampler_y as f32
+ {
+ // Get access to the actual object definition
+ let object_key =
+ format!("{}:{}", obj_ref.kind, obj_ref.name);
+ let obj_def = self
+ .world_objects
+ .object_definitions
+ .get(&object_key)
+ .unwrap();
+
+ // We need to render the base layer of the object
+ if obj_def.bottom_texture.animated.unwrap_or(false) {
+ let tex = self
+ .world_objects
+ .bottom_animated_textures
+ .get_mut(&object_key)
+ .unwrap();
+ tex.render_automatic(
+ draw_handle,
+ obj_ref.position - (tex.size() / 2.0),
+ None,
+ Some(tex.size() / 2.0),
+ Some(obj_ref.rotation_radians.to_degrees()),
+ None,
+ );
+ } else {
+ let tex = self
+ .world_objects
+ .bottom_static_textures
+ .get_mut(&object_key)
+ .unwrap();
+ let p: Vector2 = obj_ref.position.into();
+ let r1 = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: tex.width as f32,
+ height: tex.height as f32,
+ };
+ let r2 = Rectangle {
+ x: p.x,
+ y: p.y,
+ width: tex.width as f32,
+ height: tex.height as f32,
+ };
+
+ draw_handle.draw_texture_pro(
+ &tex,
+ r1,
+ r2,
+ Vector2::new(
+ tex.width as f32 / 2.0,
+ tex.height as f32 / 2.0,
+ ),
+ obj_ref.rotation_radians.to_degrees(),
+ Color::WHITE,
+ );
+ }
+
+ // If needed we can render the top layer of the object
+ if let Some(top_texture) = &obj_def.top_texture {
+ // We need to detect if the player is in the footprint of the object
+ let mut tint = Color::WHITE;
+ if let Some(footprint_radius) =
+ obj_def.footprint_radius
+ {
+ let player_dist_to_object =
+ (obj_ref.position - player_position).norm();
+ // debug!(
+ // "Player dist to object: {}",
+ // player_dist_to_object
+ // );
+ if player_dist_to_object <= footprint_radius {
+ tint.a = 128;
+ }
+ }
+
+ if top_texture.animated.unwrap_or(false) {
+ let tex = self
+ .world_objects
+ .top_animated_textures
+ .get_mut(&object_key)
+ .unwrap();
+ tex.render_automatic(
+ draw_handle,
+ obj_ref.position - (tex.size() / 2.0),
+ None,
+ Some(tex.size() / 2.0),
+ Some(obj_ref.rotation_radians.to_degrees()),
+ Some(tint),
+ );
+ } else {
+ let tex = self
+ .world_objects
+ .top_static_textures
+ .get_mut(&object_key)
+ .unwrap();
+ let p: Vector2 = obj_ref.position.into();
+ let r1 = Rectangle {
+ x: 0.0,
+ y: 0.0,
+ width: tex.width as f32,
+ height: tex.height as f32,
+ };
+ let r2 = Rectangle {
+ x: p.x,
+ y: p.y,
+ width: tex.width as f32,
+ height: tex.height as f32,
+ };
+
+ draw_handle.draw_texture_pro(
+ &tex,
+ r1,
+ r2,
+ Vector2::new(
+ tex.width as f32 / 2.0,
+ tex.height as f32 / 2.0,
+ ),
+ obj_ref.rotation_radians.to_degrees(),
+ tint,
+ );
+ }
+ }
+ }
+ }
+
if show_debug_grid {
draw_handle.draw_rectangle_lines(
tile_x * tile_width as i32,
diff --git a/game/game_logic/src/scenes/main_menu.rs b/game/game_logic/src/scenes/main_menu.rs
index 568cec34..2279962c 100644
--- a/game/game_logic/src/scenes/main_menu.rs
+++ b/game/game_logic/src/scenes/main_menu.rs
@@ -25,7 +25,7 @@ pub enum MenuStateSignal {
#[derive(Debug)]
pub struct MainMenu {
- has_updated_discord_rpc: bool,
+ pub has_updated_discord_rpc: bool,
volume_percentage: f32,
}
diff --git a/game/game_logic/src/scenes/mod.rs b/game/game_logic/src/scenes/mod.rs
index e0b4a9d8..c71de971 100644
--- a/game/game_logic/src/scenes/mod.rs
+++ b/game/game_logic/src/scenes/mod.rs
@@ -23,6 +23,8 @@ mod test_fox;
/// This is a struct to allow for stateful data (like sub-screens) to be set up
pub struct SceneRenderDelegate {
menu_control_signal: MenuStateSignal,
+ pub needs_exit: bool,
+ audio_subsystem: RaylibAudio,
/* Scenes */
scene_test_fox: TestFoxScene,
scene_playable: PlayableScene,
@@ -36,7 +38,9 @@ impl SceneRenderDelegate {
rl_thread: &RaylibThread,
constants: &ProjectConstants,
) -> Self {
- // TODO: Stick any init code you want here.
+ // Set up audio
+ let audio_subsystem = RaylibAudio::init_audio_device();
+ audio_subsystem.set_master_volume(0.4);
// Init some scenes
let scene_test_fox = TestFoxScene::new(raylib, rl_thread);
@@ -45,6 +49,8 @@ impl SceneRenderDelegate {
Self {
menu_control_signal: MenuStateSignal::DoMainMenu,
+ needs_exit: false,
+ audio_subsystem,
scene_test_fox,
scene_playable,
scene_main_menu,
@@ -62,24 +68,29 @@ impl SceneRenderDelegate {
global_resources: &GlobalResources,
constants: &ProjectConstants,
) {
+
// Render the main menu if in it, otherwise, render the game
match self.menu_control_signal {
MenuStateSignal::StartGame => {
- // self.scene_playable
- // .render_frame(raylib, rl_thread, &discord, global_resources, constants)
- // .await;
-
- // TODO: remove this test scene
- self.scene_test_fox
- .render_frame(raylib, rl_thread, &discord, global_resources)
+ self.scene_playable
+ .render_frame(raylib, rl_thread, &discord, global_resources, constants, &mut self.audio_subsystem)
.await;
+ self.scene_playable.update_physics(raylib, constants).await;
+
+ // Clear the menu system discord status
+ self.scene_main_menu.has_updated_discord_rpc = false;
+ }
+ MenuStateSignal::QuitGame => {
+ self.needs_exit = true;
}
- MenuStateSignal::QuitGame => unimplemented!(),
MenuStateSignal::DoMainMenu => {
self.menu_control_signal = self
.scene_main_menu
.render_main_menu_frame(raylib, rl_thread, discord, global_resources, constants)
- .await
+ .await;
+
+ // Clear the ingame discord status
+ self.scene_playable.has_updated_discord_rpc = false;
}
MenuStateSignal::DoOptions => {
self.menu_control_signal = self
diff --git a/game/game_logic/src/scenes/player_interaction.rs b/game/game_logic/src/scenes/player_interaction.rs
index db34e0bd..dfcb9708 100644
--- a/game/game_logic/src/scenes/player_interaction.rs
+++ b/game/game_logic/src/scenes/player_interaction.rs
@@ -2,42 +2,76 @@
use nalgebra as na;
use raylib::prelude::*;
+use std::time::SystemTime;
use crate::{
+ asset_manager::{load_music_from_internal_data, load_sound_from_internal_data},
discord::{DiscordChannel, DiscordRpcSignal},
global_resource_package::GlobalResources,
model::player::Player,
project_constants::ProjectConstants,
- rendering::utilities::anim_texture::AnimatedTexture,
+ rendering::utilities::{anim_texture::AnimatedTexture, map_render::MapRenderer},
};
#[derive(Debug)]
pub struct PlayableScene {
- has_updated_discord_rpc: bool,
+ pub has_updated_discord_rpc: bool,
player: Player,
+ world_map: MapRenderer,
+ camera: raylib::camera::Camera2D,
+ last_update: SystemTime,
+ game_soundtrack: Music,
}
impl PlayableScene {
/// Construct a new `PlayableScene`
pub fn new(
- raylib_handle: &mut RaylibHandle,
- thread: &RaylibThread,
+ raylib_handle: &mut raylib::RaylibHandle,
+ thread: &raylib::RaylibThread,
constants: &ProjectConstants,
) -> Self {
+ let map_renderer = MapRenderer::new(
+ "map_gameMap.tmx",
+ "map_gameMap.objects.json",
+ raylib_handle,
+ thread,
+ )
+ .unwrap();
+
+ // Load the game music
+ let game_soundtrack =
+ load_music_from_internal_data(thread, "assets/audio/gameSoundtrack.mp3").unwrap();
+
Self {
has_updated_discord_rpc: false,
- player: Player::new(na::Vector2::new(10.0, 10.0)),
+ player: Player::new(na::Vector2::new(10.0 * constants.tile_size as f32, -10.0 * constants.tile_size as f32)),
+ world_map: map_renderer,
+ camera: raylib::camera::Camera2D {
+ target: raylib::math::Vector2 {
+ x: 0.0,
+ y: 0.0,
+ },
+ offset: raylib::math::Vector2 {
+ x: 0.0,
+ y: 0.0
+ },
+ rotation: 0.0,
+ zoom: 1.0,
+ },
+ last_update: SystemTime::UNIX_EPOCH,
+ game_soundtrack,
}
}
/// Handler for each frame
pub async fn render_frame(
&mut self,
- raylib: &mut RaylibHandle,
- rl_thread: &RaylibThread,
+ raylib: &mut raylib::RaylibHandle,
+ rl_thread: &raylib::RaylibThread,
discord: &DiscordChannel,
global_resources: &GlobalResources,
constants: &ProjectConstants,
+ audio_subsystem: &mut RaylibAudio,
) {
// Handle updating discord RPC
if !self.has_updated_discord_rpc {
@@ -55,13 +89,154 @@ impl PlayableScene {
self.has_updated_discord_rpc = true;
}
+ // Ensure the game soundtrack is playing
+ if !audio_subsystem.is_music_playing(&self.game_soundtrack) {
+ debug!("Playing game soundtrack");
+ audio_subsystem.play_music_stream(&mut self.game_soundtrack);
+ } else {
+ audio_subsystem.update_music_stream(&mut self.game_soundtrack);
+ }
+
// Get a drawing handle
let mut draw = raylib.begin_drawing(rl_thread);
// Clear the screen
draw.clear_background(Color::WHITE);
- // TODO: Render stuff
- // self.player.
+ self.draw_world(&mut draw, constants);
+
+ self.draw_ui(&mut draw, constants);
+ }
+
+ pub fn draw_world(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) {
+ // Begin camera mode
+ let mut ctx2d = draw.begin_mode2D(self.camera);
+
+ // Render the map
+ self.world_map
+ .render_map(&mut ctx2d, &self.camera, true, self.player.position);
+
+ // NOTE: This is how to check friction and temperature
+ let current_friction = self.world_map.sample_friction_at(self.player.position);
+ let current_temperature = self.world_map.sample_temperature_at(self.player.position);
+
+ let player_size =
+ (constants.tile_size as f32 * constants.player.start_size * self.player.size) as i32;
+
+ ctx2d.draw_rectangle(
+ self.player.position[0] as i32 - player_size / 2,
+ self.player.position[1] as i32 * -1 - player_size / 2,
+ player_size,
+ player_size,
+ Color::LIGHTBLUE,
+ );
+ }
+
+ pub fn draw_ui(&mut self, draw: &mut RaylibDrawHandle, constants: &ProjectConstants) {
+ draw.draw_rectangle(draw.get_screen_width() / 2 - 225, 0, 450, 40, Color::WHITE);
+ draw.draw_text(
+ "Unregistered HyperCam 2",
+ draw.get_screen_width() / 2 - 215,
+ 0,
+ 32,
+ Color::BLACK,
+ );
+ }
+
+ // Physics
+ pub async fn update_physics(
+ &mut self,
+ raylib: &raylib::RaylibHandle,
+ constants: &ProjectConstants,
+ ) {
+ // Get time since last physics update
+ let time = SystemTime::now();
+ let elapsed = time
+ .duration_since(self.last_update)
+ .expect("Time Appears to Have Moved Backwards!");
+ self.last_update = time;
+ let delta_time = elapsed.as_millis() as f32 / 1000.0; // Physics will be scaled by this value
+
+ let player = &mut self.player;
+
+ // Get input direction components
+ let h_axis = raylib.is_key_down(KeyboardKey::KEY_D) as i8
+ - raylib.is_key_down(KeyboardKey::KEY_A) as i8;
+ let v_axis = raylib.is_key_down(KeyboardKey::KEY_W) as i8
+ - raylib.is_key_down(KeyboardKey::KEY_S) as i8;
+ if h_axis != 0 || v_axis != 0 {
+ // Normalize input and accelerate in desired direction
+ let direction = na::Vector2::new(h_axis as f32, v_axis as f32).normalize();
+ player.velocity += &direction.xy()
+ * constants.player.acceleration as f32
+ * constants.tile_size as f32
+ * delta_time;
+ }
+
+ if player.velocity.magnitude() != 0.0 {
+ player.velocity -= player.velocity.normalize()
+ * constants.player.deceleration as f32
+ * constants.tile_size as f32
+ * delta_time;
+ if player.velocity.magnitude() < 1.0 {
+ player.velocity.set_magnitude(0.0);
+ }
+ }
+
+ if ((constants.player.max_velocity * constants.tile_size) as f32)
+ < player.velocity.magnitude()
+ {
+ player
+ .velocity
+ .set_magnitude((constants.player.max_velocity * constants.tile_size) as f32);
+ }
+
+ player.position += &player.velocity * delta_time;
+
+ self.update_camera(raylib);
+ }
+
+ // Update the camera
+ pub fn update_camera(
+ &mut self,
+ raylib: & raylib::RaylibHandle,
+ ) {
+
+ // Bounding box
+ let bbox = na::Vector2::new(0.2, 0.2);
+
+ // Get bounding box dimensions on the screen
+ let bbox_screen_min: raylib::math::Vector2 = (((na::Vector2::new(1.0, 1.0) - bbox) * 0.5).component_mul(
+ &na::Vector2::new(raylib.get_screen_width() as f32, raylib.get_screen_height() as f32)
+ )).into();
+ let bbox_screen_max: raylib::math::Vector2 = (((na::Vector2::new(1.0, 1.0) + bbox) * 0.5).component_mul(
+ &na::Vector2::new(raylib.get_screen_width() as f32, raylib.get_screen_height() as f32)
+ )).into();
+
+ // Get bounding box in world space
+ let mut bbox_world_min = raylib.get_screen_to_world2D(bbox_screen_min, self.camera);
+ let mut bbox_world_max = raylib.get_screen_to_world2D(bbox_screen_max, self.camera);
+
+ // Invert y
+ bbox_world_min.y *= -1.0;
+ bbox_world_max.y *= -1.0;
+
+ self.camera.offset = bbox_screen_min;
+
+ if self.player.position.x < bbox_world_min.x {
+ self.camera.target.x = self.player.position.x;
+ }
+
+ if self.player.position.y > bbox_world_min.y {
+ self.camera.target.y = -self.player.position.y;
+ }
+
+ if self.player.position.x > bbox_world_max.x {
+ self.camera.target.x = bbox_world_min.x + (self.player.position.x - bbox_world_max.x);
+ }
+
+ if self.player.position.y < bbox_world_max.y {
+ self.camera.target.y = bbox_world_max.y - (self.player.position.y + bbox_world_min.y);
+ }
}
}
diff --git a/game/game_logic/src/scenes/test_fox.rs b/game/game_logic/src/scenes/test_fox.rs
index d9d1973e..a4d19043 100644
--- a/game/game_logic/src/scenes/test_fox.rs
+++ b/game/game_logic/src/scenes/test_fox.rs
@@ -24,14 +24,20 @@ impl TestFoxScene {
let fox = AnimatedTexture::new(raylib_handle, thread, "chr", "testFox").unwrap();
// Load the map
- let map_renderer = MapRenderer::new("map_gameMap.tmx", raylib_handle, thread).unwrap();
+ let map_renderer = MapRenderer::new(
+ "map_gameMap.tmx",
+ "map_gameMap.objects.json",
+ raylib_handle,
+ thread,
+ )
+ .unwrap();
// Create a camera
let camera = Camera2D {
target: Vector2 { x: 0.0, y: 0.0 },
offset: Vector2 {
- x: raylib_handle.get_screen_width() as f32,
- y: (raylib_handle.get_screen_height() as f32) * -0.5,
+ x: raylib_handle.get_screen_width() as f32 * 0.5,
+ y: (raylib_handle.get_screen_height() as f32) * 0.5,
},
rotation: 0.0,
zoom: 1.0,
@@ -58,15 +64,15 @@ impl TestFoxScene {
// Clear the screen
draw.clear_background(Color::WHITE);
- // Render the fox
- self.fox_animation.render_automatic(
- &mut draw,
- na::Vector2::new(0.0, 0.0),
- None,
- None,
- None,
- None,
- );
+ // // Render the fox
+ // self.fox_animation.render_automatic(
+ // &mut draw,
+ // na::Vector2::new(0.0, 0.0),
+ // None,
+ // None,
+ // None,
+ // None,
+ // );
// Allow the camera to be moved with wasd
if draw.is_key_down(KeyboardKey::KEY_W) {
@@ -87,7 +93,19 @@ impl TestFoxScene {
let mut ctx2d = draw.begin_mode2D(self.camera);
// Render the map
- self.world_map.render_map(&mut ctx2d, &self.camera, true);
+ self.world_map.render_map(
+ &mut ctx2d,
+ &self.camera,
+ true,
+ na::Vector2::new(self.camera.target.x, self.camera.target.y).into(),
+ );
}
+
+ draw.draw_circle(
+ draw.get_screen_width() / 2,
+ draw.get_screen_height() / 2,
+ 4.0,
+ Color::RED,
+ );
}
}