Merge pull request #19 from Ewpratten/master

Bring in rendering updates
This commit is contained in:
Evan Pratten 2022-04-02 14:52:38 -04:00 committed by GitHub
commit 03398a60d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 419 additions and 13 deletions

3
.gitmodules vendored
View File

@ -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

Binary file not shown.

Binary file not shown.

12
game.tiled-project Normal file
View File

@ -0,0 +1,12 @@
{
"automappingRulesFile": "",
"commands": [
],
"extensionsPath": "extensions",
"folders": [
"game/dist"
],
"objectTypesFile": "",
"propertyTypes": [
]
}

65
game.tiled-session Normal file
View 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
View File

@ -0,0 +1 @@
!**/env/

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

42
game/dist/map_gameMap.tmx vendored Normal file
View 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>

View File

@ -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" }

View 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)
}

View File

@ -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)

View 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!(),
}
}
}
}

View File

@ -1 +1,2 @@
pub mod anim_texture;
pub mod anim_texture;
pub mod map_render;

View File

@ -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
},
}
}
}
}

View File

@ -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

@ -0,0 +1 @@
Subproject commit 1629541a446cff53c4dd6aa827aa1bb0afc4e96c