bevy_pixel_map/
tilemap.rs

1#![allow(clippy::type_complexity)]
2use std::collections::{HashMap, VecDeque};
3
4use bevy::{prelude::*, transform::TransformBundle};
5
6use crate::{
7    chunk::{Chunk, ChunkBundle},
8    tile::{Tile, TileBundle},
9    util::{chunk_from_location, tile_from_location},
10};
11
12#[derive(Clone, Debug)]
13pub enum TileEvent {
14    MakeChunk(IVec2),
15    SetTile {
16        loc: IVec2,
17        entity: Entity,
18    },
19    DeleteTile {
20        loc: IVec2,
21        mark: bool,
22    },
23    SetPixel {
24        loc: IVec2,
25        pixel: IVec2,
26        color: Color,
27    },
28}
29
30#[derive(Bundle, Default)]
31pub struct TilemapBundle {
32    tilemap: Tilemap,
33    transform: TransformBundle,
34    visibility: VisibilityBundle,
35}
36
37#[derive(Component, Default)]
38pub struct Tilemap {
39    chunks: HashMap<IVec2, Entity>,
40    pub(crate) tasks: VecDeque<TileEvent>,
41}
42
43impl Tilemap {
44    pub fn new() -> Self {
45        Self {
46            chunks: HashMap::new(),
47            tasks: VecDeque::new(),
48        }
49    }
50
51    pub fn require_chunk(&mut self, loc: IVec2) {
52        let chunk = chunk_from_location(loc);
53
54        if !self.chunks.contains_key(&IVec2::new(chunk.x, chunk.y)) {
55            self.tasks
56                .push_front(TileEvent::MakeChunk(IVec2::new(chunk.x, chunk.y)));
57        }
58    }
59
60    pub fn set_tile(
61        &mut self,
62        commands: &mut Commands,
63        loc: IVec2,
64        tile: Tile,
65        additional_components: impl Bundle,
66    ) -> Entity {
67        self.require_chunk(loc);
68
69        let entity = commands
70            .spawn((TileBundle::new(tile, loc), additional_components))
71            .id();
72
73        self.tasks.push_back(TileEvent::SetTile { loc, entity });
74
75        entity
76    }
77
78    pub fn try_set_tile(
79        &mut self,
80        commands: &mut Commands,
81        chunks: &Query<&Chunk>,
82        loc: IVec2,
83        tile: Tile,
84        additional_components: impl Bundle,
85    ) -> Option<Entity> {
86        if self.get_tile(loc, chunks).is_some() {
87            return None;
88        }
89
90        Some(self.set_tile(commands, loc, tile, additional_components))
91    }
92
93    pub fn set_pixel(&mut self, loc: IVec2, pixel: IVec2, color: Color) {
94        if !self.has_chunk(loc) {
95            return;
96        }
97
98        self.tasks
99            .push_back(TileEvent::SetPixel { loc, pixel, color })
100    }
101
102    pub fn delete_tile(&mut self, loc: IVec2) {
103        if !self.has_chunk(loc) {
104            return;
105        }
106
107        self.tasks
108            .push_back(TileEvent::DeleteTile { loc, mark: true });
109
110        self.require_chunk(loc)
111    }
112
113    pub fn delete_without_marker(&mut self, loc: IVec2) {
114        if !self.has_chunk(loc) {
115            return;
116        }
117
118        self.tasks
119            .push_back(TileEvent::DeleteTile { loc, mark: false });
120
121        self.require_chunk(loc)
122    }
123
124    pub fn get_tile(&mut self, loc: IVec2, chunks: &Query<&Chunk>) -> Option<Entity> {
125        if !self.has_chunk(loc) {
126            return None;
127        }
128
129        if let Some(entity) = self.get_chunk(loc) {
130            if let Ok(chunk) = chunks.get(entity) {
131                return chunk.get_tile(tile_from_location(loc));
132            }
133        }
134
135        None
136    }
137
138    pub fn get_chunk(&self, loc: IVec2) -> Option<Entity> {
139        let loc = chunk_from_location(loc);
140        self.chunks.get(&loc).copied()
141    }
142
143    pub fn has_chunk(&self, loc: IVec2) -> bool {
144        let loc = chunk_from_location(loc);
145        self.chunks.contains_key(&loc)
146    }
147}
148
149pub fn tilemap_event_system(
150    mut commands: Commands,
151    mut tilemaps: Query<(Entity, &mut Tilemap)>,
152    mut chunks: Query<(Entity, &mut Chunk)>,
153    mut tiles: Query<(Entity, &mut Tile)>,
154    mut images: ResMut<Assets<Image>>,
155) {
156    for (tilemap_entity, mut tilemap) in &mut tilemaps {
157        let mut remaining_tasks = VecDeque::new();
158        while let Some(event) = tilemap.tasks.pop_front() {
159            match event {
160                TileEvent::MakeChunk(loc) => {
161                    if !tilemap.has_chunk(loc) {
162                        let entity = commands
163                            .spawn(ChunkBundle::new(
164                                chunk_from_location(loc),
165                                Chunk::new(&mut images),
166                            ))
167                            .id();
168
169                        commands.entity(entity).set_parent(tilemap_entity);
170
171                        tilemap.chunks.insert(chunk_from_location(loc), entity);
172                    }
173                }
174                TileEvent::SetTile { loc, entity } => {
175                    let chunk_loc = chunk_from_location(loc);
176
177                    if !tilemap.has_chunk(loc) {
178                        remaining_tasks.push_front(TileEvent::MakeChunk(loc));
179                        remaining_tasks.push_back(TileEvent::SetTile { loc, entity });
180                    } else if let Ok((chunk_entity, mut chunk)) =
181                        chunks.get_mut(*tilemap.chunks.get(&chunk_loc).expect("chunk should exist"))
182                    {
183                        commands.entity(entity).set_parent(chunk_entity);
184                        chunk.set_tile_entity(tile_from_location(loc), entity, &mut commands)
185                    } else {
186                        remaining_tasks.push_back(TileEvent::SetTile { loc, entity })
187                    }
188                }
189                TileEvent::DeleteTile { loc, mark } => {
190                    let chunk_loc = chunk_from_location(loc);
191
192                    if tilemap.has_chunk(loc) {
193                        if let Ok((_, mut chunk)) = chunks.get_mut(
194                            *tilemap
195                                .chunks
196                                .get_mut(&chunk_loc)
197                                .expect("Chunk should exist"),
198                        ) {
199                            if mark {
200                                chunk.delete_tile(tile_from_location(loc), &mut commands);
201                            } else {
202                                chunk.delete_unmarked(tile_from_location(loc), &mut commands);
203                            }
204                        }
205                    }
206                }
207                TileEvent::SetPixel { loc, pixel, color } => {
208                    let chunk_loc = chunk_from_location(loc);
209
210                    if tilemap.has_chunk(loc) {
211                        if let Ok((_, mut chunk)) = chunks.get_mut(
212                            *tilemap
213                                .chunks
214                                .get_mut(&chunk_loc)
215                                .expect("Chunk should exist"),
216                        ) {
217                            if let Some(tile) = chunk.get_tile(tile_from_location(loc)) {
218                                tiles
219                                    .get_mut(tile)
220                                    .expect("Tile should exist")
221                                    .1
222                                    .set_pixel(pixel, color);
223                            }
224                            chunk.update_tile(tile_from_location(loc));
225                        }
226                    }
227                }
228            }
229        }
230        tilemap.tasks.append(&mut remaining_tasks);
231    }
232}