bones_framework/render/tilemap.rs
1//! Tile map rendering components.
2
3use crate::prelude::*;
4
5/// Used for mapping entities to a sized grid. This is used in the [`TileLayer`]
6/// component for mapping entities with [`Tile`] components to it.
7#[derive(Clone, Debug, HasSchema, Default)]
8pub struct EntityGridMap {
9 /// The vector of tile slots in this layer.
10 pub tiles: Vec<Option<Entity>>,
11 /// The size of the map in tiles (or columns and rows).
12 pub grid_size: UVec2,
13}
14
15/// A tilemap layer component.
16#[derive(Clone, Debug, HasSchema, Default, Deref, DerefMut)]
17pub struct TileLayer {
18 #[deref]
19 /// The grid map for this [`TileLayer`]'s [`Tile`]s.
20 pub map: EntityGridMap,
21 /// The texture atlas to use for the layer
22 pub atlas: Handle<Atlas>,
23}
24impl TileLayer {
25 /// Create a new [`TileLayer`].
26 pub fn new(grid_size: UVec2, atlas: Handle<Atlas>) -> Self {
27 Self {
28 map: EntityGridMap::new(grid_size),
29 atlas,
30 }
31 }
32}
33
34/// A tilemap tile component.
35#[derive(Clone, Debug, HasSchema, Default)]
36#[repr(C)]
37pub struct Tile {
38 /// The tile index in the tilemap texture.
39 pub idx: u32,
40 /// Whether or not to flip the tile horizontally.
41 pub flip_x: bool,
42 /// Whether or not to flip tile vertically.
43 pub flip_y: bool,
44}
45
46impl EntityGridMap {
47 /// Create a new tile layer
48 pub fn new(grid_size: UVec2) -> Self {
49 let mut out = Self {
50 tiles: Vec::new(),
51 grid_size,
52 };
53 out.ensure_space();
54 out
55 }
56
57 /// Get's the entity at the given position in the grid if set, indexed with
58 /// the bottom-left of the grid being (0, 0).
59 ///
60 /// Returns `None` is no `Entity` assigned to that position.
61 ///
62 /// Panics if the position is out of the grid bounds.
63 pub fn get(&self, pos: UVec2) -> Option<Entity> {
64 let idx = self.index(pos);
65 self.tiles.get(idx as usize).cloned().flatten()
66 }
67
68 /// Get's the entity at the given position in the grid if set, indexed with
69 /// the bottom-left of the grid being (0, 0).
70 ///
71 /// Returns `None` if the position is out of the grid bounds or there is no
72 /// `Entity` assigned to that position.
73 pub fn try_get(&self, pos: UVec2) -> Option<Entity> {
74 self.try_index(pos).and_then(|idx| self.tiles[idx as usize])
75 }
76
77 /// Get the position on the grid with the given entity index.
78 ///
79 /// Panics if the index is out of the grid bounds.
80 pub fn pos(&self, idx: u32) -> UVec2 {
81 if idx as usize >= self.tiles.len() {
82 panic!("index out of grid bounds")
83 }
84 let y = idx / self.grid_size.x;
85 let x = idx - (y * self.grid_size.x);
86
87 UVec2::new(x, y)
88 }
89 /// Try to get the position on the grid with the given entity index.
90 ///
91 /// Returns `None` if the index is out of the grid bounds.
92 pub fn try_pos(&self, idx: u32) -> Option<UVec2> {
93 if idx as usize >= self.tiles.len() {
94 None
95 } else {
96 let y = idx / self.grid_size.x;
97 let x = idx - (y * self.grid_size.x);
98
99 UVec2::new(x, y).into()
100 }
101 }
102
103 /// Get the index of the tile at the given position.
104 #[inline]
105 #[track_caller]
106 pub fn index(&self, pos: UVec2) -> u32 {
107 if pos.x + 1 > self.grid_size.x || pos.y + 1 > self.grid_size.y {
108 panic!("grid postion out of bounds")
109 }
110 self.grid_size.x * pos.y + pos.x
111 }
112 /// Try to get the index of the tile at the given position.
113 #[inline]
114 pub fn try_index(&self, pos: UVec2) -> Option<u32> {
115 if pos.x + 1 > self.grid_size.x || pos.y + 1 > self.grid_size.y {
116 None
117 } else {
118 Some(self.grid_size.x * pos.y + pos.x)
119 }
120 }
121
122 /// Set the tile at the given position, to a certain entity.
123 pub fn set(&mut self, pos: UVec2, entity: Option<Entity>) {
124 self.ensure_space();
125
126 let idx = self.index(pos);
127 self.tiles[idx as usize] = entity;
128 }
129
130 /// Makes sure the tiles vector has space for all of our tiles.
131 fn ensure_space(&mut self) {
132 let tile_count = (self.grid_size.x * self.grid_size.y) as usize;
133
134 if unlikely(self.tiles.len() < tile_count) {
135 self.tiles
136 .extend((0..(tile_count - self.tiles.len())).map(|_| None));
137 }
138 }
139
140 /// Set a tile position, resizing the map to accommodate the new tile if the map grid is not
141 /// already large enough.
142 pub fn set_with_resize(&mut self, pos: UVec2, entity: Option<Entity>) {
143 // Calculate whatever size is larger, the grid size, or the size needed to store the tile
144 // position.
145 let required_grid_size = self.grid_size.max(pos + UVec2::ONE);
146
147 // If the larger size is not the grid size we have currently
148 if required_grid_size != self.grid_size {
149 // Resize the grid to fit the position
150 self.resize_grid(required_grid_size);
151 }
152
153 // Set the tile position
154 self.set(pos, entity)
155 }
156
157 /// Copy all the tiles from another tileset into this one.
158 ///
159 /// This can be useful for resizing a map, for example.
160 ///
161 /// The tiles from the other layer are copied to the corresponding positions in this layer, if
162 /// they fit into this layer's size.
163 pub fn copy_from(&mut self, other: &EntityGridMap) {
164 // Get the smallest size so that we know we won't go out of bounds while iterating over the
165 // tiles.
166 let visit_size = other.grid_size.min(self.grid_size);
167
168 // Iterate over every position
169 for x in 0..visit_size.x {
170 for y in 0..visit_size.y {
171 let pos = uvec2(x, y);
172 // Set the position in this layer from the position in the other layer
173 self.set(pos, other.get(pos))
174 }
175 }
176 }
177
178 /// Resize the layer, setting a new grid size.
179 ///
180 /// If shrinking the layer, tiles that are out-of-bounds will be removed.
181 pub fn resize_grid(&mut self, grid_size: UVec2) {
182 // Create a new layer with the desired grid size
183 let mut new_layer = EntityGridMap::new(grid_size);
184
185 // Copy the tiles from this layer into the new one
186 new_layer.copy_from(self);
187
188 // Swap out the new layer and the current one
189 std::mem::swap(self, &mut new_layer);
190 }
191}
192
193// TODO: add tests for EntityGridMap