diff --git a/.gitmodules b/.gitmodules
index 9a8c6484..36c2f7aa 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,3 +6,6 @@
path = third_party/sm
url = https://github.com/Ewpratten/sm
ignore = dirty
+[submodule "third_party/rs-tiled"]
+ path = third_party/rs-tiled
+ url = https://github.com/Ewpratten/rs-tiled
diff --git a/assets/ewpratten/env_beachTile/env_beachTile.xcf b/assets/ewpratten/env_beachTile/env_beachTile.xcf
new file mode 100644
index 00000000..6f7bfc92
Binary files /dev/null and b/assets/ewpratten/env_beachTile/env_beachTile.xcf differ
diff --git a/assets/ewpratten/env_beachTile/env_beachTileSwirly.xcf b/assets/ewpratten/env_beachTile/env_beachTileSwirly.xcf
new file mode 100644
index 00000000..448b9929
Binary files /dev/null and b/assets/ewpratten/env_beachTile/env_beachTileSwirly.xcf differ
diff --git a/game.tiled-project b/game.tiled-project
new file mode 100644
index 00000000..05405582
--- /dev/null
+++ b/game.tiled-project
@@ -0,0 +1,12 @@
+{
+ "automappingRulesFile": "",
+ "commands": [
+ ],
+ "extensionsPath": "extensions",
+ "folders": [
+ "game/dist"
+ ],
+ "objectTypesFile": "",
+ "propertyTypes": [
+ ]
+}
diff --git a/game.tiled-session b/game.tiled-session
new file mode 100644
index 00000000..9095aa56
--- /dev/null
+++ b/game.tiled-session
@@ -0,0 +1,65 @@
+{
+ "Map/SizeTest": {
+ "height": 4300,
+ "width": 2
+ },
+ "activeFile": "",
+ "expandedProjectPaths": [
+ ],
+ "file.lastUsedOpenFilter": "All Files (*)",
+ "fileStates": {
+ "": {
+ "scaleInDock": 1
+ },
+ "#env_beachTile": {
+ "dynamicWrapping": false
+ },
+ "game/dist/assets/env/env_beachTile/beachTile.tsx": {
+ "dynamicWrapping": true,
+ "scaleInDock": 1,
+ "scaleInEditor": 1
+ },
+ "game/dist/assets/env/env_beachTile/beachTileSwirly.tsx": {
+ "dynamicWrapping": true,
+ "scaleInDock": 1,
+ "scaleInEditor": 1
+ },
+ "game/dist/map/map_gameMap.tmx": {
+ "scale": 0.33,
+ "selectedLayer": 0,
+ "viewCenter": {
+ "x": 200,
+ "y": -148.4848484848485
+ }
+ },
+ "game/dist/map_gameMap.tmx": {
+ "scale": 0.25,
+ "selectedLayer": 0,
+ "viewCenter": {
+ "x": 224,
+ "y": -80
+ }
+ },
+ "game/dist/map_gameMap.tmx#env_beachTile": {
+ "dynamicWrapping": false
+ }
+ },
+ "last.imagePath": "/home/ewpratten/projects/ludum-dare-50/game/dist/assets/env/env_beachTile",
+ "map.fixedSize": false,
+ "map.lastUsedFormat": "tmx",
+ "map.tileHeight": 128,
+ "map.tileWidth": 128,
+ "openFiles": [
+ ],
+ "project": "game.tiled-project",
+ "property.type": "float",
+ "recentFiles": [
+ "game/dist/assets/env/env_beachTile/beachTile.tsx",
+ "game/dist/map_gameMap.tmx",
+ "game/dist/assets/env/env_beachTile/beachTileSwirly.tsx",
+ "game/dist/map/map_gameMap.tmx"
+ ],
+ "tileset.embedInMap": true,
+ "tileset.lastUsedFormat": "tsx",
+ "tileset.type": 1
+}
diff --git a/game/dist/.gitignore b/game/dist/.gitignore
new file mode 100644
index 00000000..a06c1e50
--- /dev/null
+++ b/game/dist/.gitignore
@@ -0,0 +1 @@
+!**/env/
\ No newline at end of file
diff --git a/game/dist/assets/env/env_beachTile/env_beachTile.png b/game/dist/assets/env/env_beachTile/env_beachTile.png
new file mode 100644
index 00000000..fafdd41a
Binary files /dev/null and b/game/dist/assets/env/env_beachTile/env_beachTile.png differ
diff --git a/game/dist/assets/env/env_beachTile/env_beachTileSwirly.png b/game/dist/assets/env/env_beachTile/env_beachTileSwirly.png
new file mode 100644
index 00000000..d4563aa5
Binary files /dev/null and b/game/dist/assets/env/env_beachTile/env_beachTileSwirly.png differ
diff --git a/game/dist/map_gameMap.tmx b/game/dist/map_gameMap.tmx
new file mode 100644
index 00000000..f3384c58
--- /dev/null
+++ b/game/dist/map_gameMap.tmx
@@ -0,0 +1,42 @@
+
+
diff --git a/game/game_logic/Cargo.toml b/game/game_logic/Cargo.toml
index be0d2754..e285ec99 100644
--- a/game/game_logic/Cargo.toml
+++ b/game/game_logic/Cargo.toml
@@ -25,4 +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"
\ No newline at end of file
+nalgebra = "0.30.1"
+tiled = { version = "0.10.1", path = "../../third_party/rs-tiled" }
diff --git a/game/game_logic/src/coord_convert.rs b/game/game_logic/src/coord_convert.rs
new file mode 100644
index 00000000..92d4d9a4
--- /dev/null
+++ b/game/game_logic/src/coord_convert.rs
@@ -0,0 +1,11 @@
+use nalgebra as na;
+
+/// Converts from the tiled coordinate system to the game coordinate system.
+pub fn tiled_to_game(vec: na::Vector2) -> na::Vector2 {
+ na::Vector2::new(vec.x, vec.y * -1.0)
+}
+
+/// Converts from the game coordinate system to the tiled coordinate system.
+pub fn game_to_tiled(vec: na::Vector2) -> na::Vector2 {
+ tiled_to_game(vec)
+}
\ No newline at end of file
diff --git a/game/game_logic/src/lib.rs b/game/game_logic/src/lib.rs
index 7744fcec..f41e176c 100644
--- a/game/game_logic/src/lib.rs
+++ b/game/game_logic/src/lib.rs
@@ -40,6 +40,7 @@ pub(crate) mod project_constants;
pub(crate) mod rendering;
pub(crate) mod scenes;
pub(crate) mod model;
+pub(crate) mod coord_convert;
/// This is the game logic entrypoint. Despite being async,
/// this is expected to block the main thread for rendering and stuff.
diff --git a/game/game_logic/src/rendering/utilities/map_render.rs b/game/game_logic/src/rendering/utilities/map_render.rs
new file mode 100644
index 00000000..b9822917
--- /dev/null
+++ b/game/game_logic/src/rendering/utilities/map_render.rs
@@ -0,0 +1,213 @@
+use std::{collections::HashMap, path::PathBuf, sync::Arc};
+
+use crate::asset_manager::{load_texture_from_internal_data, InternalData};
+use nalgebra as na;
+use raylib::{
+ camera::Camera2D,
+ color::Color,
+ math::Vector2,
+ prelude::{RaylibDraw, RaylibDrawHandle, RaylibMode2D},
+ texture::Texture2D,
+ RaylibHandle, RaylibThread,
+};
+use tiled::{Loader, Map, ResourceCache, ResourcePath, ResourcePathBuf, Tileset};
+
+/// Possible errors generated by the map loading process
+#[derive(Debug, thiserror::Error)]
+pub enum MapRenderError {
+ #[error("Could not load embedded asset: {0}")]
+ AssetNotFound(String),
+ #[error(transparent)]
+ TiledError(#[from] tiled::Error),
+}
+
+#[derive(Debug)]
+struct ProgramDataTileCache {
+ tilesets: HashMap>,
+ internal_loader: Loader,
+}
+
+impl ProgramDataTileCache {
+ fn new() -> Self {
+ Self {
+ tilesets: HashMap::new(),
+ internal_loader: Loader::new(),
+ }
+ }
+}
+
+impl ResourceCache for ProgramDataTileCache {
+ /// Load the tileset. First attempts to pull from an in-RAM cache, otherwise attempts to load from disk.
+ fn get_tileset(&self, path: impl AsRef) -> Option> {
+ let possibly_cached_tileset = self.tilesets.get(path.as_ref()).map(Clone::clone);
+ if let Some(tileset) = possibly_cached_tileset {
+ return Some(tileset);
+ } else {
+ // Pull the TSX from storage and parse it
+ InternalData::get(path.as_ref().to_str().unwrap()).map(|file| {
+ let data = file.data.into_owned();
+ Arc::new(
+ self.internal_loader
+ .load_tsx_tileset_from(data.as_slice(), path)
+ .unwrap(),
+ )
+ })
+ }
+ }
+
+ fn get_or_try_insert_tileset_with(
+ &mut self,
+ path: ResourcePathBuf,
+ f: F,
+ ) -> Result, E>
+ where
+ F: FnOnce() -> Result,
+ {
+ Ok(match self.tilesets.entry(path) {
+ std::collections::hash_map::Entry::Occupied(o) => o.into_mut(),
+ std::collections::hash_map::Entry::Vacant(v) => v.insert(Arc::new(f()?)),
+ }
+ .clone())
+ }
+}
+
+#[derive(Debug)]
+pub struct MapRenderer {
+ map: Map,
+ tile_textures: HashMap,
+}
+
+impl MapRenderer {
+ /// Construct a new MapRenderer.
+ pub fn new(
+ tmx_path: &str,
+ raylib: &mut RaylibHandle,
+ raylib_thread: &RaylibThread,
+ ) -> Result {
+ // Pull the TMX from storage
+ let data = InternalData::get(tmx_path)
+ .ok_or(MapRenderError::AssetNotFound(tmx_path.to_string()))?
+ .data
+ .into_owned();
+
+ // Attempt to parse the TMX file
+ let mut loader = Loader::with_cache(ProgramDataTileCache::new());
+ let map = loader.load_tmx_map_from(data.as_slice(), tmx_path)?;
+
+ // Iterate over all images in the map
+ let mut tile_textures = HashMap::new();
+ for tileset in map.tilesets() {
+ for (idx, tile) in tileset.tiles() {
+ if let Some(image) = &tile.data.image {
+ // We now have a path to an image
+ let image_path = image.source.clone();
+
+ // Load the texture
+ let texture = load_texture_from_internal_data(
+ raylib,
+ raylib_thread,
+ image_path.to_str().unwrap(),
+ )
+ .unwrap();
+
+ // Store the texture in the cache
+ tile_textures.insert(image_path, texture);
+ }
+ }
+ }
+
+ Ok(Self { map, tile_textures })
+ }
+
+ pub fn sample_friction_at(&self, position: na::Vector2) -> f32 {
+ todo!()
+ }
+
+ pub fn sample_temperature_at(&self, position: na::Vector2) -> f32 {
+ todo!()
+ }
+
+ pub fn render_map(&self, draw_handle: &mut RaylibMode2D, camera: &Camera2D, show_debug_grid:bool) {
+ // Get the window corners in world space
+ let screen_width = draw_handle.get_screen_width();
+ let screen_height = draw_handle.get_screen_height();
+ let world_win_top_left = draw_handle.get_screen_to_world2D(Vector2::new(0.0, 0.0), camera);
+ let world_win_bottom_right = draw_handle.get_screen_to_world2D(
+ Vector2::new(screen_width as f32, screen_height as f32),
+ camera,
+ );
+
+ // Handle each layer from the bottom up
+ for layer in self.map.layers() {
+ // Handle different layer types
+ match layer.layer_type() {
+ tiled::LayerType::TileLayer(layer) => {
+ // Keep track of our sampler X and Y values
+ let mut sampler_x = 0;
+ let mut sampler_y = 0;
+
+ // Get the tile width and height
+ let tile_width = 128;
+ let tile_height = 128;
+
+ // Loop until we have covered all tiles on the screen
+ for y in (world_win_top_left.y as i64)..(world_win_bottom_right.y as i64) {
+ // Convert the pixel coordinates to tile coordinates
+ let tile_y = (y as f32 / tile_height as f32).floor() as i32;
+
+ // If we are looking at a new tile, update the sampler
+ if sampler_y != tile_y {
+ sampler_y = tile_y;
+
+ for x in
+ (world_win_top_left.x as i64)..(world_win_bottom_right.x as i64)
+ {
+ // Convert the pixel coordinates to tile coordinates
+ let tile_x = (x as f32 / tile_width as f32).floor() as i32;
+ // debug!("Tile: ({}, {})", tile_x, tile_y);
+
+ // If we are looking at a new tile, update the sampler
+ if sampler_x != tile_x {
+ sampler_x = tile_x;
+
+ // Get the tile at this coordinate
+ if let Some(tile) = layer.get_tile(sampler_x, sampler_y) {
+ // debug!("Tile: ({}, {})", tile_x, tile_y);
+ // Fetch the texture for this tile
+ let real_tile = tile.get_tile().unwrap();
+ let texture = self
+ .tile_textures
+ .get(&real_tile.image.as_ref().unwrap().source)
+ .unwrap();
+
+ // Draw the tile
+ draw_handle.draw_texture(
+ texture,
+ tile_x * tile_width as i32,
+ tile_y * tile_height as i32,
+ Color::WHITE,
+ );
+ }
+
+ if show_debug_grid {
+ draw_handle.draw_rectangle_lines(
+ tile_x * tile_width as i32,
+ tile_y * tile_height as i32,
+ self.map.tile_width as i32,
+ self.map.tile_height as i32,
+ Color::RED,
+ );
+ draw_handle.draw_pixel(x as i32, y as i32, Color::BLUE);
+ }
+ }
+ }
+ }
+ }
+ }
+ tiled::LayerType::ObjectLayer(_) => todo!(),
+ tiled::LayerType::ImageLayer(_) => todo!(),
+ tiled::LayerType::GroupLayer(_) => todo!(),
+ }
+ }
+ }
+}
diff --git a/game/game_logic/src/rendering/utilities/mod.rs b/game/game_logic/src/rendering/utilities/mod.rs
index 09d38ecf..524ae38e 100644
--- a/game/game_logic/src/rendering/utilities/mod.rs
+++ b/game/game_logic/src/rendering/utilities/mod.rs
@@ -1 +1,2 @@
-pub mod anim_texture;
\ No newline at end of file
+pub mod anim_texture;
+pub mod map_render;
\ No newline at end of file
diff --git a/game/game_logic/src/scenes/mod.rs b/game/game_logic/src/scenes/mod.rs
index ee1a40c8..e0b4a9d8 100644
--- a/game/game_logic/src/scenes/mod.rs
+++ b/game/game_logic/src/scenes/mod.rs
@@ -65,8 +65,13 @@ impl SceneRenderDelegate {
// 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)
+ // 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)
.await;
}
MenuStateSignal::QuitGame => unimplemented!(),
@@ -81,19 +86,25 @@ impl SceneRenderDelegate {
.scene_main_menu
.render_options_frame(raylib, rl_thread, discord, global_resources, constants)
.await
- },
+ }
MenuStateSignal::DoCredits => {
self.menu_control_signal = self
.scene_main_menu
.render_credits_frame(raylib, rl_thread, discord, global_resources, constants)
.await
- },
+ }
MenuStateSignal::DoLeaderboard => {
self.menu_control_signal = self
.scene_main_menu
- .render_leaderboard_frame(raylib, rl_thread, discord, global_resources, constants)
+ .render_leaderboard_frame(
+ raylib,
+ rl_thread,
+ discord,
+ global_resources,
+ constants,
+ )
.await
- },
+ }
}
}
}
diff --git a/game/game_logic/src/scenes/test_fox.rs b/game/game_logic/src/scenes/test_fox.rs
index 526dc57e..d9d1973e 100644
--- a/game/game_logic/src/scenes/test_fox.rs
+++ b/game/game_logic/src/scenes/test_fox.rs
@@ -1,17 +1,20 @@
//! This "scene" is used only for testing animation and resource loading
//! It should be removed once the game is being worked on
-use raylib::prelude::*;
use nalgebra as na;
+use raylib::prelude::*;
use crate::{
- discord::DiscordChannel, global_resource_package::GlobalResources,
- rendering::utilities::anim_texture::AnimatedTexture,
+ discord::DiscordChannel,
+ global_resource_package::GlobalResources,
+ rendering::utilities::{anim_texture::AnimatedTexture, map_render::MapRenderer},
};
#[derive(Debug)]
pub struct TestFoxScene {
fox_animation: AnimatedTexture,
+ world_map: MapRenderer,
+ camera: Camera2D,
}
impl TestFoxScene {
@@ -20,11 +23,29 @@ impl TestFoxScene {
// Load the fox texture
let fox = AnimatedTexture::new(raylib_handle, thread, "chr", "testFox").unwrap();
- Self { fox_animation: fox }
+ // Load the map
+ let map_renderer = MapRenderer::new("map_gameMap.tmx", 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,
+ },
+ rotation: 0.0,
+ zoom: 1.0,
+ };
+
+ Self {
+ fox_animation: fox,
+ world_map: map_renderer,
+ camera,
+ }
}
/// Handler for each frame
- pub fn render_frame(
+ pub async fn render_frame(
&mut self,
raylib: &mut RaylibHandle,
rl_thread: &RaylibThread,
@@ -46,5 +67,27 @@ impl TestFoxScene {
None,
None,
);
+
+ // Allow the camera to be moved with wasd
+ if draw.is_key_down(KeyboardKey::KEY_W) {
+ self.camera.target.y -= 5.0;
+ }
+ if draw.is_key_down(KeyboardKey::KEY_S) {
+ self.camera.target.y += 5.0;
+ }
+ if draw.is_key_down(KeyboardKey::KEY_A) {
+ self.camera.target.x -= 5.0;
+ }
+ if draw.is_key_down(KeyboardKey::KEY_D) {
+ self.camera.target.x += 5.0;
+ }
+
+ {
+ // Begin camera mode
+ let mut ctx2d = draw.begin_mode2D(self.camera);
+
+ // Render the map
+ self.world_map.render_map(&mut ctx2d, &self.camera, true);
+ }
}
}
diff --git a/third_party/rs-tiled b/third_party/rs-tiled
new file mode 160000
index 00000000..1629541a
--- /dev/null
+++ b/third_party/rs-tiled
@@ -0,0 +1 @@
+Subproject commit 1629541a446cff53c4dd6aa827aa1bb0afc4e96c