Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_sprite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod sprite;
#[cfg(feature = "bevy_text")]
mod text2d;
mod texture_slice;
mod tilemap;

/// The sprite prelude.
///
Expand Down Expand Up @@ -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::*;
Expand Down
79 changes: 79 additions & 0 deletions crates/bevy_sprite/src/tilemap/commands.rs
Original file line number Diff line number Diff line change
@@ -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<T: TileData>(
&mut self,
tilemap: Entity,
tile_position: IVec2,
maybe_tile: Option<T>,
);

fn remove_tile(&mut self, tilemap: Entity, tile_position: IVec2);
}

impl CommandsTilemapExt for Commands<'_, '_> {
fn set_tile<T: TileData>(
&mut self,
tilemap_id: Entity,
tile_position: IVec2,
maybe_tile: Option<T>,
) {
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::<Tilemap>() 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::<TileStorage<T>>()
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::<T>::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::<Tilemap>() 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!()
}
}
95 changes: 95 additions & 0 deletions crates/bevy_sprite/src/tilemap/mod.rs
Original file line number Diff line number Diff line change
@@ -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<IVec2, Entity>,
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 {}
82 changes: 82 additions & 0 deletions crates/bevy_sprite/src/tilemap/storage.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
pub tiles: Vec<Option<T>>,
size: UVec2,
}

impl<T> TileStorage<T> {
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<T>) -> Option<T> {
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<T> {
self.set(tile_position, None)
}

// pub fn iter(&self) -> impl Iterator<Item = Option<Entity>> {
// self.tiles.iter().cloned()
// }

// pub fn iter_sub_rect(&self, rect: URect) -> impl Iterator<Item = Option<Entity>> {
// 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<Item = Option<Entity>> {
// 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
}
}
Loading