diff --git a/Cargo.toml b/Cargo.toml index 9e1af044b7401..b6fff32649279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -995,6 +995,17 @@ description = "Renders a tilemap chunk" category = "2D Rendering" wasm = true +[[example]] +name = "tilemap" +path = "examples/2d/tilemap.rs" +doc-scrape-examples = true + +[package.metadata.example.tilemap] +name = "Tilemap" +description = "Renders a tilemap" +category = "2D Rendering" +wasm = true + [[example]] name = "transparency_2d" path = "examples/2d/transparency_2d.rs" diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index f980c56fda8c6..3eb1289cdfdd8 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -23,6 +23,7 @@ bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.18.0-dev" } bevy_math = { path = "../bevy_math", version = "0.18.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.18.0-dev", optional = true } +bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev" } bevy_window = { path = "../bevy_window", version = "0.18.0-dev", optional = true } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index c14aab0c623a1..3cd123497edab 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -16,6 +16,7 @@ mod sprite; #[cfg(feature = "bevy_text")] mod text2d; mod texture_slice; +mod tilemap; /// The sprite prelude. /// @@ -50,6 +51,7 @@ pub use sprite::*; #[cfg(feature = "bevy_text")] pub use text2d::*; pub use texture_slice::*; +pub use tilemap::*; use bevy_app::prelude::*; use bevy_ecs::prelude::*; diff --git a/crates/bevy_sprite/src/tilemap/commands.rs b/crates/bevy_sprite/src/tilemap/commands.rs new file mode 100644 index 0000000000000..764f794f69dfb --- /dev/null +++ b/crates/bevy_sprite/src/tilemap/commands.rs @@ -0,0 +1,79 @@ +use crate::tilemap::{TileData, TileStorage, Tilemap}; +use bevy_ecs::{entity::Entity, hierarchy::ChildOf, system::Commands, world::World}; +use bevy_math::{IVec2, UVec2, Vec2, Vec3}; +use bevy_transform::components::Transform; + +pub trait CommandsTilemapExt { + fn set_tile( + &mut self, + tilemap: Entity, + tile_position: IVec2, + maybe_tile: Option, + ); + + fn remove_tile(&mut self, tilemap: Entity, tile_position: IVec2); +} + +impl CommandsTilemapExt for Commands<'_, '_> { + fn set_tile( + &mut self, + tilemap_id: Entity, + tile_position: IVec2, + maybe_tile: Option, + ) { + self.queue(move |w: &mut World| { + let Ok(mut tilemap_entity) = w.get_entity_mut(tilemap_id) else { + tracing::warn!("Could not find Tilemap Entity {:?}", tilemap_id); + return; + }; + + let Some(tilemap) = tilemap_entity.get::() else { + tracing::warn!("Could not find Tilemap on Entity {:?}", tilemap_id); + return; + }; + + let chunk_position = tilemap.tile_chunk_position(tile_position); + let tile_position = tilemap.tile_chunk_local_position(tile_position); + + if let Some(tile_storage_id) = tilemap.chunks.get(&chunk_position).cloned() { + tilemap_entity.world_scope(move |w| { + let Ok(mut tilestorage_entity) = w.get_entity_mut(tile_storage_id) else { + tracing::warn!("Could not find TileStorage Entity {:?}", tile_storage_id); + return; + }; + + let Some(mut tile_storage) = tilestorage_entity.get_mut::>() + else { + tracing::warn!( + "Could not find TileStorage on Entity {:?}", + tile_storage_id + ); + return; + }; + + tile_storage.set(tile_position, maybe_tile); + }); + } else { + let chunk_size = tilemap.chunk_size; + let tile_size = tilemap.tile_display_size; + let tile_storage_id = tilemap_entity.world_scope(move |w| { + let mut tile_storage = TileStorage::::new(chunk_size); + tile_storage.set(tile_position, maybe_tile); + let translation = Vec2::new(chunk_size.x as f32, chunk_size.y as f32) * Vec2::new(tile_size.x as f32, tile_size.y as f32) * Vec2::new(chunk_position.x as f32, chunk_position.y as f32); + let translation = Vec3::new(translation.x, translation.y, 0.0); + let transform = Transform::from_translation(translation); + w.spawn((ChildOf(tilemap_id), tile_storage, transform)).id() + }); + let Some(mut tilemap) = tilemap_entity.get_mut::() else { + tracing::warn!("Could not find Tilemap on Entity {:?}", tilemap_id); + return; + }; + tilemap.chunks.insert(chunk_position, tile_storage_id); + }; + }); + } + + fn remove_tile(&mut self, tilemap: Entity, tile_position: IVec2) { + todo!() + } +} diff --git a/crates/bevy_sprite/src/tilemap/mod.rs b/crates/bevy_sprite/src/tilemap/mod.rs new file mode 100644 index 0000000000000..6c751ddf0fb62 --- /dev/null +++ b/crates/bevy_sprite/src/tilemap/mod.rs @@ -0,0 +1,95 @@ +use bevy_app::{App, Plugin}; +use bevy_ecs::{component::Component, entity::Entity, name::Name, reflect::ReflectComponent}; +use bevy_math::{IVec2, UVec2}; +use bevy_platform::collections::HashMap; +use bevy_reflect::Reflect; +use bevy_transform::components::Transform; + +mod commands; +mod storage; + +pub use commands::*; +pub use storage::*; + +/// Plugin that handles the initialization and updating of tilemap chunks. +/// Adds systems for processing newly added tilemap chunks. +pub struct TilemapPlugin; + +impl Plugin for TilemapPlugin { + fn build(&self, app: &mut App) { + //app.add_plugins(TilemapChunkPlugin).add_plugins(TilePlugin); + } +} + +#[derive(Component, Clone, Debug, Reflect)] +#[reflect(Component, Clone, Debug)] +#[require(Name::new("Tilemap"), Transform)] +pub struct Tilemap { + pub chunks: HashMap, + pub chunk_size: UVec2, + pub tile_display_size: UVec2, +} + +impl Tilemap { + pub fn new(chunk_size: UVec2, tile_display_size: UVec2) -> Self { + Self { + chunks: HashMap::new(), + chunk_size, + tile_display_size, + } + } + + /// Get the coordinates of the chunk a given tile is in. + // TODO: NAME THIS BETTER + pub fn tile_chunk_position(&self, tile_position: IVec2) -> IVec2 { + tile_position.div_euclid( + self.chunk_size + .try_into() + .expect("Could not convert chunk size into IVec2"), + ) + } + + /// Get the coordinates with in a chunk from a tiles global coordinates. + // TODO: NAME THIS BETTER + pub fn tile_chunk_local_position(&self, tile_position: IVec2) -> UVec2 { + let chunk_size = self + .chunk_size + .try_into() + .expect("Could not convert chunk size into IVec2"); + let mut res = tile_position.rem_euclid(chunk_size); + if res.x < 0 { + res.x = chunk_size.x - res.x.abs() - 1; + } + if res.y < 0 { + res.y = chunk_size.y - res.y.abs() - 1; + } + res.try_into() + .expect("Could not convert chunk local position into UVec2") + } + + pub fn calculate_tile_transform(&self, tile_position: IVec2) -> Transform { + Transform::from_xyz( + // tile position + tile_position.x as f32 + // times display size for a tile + * self.tile_display_size.x as f32 + // plus 1/2 the tile_display_size to correct the center + + self.tile_display_size.x as f32 / 2. + // minus 1/2 the tilechunk size, in terms of the tile_display_size, + // to place the 0 at left of tilemapchunk + - self.tile_display_size.x as f32 * self.chunk_size.x as f32 / 2., + // tile position + tile_position.y as f32 + // times display size for a tile + * self.tile_display_size.y as f32 + // minus 1/2 the tile_display_size to correct the center + + self.tile_display_size.y as f32 / 2. + // plus 1/2 the tilechunk size, in terms of the tile_display_size, + // to place the 0 at top of tilemapchunk + - self.tile_display_size.y as f32 * self.chunk_size.y as f32 / 2., + 0., + ) + } +} + +pub trait TileData: Send + Sync + 'static {} diff --git a/crates/bevy_sprite/src/tilemap/storage.rs b/crates/bevy_sprite/src/tilemap/storage.rs new file mode 100644 index 0000000000000..910200ad0e444 --- /dev/null +++ b/crates/bevy_sprite/src/tilemap/storage.rs @@ -0,0 +1,82 @@ +use std::ops::Deref; + +use bevy_ecs::{component::Component, entity::Entity, name::Name, reflect::ReflectComponent}; +use bevy_math::{URect, UVec2}; +use bevy_reflect::Reflect; +use bevy_transform::components::Transform; + +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component)] +#[require(Name::new("TileStorage"), Transform)] +pub struct TileStorage { + pub tiles: Vec>, + size: UVec2, +} + +impl TileStorage { + pub fn new(size: UVec2) -> Self { + let mut tiles = Vec::new(); + tiles.resize_with(size.element_product() as usize, Default::default); + Self { tiles, size } + } + + pub fn index(&self, tile_coord: UVec2) -> usize { + (tile_coord.y * self.size.x + tile_coord.x) as usize + } + + pub fn get(&self, tile_coord: UVec2) -> Option<&T> { + let index = self.index(tile_coord); + self.tiles.get(index).map(Option::as_ref).flatten() + } + + pub fn get_mut(&mut self, tile_coord: UVec2) -> Option<&mut T> { + let index = self.index(tile_coord); + self.tiles.get_mut(index).map(Option::as_mut).flatten() + } + + pub fn set(&mut self, tile_position: UVec2, maybe_tile: Option) -> Option { + let index = self.index(tile_position); + let tile = self.tiles.get_mut(index)?; + core::mem::replace(tile, maybe_tile) + } + + pub fn remove(&mut self, tile_position: UVec2) -> Option { + self.set(tile_position, None) + } + + // pub fn iter(&self) -> impl Iterator> { + // self.tiles.iter().cloned() + // } + + // pub fn iter_sub_rect(&self, rect: URect) -> impl Iterator> { + // let URect { min, max } = rect; + + // (min.y..max.y).flat_map(move |y| { + // (min.x..max.x).map(move |x| { + // if x >= self.size.x || y >= self.size.y { + // return None; + // } + + // let index = (y * self.size.x + x) as usize; + // self.tiles.get(index).cloned().flatten() + // }) + // }) + // } + + // pub fn iter_chunk_tiles( + // &self, + // chunk_position: UVec2, + // chunk_size: UVec2, + // ) -> impl Iterator> { + // let chunk_rect = URect::from_corners( + // chunk_position * chunk_size, + // (chunk_position + UVec2::splat(1)) * chunk_size, + // ); + + // self.iter_sub_rect(chunk_rect) + // } + + pub fn size(&self) -> UVec2 { + self.size + } +} diff --git a/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs b/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs index 5139ab704b5a6..965601aab1c1c 100644 --- a/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs +++ b/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs @@ -1,28 +1,23 @@ -use core::ops::Neg; +use std::any::type_name; use crate::{AlphaMode2d, MeshMaterial2d}; use bevy_app::{App, Plugin, Update}; use bevy_asset::{Assets, Handle}; +use bevy_camera::visibility::Visibility; use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - component::Component, - entity::Entity, - lifecycle::HookContext, - query::Changed, - reflect::{ReflectComponent, ReflectResource}, - resource::Resource, - system::{Query, ResMut}, - world::DeferredWorld, + component::Component, entity::Entity, hierarchy::ChildOf, lifecycle::{HookContext, Insert}, observer::On, query::Changed, reflect::{ReflectComponent, ReflectResource}, resource::Resource, system::{Commands, Query, ResMut}, world::DeferredWorld }; use bevy_image::Image; use bevy_math::{primitives::Rectangle, UVec2}; use bevy_mesh::{Mesh, Mesh2d}; use bevy_platform::collections::HashMap; use bevy_reflect::{prelude::*, Reflect}; +use bevy_sprite::{TileData, TileStorage, Tilemap}; use bevy_transform::components::Transform; use bevy_utils::default; -use tracing::warn; +use tracing::{trace, warn}; mod tilemap_chunk_material; @@ -36,6 +31,7 @@ impl Plugin for TilemapChunkPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems(Update, update_tilemap_chunk_indices); + app.world_mut().register_component_hooks::>().on_insert(on_insert_chunk_tile_render_data); } } @@ -49,7 +45,7 @@ pub struct TilemapChunkMeshCache(HashMap>); #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component, Clone, Debug, Default)] #[component(immutable, on_insert = on_insert_tilemap_chunk)] -pub struct TilemapChunk { +pub struct TilemapChunkRenderData { /// The size of the chunk in tiles. pub chunk_size: UVec2, /// The size to use for each tile, not to be confused with the size of a tile in the tileset image. @@ -61,7 +57,20 @@ pub struct TilemapChunk { pub alpha_mode: AlphaMode2d, } -impl TilemapChunk { +/// A component representing a chunk of a tilemap. +/// Each chunk is a rectangular section of tiles that is rendered as a single mesh. +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component, Clone, Debug, Default)] +#[require(Transform, Visibility)] +#[component(immutable)] +pub struct TilemapRenderData { + /// Handle to the tileset image containing all tile textures. + pub tileset: Handle, + /// The alpha mode to use for the tilemap chunk. + pub alpha_mode: AlphaMode2d, +} + +impl TilemapChunkRenderData { pub fn calculate_tile_transform(&self, position: UVec2) -> Transform { Transform::from_xyz( // tile position @@ -76,21 +85,60 @@ impl TilemapChunk { // tile position position.y as f32 // times display size for a tile - * (self.tile_display_size.y as f32).neg() + * self.tile_display_size.y as f32 // minus 1/2 the tile_display_size to correct the center - - self.tile_display_size.y as f32 / 2. + + self.tile_display_size.y as f32 / 2. // plus 1/2 the tilechunk size, in terms of the tile_display_size, - // to place the 0 at top of tilemapchunk - + self.tile_display_size.y as f32 * self.chunk_size.y as f32 / 2., + // to place the 0 at bottom of tilemapchunk + - self.tile_display_size.y as f32 * self.chunk_size.y as f32 / 2., 0., ) } } +fn on_insert_chunk_tile_render_data(mut world: DeferredWorld, HookContext { entity, .. }: HookContext){ + let Ok(chunk) = world.get_entity(entity) else { + warn!("Chunk {} not found", entity); + return; + }; + if chunk.contains::() { + trace!("Chunk {} already contains TilemapChunkRenderData", entity); + return; + } + let Some(child_of) = chunk.get::() else { + warn!("Chunk {} is not a child of an entity", entity); + return; + }; + let Ok(tilemap) = world.get_entity(child_of.parent()) else { + warn!("Could not find chunk {}'s parent {}", entity, child_of.parent()); + return; + }; + let Some(tilemap_render_data) = tilemap.get::() else { + warn!("Could not find TilemapRenderData on chunk {}'s parent {}", entity, child_of.parent()); + return; + }; + let Some(tilemap) = tilemap.get::() else { + warn!("Could not find Tilemap on chunk {}'s parent {}", entity, child_of.parent()); + return; + }; + + let data = TilemapChunkRenderData { + chunk_size: tilemap.chunk_size, + tile_display_size: tilemap.tile_display_size, + tileset: tilemap_render_data.tileset.clone(), + alpha_mode: tilemap_render_data.alpha_mode, + }; + + world + .commands() + .entity(entity) + .insert(data); +} + /// Data for a single tile in the tilemap chunk. #[derive(Clone, Copy, Debug, Reflect)] #[reflect(Clone, Debug, Default)] -pub struct TileData { +pub struct TileRenderData { /// The index of the tile in the corresponding tileset array texture. pub tileset_index: u16, /// The color tint of the tile. White leaves the sampled texture color unchanged. @@ -99,7 +147,9 @@ pub struct TileData { pub visible: bool, } -impl TileData { +impl TileData for TileRenderData {} + +impl TileRenderData { /// Creates a new `TileData` with the given tileset index and default values. pub fn from_tileset_index(tileset_index: u16) -> Self { Self { @@ -109,7 +159,7 @@ impl TileData { } } -impl Default for TileData { +impl Default for TileRenderData { fn default() -> Self { Self { tileset_index: 0, @@ -119,15 +169,9 @@ impl Default for TileData { } } -/// Component storing the data of tiles within a chunk. -/// Each index corresponds to a specific tile in the tileset. `None` indicates an empty tile. -#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)] -#[reflect(Component, Clone, Debug)] -pub struct TilemapChunkTileData(pub Vec>); - fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { - let Some(tilemap_chunk) = world.get::(entity) else { - warn!("TilemapChunk not found for tilemap chunk {}", entity); + let Some(tilemap_chunk) = world.get::(entity) else { + warn!("{} not found for tilemap chunk {}", type_name::(), entity); return; }; @@ -135,25 +179,25 @@ fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }: let alpha_mode = tilemap_chunk.alpha_mode; let tileset = tilemap_chunk.tileset.clone(); - let Some(tile_data) = world.get::(entity) else { - warn!("TilemapChunkIndices not found for tilemap chunk {}", entity); + let Some(tile_data) = world.get::>(entity) else { + warn!("{} not found for tilemap chunk {}", type_name::>(), entity); return; }; let expected_tile_data_length = chunk_size.element_product() as usize; - if tile_data.len() != expected_tile_data_length { + if tile_data.tiles.len() != expected_tile_data_length { warn!( "Invalid tile data length for tilemap chunk {} of size {}. Expected {}, got {}", entity, chunk_size, expected_tile_data_length, - tile_data.len(), + tile_data.tiles.len(), ); return; } let packed_tile_data: Vec = - tile_data.0.iter().map(|&tile| tile.into()).collect(); + tile_data.tiles.iter().map(|&tile| tile.into()).collect(); let tile_data_image = make_chunk_tile_data_image(&chunk_size, &packed_tile_data); @@ -188,30 +232,30 @@ fn update_tilemap_chunk_indices( query: Query< ( Entity, - &TilemapChunk, - &TilemapChunkTileData, + &TilemapChunkRenderData, + &TileStorage, &MeshMaterial2d, ), - Changed, + Changed>, >, mut materials: ResMut>, mut images: ResMut>, ) { - for (chunk_entity, TilemapChunk { chunk_size, .. }, tile_data, material) in query { + for (chunk_entity, TilemapChunkRenderData { chunk_size, .. }, tile_data, material) in query { let expected_tile_data_length = chunk_size.element_product() as usize; - if tile_data.len() != expected_tile_data_length { + if tile_data.tiles.len() != expected_tile_data_length { warn!( "Invalid TilemapChunkTileData length for tilemap chunk {} of size {}. Expected {}, got {}", chunk_entity, chunk_size, - tile_data.len(), + tile_data.tiles.len(), expected_tile_data_length ); continue; } let packed_tile_data: Vec = - tile_data.0.iter().map(|&tile| tile.into()).collect(); + tile_data.tiles.iter().map(|&tile| tile.into()).collect(); // Getting the material mutably to trigger change detection let Some(material) = materials.get_mut(material.id()) else { @@ -239,15 +283,3 @@ fn update_tilemap_chunk_indices( data.extend_from_slice(bytemuck::cast_slice(&packed_tile_data)); } } - -impl TilemapChunkTileData { - pub fn tile_data_from_tile_pos( - &self, - tilemap_size: UVec2, - position: UVec2, - ) -> Option<&TileData> { - self.0 - .get(tilemap_size.x as usize * position.y as usize + position.x as usize) - .and_then(|opt| opt.as_ref()) - } -} diff --git a/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs b/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs index 53039c3458de3..5372445cc35c3 100644 --- a/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs +++ b/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs @@ -1,4 +1,4 @@ -use crate::{AlphaMode2d, Material2d, Material2dPlugin, TileData}; +use crate::{AlphaMode2d, Material2d, Material2dPlugin, TileRenderData}; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, embedded_path, Asset, AssetPath, Handle, RenderAssetUsages}; use bevy_color::ColorToPacked; @@ -68,13 +68,13 @@ impl PackedTileData { } } -impl From for PackedTileData { +impl From for PackedTileData { fn from( - TileData { + TileRenderData { tileset_index, color, visible, - }: TileData, + }: TileRenderData, ) -> Self { Self { tileset_index, @@ -84,8 +84,8 @@ impl From for PackedTileData { } } -impl From> for PackedTileData { - fn from(maybe_tile_data: Option) -> Self { +impl From> for PackedTileData { + fn from(maybe_tile_data: Option) -> Self { maybe_tile_data .map(Into::into) .unwrap_or(PackedTileData::empty()) diff --git a/examples/2d/tilemap.rs b/examples/2d/tilemap.rs new file mode 100644 index 0000000000000..534309235540c --- /dev/null +++ b/examples/2d/tilemap.rs @@ -0,0 +1,68 @@ +//! Shows a tilemap chunk rendered with a single draw call. + +use bevy::{ + color::palettes::tailwind::RED_400, image::{ImageArrayLayout, ImageLoaderSettings}, prelude::*, sprite::{TileStorage, Tilemap, CommandsTilemapExt}, sprite_render::{TileRenderData, TilemapChunkRenderData, TilemapRenderData} +}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) + .add_systems(Startup, setup) + .add_systems(Update, update_tilemap) + .run(); +} + +#[derive(Component, Deref, DerefMut)] +struct UpdateTimer(Timer); + +#[derive(Resource, Deref, DerefMut)] +struct SeededRng(ChaCha8Rng); + +fn setup(mut commands: Commands, assets: Res) { + // We're seeding the PRNG here to make this example deterministic for testing purposes. + // This isn't strictly required in practical use unless you need your app to be deterministic. + let mut rng = ChaCha8Rng::seed_from_u64(42); + + let chunk_size = UVec2::splat(16); + let tile_display_size = UVec2::splat(8); + + commands.spawn(( + Transform::default(), + Visibility::default(), + Tilemap::new(chunk_size, tile_display_size), + TilemapRenderData { + tileset: assets.load_with_settings( + "textures/array_texture.png", + |settings: &mut ImageLoaderSettings| { + // The tileset texture is expected to be an array of tile textures, so we tell the + // `ImageLoader` that our texture is composed of 4 stacked tile images. + settings.array_layout = Some(ImageArrayLayout::RowCount { rows: 4 }); + }, + ), + ..default() + } + )); + + commands.spawn(Camera2d); + + commands.insert_resource(SeededRng(rng)); +} + +#[derive(Component)] +struct MovePlayer; + +fn update_tilemap( + map: Single>, + mut commands: Commands, + mut rng: ResMut, +) { + let map = *map; + for i in 0..rng.random_range(1..=20) { + let x = rng.random_range(-64..=64); + let y = rng.random_range(-64..=64); + + commands.set_tile(map, IVec2::new(x, y), Some(TileRenderData { tileset_index: rng.random_range(0..4), ..Default::default()})); + } +} \ No newline at end of file diff --git a/examples/2d/tilemap_chunk.rs b/examples/2d/tilemap_chunk.rs index 4bc1855841c04..4a24c3212af79 100644 --- a/examples/2d/tilemap_chunk.rs +++ b/examples/2d/tilemap_chunk.rs @@ -4,7 +4,8 @@ use bevy::{ color::palettes::tailwind::RED_400, image::{ImageArrayLayout, ImageLoaderSettings}, prelude::*, - sprite_render::{TileData, TilemapChunk, TilemapChunkTileData}, + sprite_render::{TilemapChunkRenderData, TileRenderData}, + sprite::TileStorage }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; @@ -30,19 +31,21 @@ fn setup(mut commands: Commands, assets: Res) { let chunk_size = UVec2::splat(64); let tile_display_size = UVec2::splat(8); - let tile_data: Vec> = (0..chunk_size.element_product()) + let tile_data: Vec> = (0..chunk_size.element_product()) .map(|_| rng.random_range(0..5)) .map(|i| { if i == 0 { None } else { - Some(TileData::from_tileset_index(i - 1)) + Some(TileRenderData::from_tileset_index(i - 1)) } }) .collect(); + let mut storage = TileStorage::::new(chunk_size); + storage.tiles = tile_data; commands.spawn(( - TilemapChunk { + TilemapChunkRenderData { chunk_size, tile_display_size, tileset: assets.load_with_settings( @@ -55,7 +58,7 @@ fn setup(mut commands: Commands, assets: Res) { ), ..default() }, - TilemapChunkTileData(tile_data), + storage, UpdateTimer(Timer::from_seconds(0.1, TimerMode::Repeating)), )); @@ -71,7 +74,7 @@ fn spawn_fake_player( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - chunk: Single<&TilemapChunk>, + chunk: Single<&TilemapChunkRenderData>, ) { let mut transform = chunk.calculate_tile_transform(UVec2::new(0, 0)); transform.translation.z = 1.; @@ -97,7 +100,7 @@ fn spawn_fake_player( fn move_player( mut player: Single<&mut Transform, With>, time: Res