Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite_render/src/tilemap_chunk/mod.rs
6849 views
1
use core::ops::Neg;
2
3
use crate::{AlphaMode2d, MeshMaterial2d};
4
use bevy_app::{App, Plugin, Update};
5
use bevy_asset::{Assets, Handle};
6
use bevy_color::Color;
7
use bevy_derive::{Deref, DerefMut};
8
use bevy_ecs::{
9
component::Component,
10
entity::Entity,
11
lifecycle::HookContext,
12
query::Changed,
13
reflect::{ReflectComponent, ReflectResource},
14
resource::Resource,
15
system::{Query, ResMut},
16
world::DeferredWorld,
17
};
18
use bevy_image::Image;
19
use bevy_math::{primitives::Rectangle, UVec2};
20
use bevy_mesh::{Mesh, Mesh2d};
21
use bevy_platform::collections::HashMap;
22
use bevy_reflect::{prelude::*, Reflect};
23
use bevy_transform::components::Transform;
24
use bevy_utils::default;
25
use tracing::warn;
26
27
mod tilemap_chunk_material;
28
29
pub use tilemap_chunk_material::*;
30
31
/// Plugin that handles the initialization and updating of tilemap chunks.
32
/// Adds systems for processing newly added tilemap chunks and updating their indices.
33
pub struct TilemapChunkPlugin;
34
35
impl Plugin for TilemapChunkPlugin {
36
fn build(&self, app: &mut App) {
37
app.init_resource::<TilemapChunkMeshCache>()
38
.add_systems(Update, update_tilemap_chunk_indices);
39
}
40
}
41
42
/// A resource storing the meshes for each tilemap chunk size.
43
#[derive(Resource, Default, Deref, DerefMut, Reflect)]
44
#[reflect(Resource, Default)]
45
pub struct TilemapChunkMeshCache(HashMap<UVec2, Handle<Mesh>>);
46
47
/// A component representing a chunk of a tilemap.
48
/// Each chunk is a rectangular section of tiles that is rendered as a single mesh.
49
#[derive(Component, Clone, Debug, Default, Reflect)]
50
#[reflect(Component, Clone, Debug, Default)]
51
#[component(immutable, on_insert = on_insert_tilemap_chunk)]
52
pub struct TilemapChunk {
53
/// The size of the chunk in tiles.
54
pub chunk_size: UVec2,
55
/// The size to use for each tile, not to be confused with the size of a tile in the tileset image.
56
/// The size of the tile in the tileset image is determined by the tileset image's dimensions.
57
pub tile_display_size: UVec2,
58
/// Handle to the tileset image containing all tile textures.
59
pub tileset: Handle<Image>,
60
/// The alpha mode to use for the tilemap chunk.
61
pub alpha_mode: AlphaMode2d,
62
}
63
64
impl TilemapChunk {
65
pub fn calculate_tile_transform(&self, position: UVec2) -> Transform {
66
Transform::from_xyz(
67
// tile position
68
position.x as f32
69
// times display size for a tile
70
* self.tile_display_size.x as f32
71
// plus 1/2 the tile_display_size to correct the center
72
+ self.tile_display_size.x as f32 / 2.
73
// minus 1/2 the tilechunk size, in terms of the tile_display_size,
74
// to place the 0 at left of tilemapchunk
75
- self.tile_display_size.x as f32 * self.chunk_size.x as f32 / 2.,
76
// tile position
77
position.y as f32
78
// times display size for a tile
79
* (self.tile_display_size.y as f32).neg()
80
// minus 1/2 the tile_display_size to correct the center
81
- self.tile_display_size.y as f32 / 2.
82
// plus 1/2 the tilechunk size, in terms of the tile_display_size,
83
// to place the 0 at top of tilemapchunk
84
+ self.tile_display_size.y as f32 * self.chunk_size.y as f32 / 2.,
85
0.,
86
)
87
}
88
}
89
90
/// Data for a single tile in the tilemap chunk.
91
#[derive(Clone, Copy, Debug, Reflect)]
92
#[reflect(Clone, Debug, Default)]
93
pub struct TileData {
94
/// The index of the tile in the corresponding tileset array texture.
95
pub tileset_index: u16,
96
/// The color tint of the tile. White leaves the sampled texture color unchanged.
97
pub color: Color,
98
/// The visibility of the tile.
99
pub visible: bool,
100
}
101
102
impl TileData {
103
/// Creates a new `TileData` with the given tileset index and default values.
104
pub fn from_tileset_index(tileset_index: u16) -> Self {
105
Self {
106
tileset_index,
107
..default()
108
}
109
}
110
}
111
112
impl Default for TileData {
113
fn default() -> Self {
114
Self {
115
tileset_index: 0,
116
color: Color::WHITE,
117
visible: true,
118
}
119
}
120
}
121
122
/// Component storing the data of tiles within a chunk.
123
/// Each index corresponds to a specific tile in the tileset. `None` indicates an empty tile.
124
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
125
#[reflect(Component, Clone, Debug)]
126
pub struct TilemapChunkTileData(pub Vec<Option<TileData>>);
127
128
fn on_insert_tilemap_chunk(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
129
let Some(tilemap_chunk) = world.get::<TilemapChunk>(entity) else {
130
warn!("TilemapChunk not found for tilemap chunk {}", entity);
131
return;
132
};
133
134
let chunk_size = tilemap_chunk.chunk_size;
135
let alpha_mode = tilemap_chunk.alpha_mode;
136
let tileset = tilemap_chunk.tileset.clone();
137
138
let Some(tile_data) = world.get::<TilemapChunkTileData>(entity) else {
139
warn!("TilemapChunkIndices not found for tilemap chunk {}", entity);
140
return;
141
};
142
143
let expected_tile_data_length = chunk_size.element_product() as usize;
144
if tile_data.len() != expected_tile_data_length {
145
warn!(
146
"Invalid tile data length for tilemap chunk {} of size {}. Expected {}, got {}",
147
entity,
148
chunk_size,
149
expected_tile_data_length,
150
tile_data.len(),
151
);
152
return;
153
}
154
155
let packed_tile_data: Vec<PackedTileData> =
156
tile_data.0.iter().map(|&tile| tile.into()).collect();
157
158
let tile_data_image = make_chunk_tile_data_image(&chunk_size, &packed_tile_data);
159
160
let tilemap_chunk_mesh_cache = world.resource::<TilemapChunkMeshCache>();
161
162
let mesh_size = chunk_size * tilemap_chunk.tile_display_size;
163
164
let mesh = if let Some(mesh) = tilemap_chunk_mesh_cache.get(&mesh_size) {
165
mesh.clone()
166
} else {
167
let mut meshes = world.resource_mut::<Assets<Mesh>>();
168
meshes.add(Rectangle::from_size(mesh_size.as_vec2()))
169
};
170
171
let mut images = world.resource_mut::<Assets<Image>>();
172
let tile_data = images.add(tile_data_image);
173
174
let mut materials = world.resource_mut::<Assets<TilemapChunkMaterial>>();
175
let material = materials.add(TilemapChunkMaterial {
176
tileset,
177
tile_data,
178
alpha_mode,
179
});
180
181
world
182
.commands()
183
.entity(entity)
184
.insert((Mesh2d(mesh), MeshMaterial2d(material)));
185
}
186
187
fn update_tilemap_chunk_indices(
188
query: Query<
189
(
190
Entity,
191
&TilemapChunk,
192
&TilemapChunkTileData,
193
&MeshMaterial2d<TilemapChunkMaterial>,
194
),
195
Changed<TilemapChunkTileData>,
196
>,
197
mut materials: ResMut<Assets<TilemapChunkMaterial>>,
198
mut images: ResMut<Assets<Image>>,
199
) {
200
for (chunk_entity, TilemapChunk { chunk_size, .. }, tile_data, material) in query {
201
let expected_tile_data_length = chunk_size.element_product() as usize;
202
if tile_data.len() != expected_tile_data_length {
203
warn!(
204
"Invalid TilemapChunkTileData length for tilemap chunk {} of size {}. Expected {}, got {}",
205
chunk_entity,
206
chunk_size,
207
tile_data.len(),
208
expected_tile_data_length
209
);
210
continue;
211
}
212
213
let packed_tile_data: Vec<PackedTileData> =
214
tile_data.0.iter().map(|&tile| tile.into()).collect();
215
216
// Getting the material mutably to trigger change detection
217
let Some(material) = materials.get_mut(material.id()) else {
218
warn!(
219
"TilemapChunkMaterial not found for tilemap chunk {}",
220
chunk_entity
221
);
222
continue;
223
};
224
let Some(tile_data_image) = images.get_mut(&material.tile_data) else {
225
warn!(
226
"TilemapChunkMaterial tile data image not found for tilemap chunk {}",
227
chunk_entity
228
);
229
continue;
230
};
231
let Some(data) = tile_data_image.data.as_mut() else {
232
warn!(
233
"TilemapChunkMaterial tile data image data not found for tilemap chunk {}",
234
chunk_entity
235
);
236
continue;
237
};
238
data.clear();
239
data.extend_from_slice(bytemuck::cast_slice(&packed_tile_data));
240
}
241
}
242
243
impl TilemapChunkTileData {
244
pub fn tile_data_from_tile_pos(
245
&self,
246
tilemap_size: UVec2,
247
position: UVec2,
248
) -> Option<&TileData> {
249
self.0
250
.get(tilemap_size.x as usize * position.y as usize + position.x as usize)
251
.and_then(|opt| opt.as_ref())
252
}
253
}
254
255