bevy_pixel_map/
chunk.rs

1use bevy::{prelude::*, render::render_resource::Extent3d};
2
3use crate::{
4    tile::{DeletingTile, Tile},
5    CHUNK_SIZE, TILE_SIZE,
6};
7
8#[derive(Bundle)]
9pub struct ChunkBundle {
10    sprite: SpriteBundle,
11    chunk: Chunk,
12}
13
14impl ChunkBundle {
15    pub fn new(loc: IVec2, chunk: Chunk) -> Self {
16        Self {
17            sprite: SpriteBundle {
18                texture: chunk.image_handle.clone_weak(),
19                transform: Transform::from_xyz(
20                    loc.x as f32 * CHUNK_SIZE as f32,
21                    loc.y as f32 * CHUNK_SIZE as f32,
22                    0.0,
23                )
24                .with_scale(Vec3::splat(1.0 / TILE_SIZE as f32)),
25                ..Default::default()
26            },
27            chunk,
28        }
29    }
30}
31
32#[derive(Component)]
33pub struct Chunk {
34    tiles: [[Option<Entity>; CHUNK_SIZE]; CHUNK_SIZE],
35    image: Image,
36    image_handle: Handle<Image>,
37    dirty_tiles: Vec<IVec2>,
38}
39
40impl Chunk {
41    pub fn new(images: &mut ResMut<Assets<Image>>) -> Self {
42        let mut data = vec![];
43
44        for _ in 0..((CHUNK_SIZE * CHUNK_SIZE) * TILE_SIZE * TILE_SIZE) {
45            data.append(&mut Color::rgba(0.0, 0.0, 0.0, 0.0).as_rgba_u8().to_vec());
46        }
47
48        let image = Image::new(
49            Extent3d {
50                width: CHUNK_SIZE as u32 * TILE_SIZE as u32,
51                height: CHUNK_SIZE as u32 * TILE_SIZE as u32,
52                ..default()
53            },
54            bevy::render::render_resource::TextureDimension::D2,
55            data,
56            bevy::render::render_resource::TextureFormat::Rgba8Unorm,
57        );
58
59        Self {
60            tiles: [[None; CHUNK_SIZE]; CHUNK_SIZE],
61            image: image.clone(),
62            image_handle: images.add(image),
63            dirty_tiles: vec![],
64        }
65    }
66
67    pub fn get_tile(&self, loc: IVec2) -> Option<Entity> {
68        if !verify_chunk_loc(loc) {
69            return None;
70        }
71
72        self.tiles[loc.x as usize][loc.y as usize]
73    }
74
75    pub fn set_tile(
76        &mut self,
77        my_entity: Entity,
78        loc: IVec2,
79        tile: Tile,
80        additional_components: impl Bundle,
81        commands: &mut Commands,
82    ) {
83        if !verify_chunk_loc(loc) {
84            return;
85        }
86
87        self.delete_tile(loc, commands);
88
89        self.tiles[loc.x as usize][loc.y as usize] = Some(
90            commands
91                .spawn((tile, additional_components))
92                .set_parent(my_entity)
93                .id(),
94        );
95
96        self.update_tile(loc)
97    }
98
99    pub fn set_tile_entity(&mut self, loc: IVec2, entity: Entity, commands: &mut Commands) {
100        self.delete_tile(loc, commands);
101        self.tiles[loc.x as usize][loc.y as usize] = Some(entity);
102
103        self.update_tile(loc)
104    }
105
106    pub fn delete_tile(&mut self, loc: IVec2, commands: &mut Commands) {
107        if let Some(entity) = self.get_tile(loc) {
108            commands.entity(entity).insert(DeletingTile);
109            self.tiles[loc.x as usize][loc.y as usize] = None;
110            self.update_tile(loc);
111        }
112    }
113
114    pub fn delete_unmarked(&mut self, loc: IVec2, commands: &mut Commands) {
115        if let Some(entity) = self.get_tile(loc) {
116            commands.entity(entity).despawn_recursive();
117            self.tiles[loc.x as usize][loc.y as usize] = None;
118            self.update_tile(loc);
119        }
120    }
121
122    pub fn update_tile(&mut self, loc: IVec2) {
123        if !verify_chunk_loc(loc) {
124            return;
125        }
126        if self.dirty_tiles.contains(&loc) {
127            return;
128        }
129
130        self.dirty_tiles.push(loc)
131    }
132
133    pub fn update_texture(
134        &mut self,
135        images: &mut ResMut<Assets<Image>>,
136        tiles: &mut Query<&mut Tile>,
137    ) {
138        if self.dirty_tiles.is_empty() {
139            return;
140        }
141
142        let mut data = self.image.data.clone();
143
144        for loc in &self.dirty_tiles {
145            if let Some(tile) = self.get_tile(*loc) {
146                if let Ok(tile) = tiles.get(tile) {
147                    for pixel_x in 0..TILE_SIZE {
148                        for pixel_y in 0..TILE_SIZE {
149                            // Inverse of y * pixels per tile + the current pixel
150                            let pixel_index_y =
151                                ((CHUNK_SIZE - 1 - loc.y as usize) * TILE_SIZE) + pixel_y;
152                            // x * pixels per tile + the current pixel
153                            let pixel_index_x = (loc.x as usize * TILE_SIZE) + pixel_x;
154
155                            let color = tile
156                                .get_pixel(IVec2::new(pixel_x as i32, pixel_y as i32))
157                                .expect("Pixel should be in range")
158                                .as_rgba_u8();
159
160                            // Update the color on the texture with tile texture
161                            data[pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4
162                                + pixel_index_x * 4] = color[0];
163
164                            data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4
165                                + pixel_index_x * 4)
166                                + 1] = color[1];
167
168                            data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4
169                                + pixel_index_x * 4)
170                                + 2] = color[2];
171
172                            data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4
173                                + pixel_index_x * 4)
174                                + 3] = color[3];
175                        }
176                    }
177                }
178            } else {
179                // It's dirty, but the tile doesn't exist, so clear the tile's color.
180                for pixel_x in 0..TILE_SIZE {
181                    for pixel_y in 0..TILE_SIZE {
182                        // Inverse of y * pixels per tile + the current pixel
183                        let pixel_index_y =
184                            ((CHUNK_SIZE - 1 - loc.y as usize) * TILE_SIZE) + pixel_y;
185
186                        // x * pixels per tile + the current pixel
187                        let pixel_index_x = (loc.x as usize * TILE_SIZE) + pixel_x;
188
189                        // Update the color on the texture with tile texture
190                        data[pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4 + pixel_index_x * 4] = 0;
191
192                        data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4 + pixel_index_x * 4)
193                            + 1] = 0;
194
195                        data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4 + pixel_index_x * 4)
196                            + 2] = 0;
197
198                        data[(pixel_index_y * (CHUNK_SIZE * TILE_SIZE) * 4 + pixel_index_x * 4)
199                            + 3] = 0;
200                    }
201                }
202            }
203        }
204
205        self.dirty_tiles.clear();
206
207        self.image.data = data;
208
209        images.insert(self.image_handle.clone(), self.image.clone());
210    }
211}
212
213fn verify_chunk_loc(loc: IVec2) -> bool {
214    if loc.x < 0 || loc.x >= CHUNK_SIZE as i32 || loc.y < 0 || loc.y >= CHUNK_SIZE as i32 {
215        return false;
216    }
217    true
218}
219
220pub fn chunk_texture_update(
221    mut images: ResMut<Assets<Image>>,
222    mut tiles: Query<&mut Tile>,
223    mut chunks: Query<&mut Chunk>,
224) {
225    for mut chunk in &mut chunks {
226        chunk.update_texture(&mut images, &mut tiles)
227    }
228}
229
230pub fn chunk_deleter(
231    mut commands: Commands,
232    deleting_tiles: Query<Entity, (With<Tile>, With<DeletingTile>)>,
233) {
234    for entity in &deleting_tiles {
235        commands.entity(entity).despawn_recursive();
236    }
237}