From 80e33fa7e68c37f7c643659a4539c8b2d3f9199e Mon Sep 17 00:00:00 2001 From: silvana Date: Sun, 4 Aug 2024 13:45:07 +0200 Subject: [PATCH] Add(assets): tiled maps support. --- .woodpecker/html.yml | 27 +-- .woodpecker/quality.yml | 53 +++-- Cargo.lock | 54 +++++ Cargo.toml | 3 + Dockerfile | 2 +- assets/maps/test.tmx | 30 +++ assets/textures/grass_tiles.png | 3 + src/lib.rs | 14 +- src/loaders/mod.rs | 1 + src/loaders/tiled.rs | 381 ++++++++++++++++++++++++++++++++ src/loading.rs | 9 +- src/map.rs | 18 ++ src/player.rs | 3 +- 13 files changed, 561 insertions(+), 37 deletions(-) create mode 100644 assets/maps/test.tmx create mode 100644 assets/textures/grass_tiles.png create mode 100644 src/loaders/mod.rs create mode 100644 src/loaders/tiled.rs create mode 100644 src/map.rs diff --git a/.woodpecker/html.yml b/.woodpecker/html.yml index cc7e6b7..64814bf 100644 --- a/.woodpecker/html.yml +++ b/.woodpecker/html.yml @@ -2,16 +2,20 @@ when: - event: [tag] steps: - cache-restore: - image: minio/mc - secrets: [access_key, secret_key] - commands: - - mc alias set minio https://minio.ragarock.moe $access_key $secret_key - - mc cp -quiet --recursive minio/yuno/cache-html/target target - failure: ignore build: image: git.ragarock.moe/silvana/yuno/rust:latest - environment: [CARGO_TERM_COLOR=always, CARGO_HOME=./.cargo-home] + secrets: [access_key, secret_key] + environment: + CARGO_TERM_COLOR: "always" + CARGO_HOME: "./.cargo-home" + RUSTC_WRAPPER: "/usr/bin/sccache" + SCCACHE_ENDPOINT: "https://minio.ragarock.moe" + SCCACHE_BUCKET: "cache" + SCCACHE_REGION: "auto" + AWS_ACCESS_KEY_ID: + from_secret: access_key + AWS_SECRET_ACCESS_KEY: + from_secret: secret_key commands: - rustup default nightly - rustup target add wasm32-unknown-unknown @@ -22,10 +26,3 @@ steps: commands: - mc alias set minio https://minio.ragarock.moe $access_key $secret_key - mc cp -quiet --recursive dist/ minio/yuno/${CI_COMMIT_TAG}/ - cache-upload: - image: minio/mc - secrets: [access_key, secret_key] - commands: - - mc alias set minio https://minio.ragarock.moe $access_key $secret_key - - mc cp -quiet --recursive target/ minio/yuno/cache-html/target - failure: ignore diff --git a/.woodpecker/quality.yml b/.woodpecker/quality.yml index df912d2..3d0f092 100644 --- a/.woodpecker/quality.yml +++ b/.woodpecker/quality.yml @@ -2,38 +2,57 @@ when: - event: [pull_request] steps: - cache-restore: - image: minio/mc - secrets: [access_key, secret_key] - commands: - - mc alias set minio https://minio.ragarock.moe $access_key $secret_key - - mc cp -quiet --recursive minio/yuno/cache-amd64/target target - failure: ignore fmt: image: git.ragarock.moe/silvana/yuno/rust:latest - environment: [CARGO_TERM_COLOR=always, CARGO_HOME=./.cargo-home] + secrets: [access_key, secret_key] + environment: + CARGO_TERM_COLOR: "always" + CARGO_HOME: "./.cargo-home" + RUSTC_WRAPPER: "/usr/bin/sccache" + SCCACHE_ENDPOINT: "https://minio.ragarock.moe" + SCCACHE_BUCKET: "cache" + SCCACHE_REGION: "auto" + AWS_ACCESS_KEY_ID: + from_secret: access_key + AWS_SECRET_ACCESS_KEY: + from_secret: secret_key commands: - rustup default nightly - rustup component add rustfmt - cargo fmt -- --check clippy: image: git.ragarock.moe/silvana/yuno/rust:latest - environment: [CARGO_TERM_COLOR=always, CARGO_HOME=./.cargo-home] + secrets: [access_key, secret_key] + environment: + CARGO_TERM_COLOR: "always" + CARGO_HOME: "./.cargo-home" + RUSTC_WRAPPER: "/usr/bin/sccache" + SCCACHE_ENDPOINT: "https://minio.ragarock.moe" + SCCACHE_BUCKET: "cache" + SCCACHE_REGION: "auto" + AWS_ACCESS_KEY_ID: + from_secret: access_key + AWS_SECRET_ACCESS_KEY: + from_secret: secret_key commands: - rustup default nightly - rustup component add clippy - cargo clippy -- -D warnings test: image: git.ragarock.moe/silvana/yuno/rust:latest - environment: [CARGO_TERM_COLOR=always, CARGO_HOME=./.cargo-home] + secrets: [access_key, secret_key] + environment: + CARGO_TERM_COLOR: "always" + CARGO_HOME: "./.cargo-home" + RUSTC_WRAPPER: "/usr/bin/sccache" + SCCACHE_ENDPOINT: "https://minio.ragarock.moe" + SCCACHE_BUCKET: "cache" + SCCACHE_REGION: "auto" + AWS_ACCESS_KEY_ID: + from_secret: access_key + AWS_SECRET_ACCESS_KEY: + from_secret: secret_key commands: - rustup default nightly - cargo check - cargo test - cache-upload: - image: minio/mc - secrets: [access_key, secret_key] - commands: - - mc alias set minio https://minio.ragarock.moe $access_key $secret_key - - mc cp -quiet --recursive target/ minio/yuno/cache-amd64/target - failure: ignore diff --git a/Cargo.lock b/Cargo.lock index 1599035..a9a050d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,6 +566,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "bevy_ecs_tilemap" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d880047f5deaf5166ffc08238125a4ccbd2837f781ca6525fa200fcf5785ba3b" +dependencies = [ + "bevy", + "log", + "regex", +] + [[package]] name = "bevy_encase_derive" version = "0.14.1" @@ -3560,6 +3571,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiled" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad408d366c0e1e7e4e504cabc14c77fdda77176d93b3c6abe5b3b31885df3ad0" +dependencies = [ + "base64 0.22.1", + "flate2", + "xml-rs", + "zstd", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -4514,9 +4537,12 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_asset_loader", + "bevy_ecs_tilemap", "bevy_kira_audio", "image", "log", + "thiserror", + "tiled", "webbrowser", "winit", ] @@ -4541,3 +4567,31 @@ dependencies = [ "quote", "syn 2.0.72", ] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 6cbeaac..56f5725 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,3 +70,6 @@ log = { version = "0.4", features = [ "max_level_debug", "release_max_level_warn", ] } +thiserror = "1.0.63" +bevy_ecs_tilemap = { version = "0.14.0" } +tiled = { version = "0.12.0", features = ["wasm"] } diff --git a/Dockerfile b/Dockerfile index 80d145c..a6f683e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:edge -RUN apk add curl build-base mold clang gcc libc-dev pkgconf libx11-dev alsa-lib-dev eudev-dev +RUN apk add curl build-base mold clang gcc libc-dev pkgconf libx11-dev alsa-lib-dev eudev-dev sccache WORKDIR /app diff --git a/assets/maps/test.tmx b/assets/maps/test.tmx new file mode 100644 index 0000000..639283f --- /dev/null +++ b/assets/maps/test.tmx @@ -0,0 +1,30 @@ + + + + + + + +1,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,1,1,1,3,3,3,1,1,1, +1,3,1,1,1,3,3,1,1,1,1,1,1,1,3,3,3,3,3,3,1,1,3,3,3,1,3,3,1,1, +1,3,1,3,3,3,1,1,1,1,3,3,1,1,1,3,1,1,1,1,1,1,3,3,1,1,1,3,1,1, +1,1,4,3,1,1,1,1,6,1,3,3,1,1,1,1,1,1,1,1,3,3,3,3,3,4,4,3,3,1, +1,1,3,3,4,4,4,4,4,1,1,1,1,1,1,3,1,1,6,1,1,1,1,1,1,4,4,4,3,1, +1,1,3,1,1,1,1,1,4,1,1,1,1,3,3,3,1,1,1,1,1,1,4,4,1,4,4,3,4,1, +1,3,3,1,1,1,1,1,4,1,3,3,3,3,1,1,3,1,5,5,1,1,5,4,1,1,4,3,4,4, +1,4,1,1,1,6,1,4,4,1,3,3,1,1,6,1,3,1,5,5,1,3,3,3,4,1,1,3,1,4, +1,4,4,1,1,1,3,3,1,1,3,3,1,1,1,1,3,3,1,5,5,3,5,1,3,4,4,3,1,4, +1,3,4,1,1,3,3,3,1,1,1,3,3,3,3,3,3,3,3,1,5,3,5,5,3,3,4,3,3,4, +1,3,3,1,1,3,4,1,3,1,1,1,3,1,1,1,3,3,3,3,1,3,1,5,1,3,3,4,4,1, +1,4,3,1,1,1,1,1,3,3,1,1,3,3,5,5,3,1,1,3,3,3,1,1,1,1,3,1,1,1, +1,1,3,1,1,1,6,1,1,3,1,1,1,3,3,3,1,1,1,1,6,3,3,1,1,1,3,1,1,1, +1,1,4,3,1,1,1,1,1,1,5,5,5,5,1,3,3,1,1,1,3,1,3,1,1,1,3,1,3,3, +1,1,1,3,3,1,1,1,1,1,5,5,5,1,1,1,3,3,1,1,4,1,3,3,1,1,1,1,3,1, +3,3,1,4,3,1,1,6,1,1,1,3,3,3,3,3,1,1,1,1,4,1,1,3,1,1,1,1,3,1, +1,3,1,1,3,3,1,1,3,3,3,3,1,1,1,1,1,1,1,1,4,4,4,1,1,1,1,3,3,1, +1,3,3,1,1,1,3,3,3,1,1,1,1,1,1,1,6,1,1,3,3,3,4,4,1,1,1,3,1,1, +1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,1,3,3,3,4,1,1,3,1,1, +1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,3,3,1,1 + + + diff --git a/assets/textures/grass_tiles.png b/assets/textures/grass_tiles.png new file mode 100644 index 0000000..7cf836e --- /dev/null +++ b/assets/textures/grass_tiles.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d90edc82dc2f5dba3642c2d4ed04f7093cd9161643b2e7bc350faf5d1abd9e49 +size 2118 diff --git a/src/lib.rs b/src/lib.rs index 8696068..8d5be9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,13 @@ #[cfg(debug_assertions)] use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}; use bevy::prelude::*; +use bevy_ecs_tilemap::TilemapPlugin; +use map::MapPlugin; use player::PlayerPlugin; +mod loaders; mod loading; +mod map; mod menu; mod player; @@ -25,8 +29,14 @@ pub struct YunoPlugin; impl Plugin for YunoPlugin { fn build(&self, app: &mut App) { - app.init_state::() - .add_plugins((LoadingPlugin, MenuPlugin, PlayerPlugin)); + app.init_state::().add_plugins(( + TilemapPlugin, + loaders::tiled::TiledMapPlugin, + MapPlugin, + LoadingPlugin, + MenuPlugin, + PlayerPlugin, + )); #[cfg(debug_assertions)] { diff --git a/src/loaders/mod.rs b/src/loaders/mod.rs new file mode 100644 index 0000000..6e4cf69 --- /dev/null +++ b/src/loaders/mod.rs @@ -0,0 +1 @@ +pub(crate) mod tiled; diff --git a/src/loaders/tiled.rs b/src/loaders/tiled.rs new file mode 100644 index 0000000..c98ff2a --- /dev/null +++ b/src/loaders/tiled.rs @@ -0,0 +1,381 @@ +// How to use this: +// You should copy/paste this into your project and use it much like examples/tiles.rs uses this +// file. When you do so you will need to adjust the code based on whether you're using the +// 'atlas` feature in bevy_ecs_tilemap. The bevy_ecs_tilemap uses this as an example of how to +// use both single image tilesets and image collection tilesets. Since your project won't have +// the 'atlas' feature defined in your Cargo config, the expressions prefixed by the #[cfg(...)] +// macro will not compile in your project as-is. If your project depends on the bevy_ecs_tilemap +// 'atlas' feature then move all of the expressions prefixed by #[cfg(not(feature = "atlas"))]. +// Otherwise remove all of the expressions prefixed by #[cfg(feature = "atlas")]. +// +// Functional limitations: +// * When the 'atlas' feature is enabled tilesets using a collection of images will be skipped. +// * Only finite tile layers are loaded. Infinite tile layers and object layers will be skipped. + +use std::io::{Cursor, ErrorKind}; +use std::path::Path; +use std::sync::Arc; + +use bevy::{ + asset::{io::Reader, AssetLoader, AssetPath, AsyncReadExt}, + log, + prelude::{ + Added, Asset, AssetApp, AssetEvent, AssetId, Assets, Bundle, Commands, Component, + DespawnRecursiveExt, Entity, EventReader, GlobalTransform, Handle, Image, Plugin, Query, + Res, Transform, Update, + }, + reflect::TypePath, + utils::HashMap, +}; +use bevy_ecs_tilemap::prelude::*; + +use thiserror::Error; + +#[derive(Default)] +pub struct TiledMapPlugin; + +impl Plugin for TiledMapPlugin { + fn build(&self, app: &mut bevy::prelude::App) { + app.init_asset::() + .register_asset_loader(TiledLoader) + .add_systems(Update, process_loaded_maps); + } +} + +#[derive(TypePath, Asset)] +pub struct TiledMap { + pub map: tiled::Map, + + pub tilemap_textures: HashMap, + + // The offset into the tileset_images for each tile id within each tileset. + pub tile_image_offsets: HashMap<(usize, tiled::TileId), u32>, +} + +// Stores a list of tiled layers. +#[derive(Component, Default)] +pub struct TiledLayersStorage { + pub storage: HashMap, +} + +#[derive(Default, Bundle)] +pub struct TiledMapBundle { + pub tiled_map: Handle, + pub storage: TiledLayersStorage, + pub transform: Transform, + pub global_transform: GlobalTransform, + pub render_settings: TilemapRenderSettings, +} + +struct BytesResourceReader { + bytes: Arc<[u8]>, +} + +impl BytesResourceReader { + fn new(bytes: &[u8]) -> Self { + Self { + bytes: Arc::from(bytes), + } + } +} + +impl tiled::ResourceReader for BytesResourceReader { + type Resource = Cursor>; + type Error = std::io::Error; + + fn read_from(&mut self, _path: &Path) -> std::result::Result { + // In this case, the path is ignored because the byte data is already provided. + Ok(Cursor::new(self.bytes.clone())) + } +} + +pub struct TiledLoader; + +#[derive(Debug, Error)] +pub enum TiledAssetLoaderError { + /// An [IO](std::io) Error + #[error("Could not load Tiled file: {0}")] + Io(#[from] std::io::Error), +} + +impl AssetLoader for TiledLoader { + type Asset = TiledMap; + type Settings = (); + type Error = TiledAssetLoaderError; + + async fn load<'a>( + &'a self, + reader: &'a mut Reader<'_>, + _settings: &'a Self::Settings, + load_context: &'a mut bevy::asset::LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + + let mut loader = tiled::Loader::with_cache_and_reader( + tiled::DefaultResourceCache::new(), + BytesResourceReader::new(&bytes), + ); + let map = loader.load_tmx_map(load_context.path()).map_err(|e| { + std::io::Error::new(ErrorKind::Other, format!("Could not load TMX map: {e}")) + })?; + + let mut tilemap_textures = HashMap::default(); + let mut tile_image_offsets = HashMap::default(); + + for (tileset_index, tileset) in map.tilesets().iter().enumerate() { + let tilemap_texture = match &tileset.image { + None => { + { + let mut tile_images: Vec> = Vec::new(); + for (tile_id, tile) in tileset.tiles() { + if let Some(img) = &tile.image { + // The load context path is the TMX file itself. If the file is at the root of the + // assets/ directory structure then the tmx_dir will be empty, which is fine. + let tmx_dir = load_context + .path() + .parent() + .expect("The asset load context was empty."); + let tile_path = tmx_dir.join(&img.source); + let asset_path = AssetPath::from(tile_path); + log::info!("Loading tile image from {asset_path:?} as image ({tileset_index}, {tile_id})"); + let texture: Handle = load_context.load(asset_path.clone()); + tile_image_offsets + .insert((tileset_index, tile_id), tile_images.len() as u32); + tile_images.push(texture.clone()); + } + } + + TilemapTexture::Vector(tile_images) + } + } + Some(img) => { + // The load context path is the TMX file itself. If the file is at the root of the + // assets/ directory structure then the tmx_dir will be empty, which is fine. + let texture: Handle = load_context.load(img.source.clone()); + + TilemapTexture::Single(texture.clone()) + } + }; + + tilemap_textures.insert(tileset_index, tilemap_texture); + } + + let asset_map = TiledMap { + map, + tilemap_textures, + tile_image_offsets, + }; + + log::info!("Loaded map: {}", load_context.path().display()); + Ok(asset_map) + } + + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &["tmx"]; + EXTENSIONS + } +} + +pub fn process_loaded_maps( + mut commands: Commands, + mut map_events: EventReader>, + maps: Res>, + tile_storage_query: Query<(Entity, &TileStorage)>, + mut map_query: Query<( + &Handle, + &mut TiledLayersStorage, + &TilemapRenderSettings, + )>, + new_maps: Query<&Handle, Added>>, +) { + let mut changed_maps = Vec::>::default(); + for event in map_events.read() { + match event { + AssetEvent::Added { id } => { + log::info!("Map added!"); + changed_maps.push(*id); + } + AssetEvent::Modified { id } => { + log::info!("Map changed!"); + changed_maps.push(*id); + } + AssetEvent::Removed { id } => { + log::info!("Map removed!"); + // if mesh was modified and removed in the same update, ignore the modification + // events are ordered so future modification events are ok + changed_maps.retain(|changed_handle| changed_handle == id); + } + _ => continue, + } + } + + // If we have new map entities add them to the changed_maps list. + for new_map_handle in new_maps.iter() { + changed_maps.push(new_map_handle.id()); + } + + for changed_map in changed_maps.iter() { + for (map_handle, mut layer_storage, render_settings) in map_query.iter_mut() { + // only deal with currently changed map + if map_handle.id() != *changed_map { + continue; + } + if let Some(tiled_map) = maps.get(map_handle) { + // TODO: Create a RemoveMap component.. + for layer_entity in layer_storage.storage.values() { + if let Ok((_, layer_tile_storage)) = tile_storage_query.get(*layer_entity) { + for tile in layer_tile_storage.iter().flatten() { + commands.entity(*tile).despawn_recursive() + } + } + // commands.entity(*layer_entity).despawn_recursive(); + } + + // The TilemapBundle requires that all tile images come exclusively from a single + // tiled texture or from a Vec of independent per-tile images. Furthermore, all of + // the per-tile images must be the same size. Since Tiled allows tiles of mixed + // tilesets on each layer and allows differently-sized tile images in each tileset, + // this means we need to load each combination of tileset and layer separately. + for (tileset_index, tileset) in tiled_map.map.tilesets().iter().enumerate() { + let Some(tilemap_texture) = tiled_map.tilemap_textures.get(&tileset_index) + else { + log::warn!("Skipped creating layer with missing tilemap textures."); + continue; + }; + + let tile_size = TilemapTileSize { + x: tileset.tile_width as f32, + y: tileset.tile_height as f32, + }; + + let tile_spacing = TilemapSpacing { + x: tileset.spacing as f32, + y: tileset.spacing as f32, + }; + + // Once materials have been created/added we need to then create the layers. + for (layer_index, layer) in tiled_map.map.layers().enumerate() { + let offset_x = layer.offset_x; + let offset_y = layer.offset_y; + + let tiled::LayerType::Tiles(tile_layer) = layer.layer_type() else { + log::info!( + "Skipping layer {} because only tile layers are supported.", + layer.id() + ); + continue; + }; + + let tiled::TileLayer::Finite(layer_data) = tile_layer else { + log::info!( + "Skipping layer {} because only finite layers are supported.", + layer.id() + ); + continue; + }; + + let map_size = TilemapSize { + x: tiled_map.map.width, + y: tiled_map.map.height, + }; + + let grid_size = TilemapGridSize { + x: tiled_map.map.tile_width as f32, + y: tiled_map.map.tile_height as f32, + }; + + let map_type = match tiled_map.map.orientation { + tiled::Orientation::Hexagonal => { + TilemapType::Hexagon(HexCoordSystem::Row) + } + tiled::Orientation::Isometric => { + TilemapType::Isometric(IsoCoordSystem::Diamond) + } + tiled::Orientation::Staggered => { + TilemapType::Isometric(IsoCoordSystem::Staggered) + } + tiled::Orientation::Orthogonal => TilemapType::Square, + }; + + let mut tile_storage = TileStorage::empty(map_size); + let layer_entity = commands.spawn_empty().id(); + + for x in 0..map_size.x { + for y in 0..map_size.y { + // Transform TMX coords into bevy coords. + let mapped_y = tiled_map.map.height - 1 - y; + + let mapped_x = x as i32; + let mapped_y = mapped_y as i32; + + let layer_tile = match layer_data.get_tile(mapped_x, mapped_y) { + Some(t) => t, + None => { + continue; + } + }; + if tileset_index != layer_tile.tileset_index() { + continue; + } + let layer_tile_data = + match layer_data.get_tile_data(mapped_x, mapped_y) { + Some(d) => d, + None => { + continue; + } + }; + + let texture_index = match tilemap_texture { + TilemapTexture::Single(_) => layer_tile.id(), + TilemapTexture::Vector(_) => + *tiled_map.tile_image_offsets.get(&(tileset_index, layer_tile.id())) + .expect("The offset into to image vector should have been saved during the initial load."), + _ => unreachable!() + }; + + let tile_pos = TilePos { x, y }; + let tile_entity = commands + .spawn(TileBundle { + position: tile_pos, + tilemap_id: TilemapId(layer_entity), + texture_index: TileTextureIndex(texture_index), + flip: TileFlip { + x: layer_tile_data.flip_h, + y: layer_tile_data.flip_v, + d: layer_tile_data.flip_d, + }, + ..Default::default() + }) + .id(); + tile_storage.set(&tile_pos, tile_entity); + } + } + + commands.entity(layer_entity).insert(TilemapBundle { + grid_size, + size: map_size, + storage: tile_storage, + texture: tilemap_texture.clone(), + tile_size, + spacing: tile_spacing, + transform: get_tilemap_center_transform( + &map_size, + &grid_size, + &map_type, + layer_index as f32, + ) * Transform::from_xyz(offset_x, -offset_y, 0.0), + map_type, + render_settings: *render_settings, + ..Default::default() + }); + + layer_storage + .storage + .insert(layer_index as u32, layer_entity); + } + } + } + } + } +} diff --git a/src/loading.rs b/src/loading.rs index 0caa6f0..aa1e52a 100644 --- a/src/loading.rs +++ b/src/loading.rs @@ -4,7 +4,7 @@ use bevy_asset_loader::{ loading_state::{config::ConfigureLoadingState, LoadingState, LoadingStateAppExt}, }; -use crate::GameState; +use crate::{loaders::tiled::TiledMap, GameState}; pub struct LoadingPlugin; @@ -13,6 +13,7 @@ impl Plugin for LoadingPlugin { app.add_loading_state( LoadingState::new(GameState::Loading) .continue_to_state(GameState::Menu) + .load_collection::() .load_collection::(), ); } @@ -38,3 +39,9 @@ pub struct TextureAssets { #[asset(path = "textures/forgejo.png")] pub forgejo: Handle, } + +#[derive(AssetCollection, Resource)] +pub struct MapAssets { + #[asset(path = "maps/test.tmx")] + pub map: Handle, +} diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000..826cb29 --- /dev/null +++ b/src/map.rs @@ -0,0 +1,18 @@ +use bevy::prelude::*; + +use crate::{loaders::tiled::TiledMapBundle, loading::MapAssets, GameState}; + +pub struct MapPlugin; + +impl Plugin for MapPlugin { + fn build(&self, app: &mut App) { + app.add_systems(OnEnter(GameState::Playing), setup); + } +} + +fn setup(mut commands: Commands, maps: Res) { + commands.spawn(TiledMapBundle { + tiled_map: maps.map.clone(), + ..default() + }); +} diff --git a/src/player.rs b/src/player.rs index 6194d12..68b843e 100644 --- a/src/player.rs +++ b/src/player.rs @@ -47,7 +47,7 @@ impl AnimationConfig { fn spawn(mut commands: Commands, textures: Res) { let mut camera = Camera2dBundle::default(); - camera.projection.scale *= 0.25; + camera.projection.scale *= 0.5; commands.spawn(camera); let animation_config = AnimationConfig::new(0, 3, 10); @@ -55,6 +55,7 @@ fn spawn(mut commands: Commands, textures: Res) { commands.spawn(( SpriteBundle { texture: textures.witch.clone(), + transform: Transform::from_xyz(0.0, 0.0, 100.0), ..default() }, TextureAtlas::from(textures.witch_layout.clone()),