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