Merge pull request #19 from Ewpratten/master
Bring in rendering updates
This commit is contained in:
commit
03398a60d3
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
BIN
assets/ewpratten/env_beachTile/env_beachTile.xcf
Normal file
BIN
assets/ewpratten/env_beachTile/env_beachTile.xcf
Normal file
Binary file not shown.
BIN
assets/ewpratten/env_beachTile/env_beachTileSwirly.xcf
Normal file
BIN
assets/ewpratten/env_beachTile/env_beachTileSwirly.xcf
Normal file
Binary file not shown.
12
game.tiled-project
Normal file
12
game.tiled-project
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"automappingRulesFile": "",
|
||||
"commands": [
|
||||
],
|
||||
"extensionsPath": "extensions",
|
||||
"folders": [
|
||||
"game/dist"
|
||||
],
|
||||
"objectTypesFile": "",
|
||||
"propertyTypes": [
|
||||
]
|
||||
}
|
65
game.tiled-session
Normal file
65
game.tiled-session
Normal file
@ -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
|
||||
}
|
1
game/dist/.gitignore
vendored
Normal file
1
game/dist/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
!**/env/
|
BIN
game/dist/assets/env/env_beachTile/env_beachTile.png
vendored
Normal file
BIN
game/dist/assets/env/env_beachTile/env_beachTile.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
BIN
game/dist/assets/env/env_beachTile/env_beachTileSwirly.png
vendored
Normal file
BIN
game/dist/assets/env/env_beachTile/env_beachTileSwirly.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
42
game/dist/map_gameMap.tmx
vendored
Normal file
42
game/dist/map_gameMap.tmx
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.8" tiledversion="1.8.4" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="128" tileheight="128" infinite="1" nextlayerid="2" nextobjectid="1">
|
||||
<tileset firstgid="1" name="env_beachTile" tilewidth="128" tileheight="128" tilecount="2" columns="0">
|
||||
<grid orientation="orthogonal" width="1" height="1"/>
|
||||
<tile id="0">
|
||||
<properties>
|
||||
<property name="friction" type="float" value="1"/>
|
||||
<property name="temperature" type="float" value="10"/>
|
||||
</properties>
|
||||
<image width="128" height="128" source="assets/env/env_beachTile/env_beachTile.png"/>
|
||||
</tile>
|
||||
<tile id="1">
|
||||
<properties>
|
||||
<property name="friction" type="float" value="5"/>
|
||||
<property name="temperature" type="float" value="1"/>
|
||||
</properties>
|
||||
<image width="128" height="128" source="assets/env/env_beachTile/env_beachTileSwirly.png"/>
|
||||
</tile>
|
||||
</tileset>
|
||||
<layer id="1" name="Tile Layer 1" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
<chunk x="-16" y="0" width="16" height="16">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,2,2,1,1,2,2,0,0,0,0,
|
||||
0,0,0,0,0,0,2,2,1,1,2,2,0,0,0,0,
|
||||
0,0,0,0,0,0,2,1,0,0,1,2,0,0,0,0,
|
||||
0,0,0,0,0,0,2,2,1,1,2,2,0,0,0,0,
|
||||
0,0,0,0,0,0,2,2,1,1,2,2,0,0,0,0,
|
||||
0,0,0,0,0,0,1,1,2,2,1,1,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</chunk>
|
||||
</data>
|
||||
</layer>
|
||||
</map>
|
@ -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"
|
||||
nalgebra = "0.30.1"
|
||||
tiled = { version = "0.10.1", path = "../../third_party/rs-tiled" }
|
||||
|
11
game/game_logic/src/coord_convert.rs
Normal file
11
game/game_logic/src/coord_convert.rs
Normal file
@ -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<f32>) -> na::Vector2<f32> {
|
||||
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<f32>) -> na::Vector2<f32> {
|
||||
tiled_to_game(vec)
|
||||
}
|
@ -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.
|
||||
@ -74,6 +75,7 @@ pub async fn entrypoint(force_recreate_savefiles: bool) {
|
||||
|builder| {
|
||||
builder
|
||||
.msaa_4x()
|
||||
.resizable()
|
||||
// .vsync()
|
||||
.title(project_constants.game_name.as_str())
|
||||
.height(project_constants.base_window_size.1 as i32)
|
||||
|
213
game/game_logic/src/rendering/utilities/map_render.rs
Normal file
213
game/game_logic/src/rendering/utilities/map_render.rs
Normal file
@ -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<ResourcePathBuf, Arc<Tileset>>,
|
||||
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<ResourcePath>) -> Option<Arc<Tileset>> {
|
||||
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<F, E>(
|
||||
&mut self,
|
||||
path: ResourcePathBuf,
|
||||
f: F,
|
||||
) -> Result<Arc<Tileset>, E>
|
||||
where
|
||||
F: FnOnce() -> Result<Tileset, E>,
|
||||
{
|
||||
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<PathBuf, Texture2D>,
|
||||
}
|
||||
|
||||
impl MapRenderer {
|
||||
/// Construct a new MapRenderer.
|
||||
pub fn new(
|
||||
tmx_path: &str,
|
||||
raylib: &mut RaylibHandle,
|
||||
raylib_thread: &RaylibThread,
|
||||
) -> Result<Self, MapRenderError> {
|
||||
// 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>) -> f32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn sample_temperature_at(&self, position: na::Vector2<f32>) -> f32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn render_map(&self, draw_handle: &mut RaylibMode2D<RaylibDrawHandle>, 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod anim_texture;
|
||||
pub mod anim_texture;
|
||||
pub mod map_render;
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
third_party/rs-tiled
vendored
Submodule
1
third_party/rs-tiled
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1629541a446cff53c4dd6aa827aa1bb0afc4e96c
|
Reference in New Issue
Block a user