Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite/src/lib.rs
9334 views
1
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
2
#![cfg_attr(docsrs, feature(doc_cfg))]
3
#![forbid(unsafe_code)]
4
#![doc(
5
html_logo_url = "https://bevy.org/assets/icon.png",
6
html_favicon_url = "https://bevy.org/assets/icon.png"
7
)]
8
9
//! Provides 2D sprite functionality.
10
11
extern crate alloc;
12
13
#[cfg(feature = "bevy_picking")]
14
mod picking_backend;
15
mod sprite;
16
mod sprite_mesh;
17
#[cfg(feature = "bevy_text")]
18
mod text2d;
19
mod texture_slice;
20
21
/// The sprite prelude.
22
///
23
/// This includes the most common types in this crate, re-exported for your convenience.
24
pub mod prelude {
25
#[cfg(feature = "bevy_picking")]
26
#[doc(hidden)]
27
pub use crate::picking_backend::{
28
SpritePickingCamera, SpritePickingMode, SpritePickingPlugin, SpritePickingSettings,
29
};
30
#[cfg(feature = "bevy_text")]
31
#[doc(hidden)]
32
pub use crate::text2d::{Text2d, Text2dReader, Text2dWriter};
33
#[doc(hidden)]
34
pub use crate::{
35
sprite::{Sprite, SpriteImageMode},
36
sprite_mesh::SpriteMesh,
37
texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer},
38
SpriteScalingMode,
39
};
40
}
41
42
use bevy_asset::Assets;
43
use bevy_camera::{
44
primitives::{Aabb, MeshAabb},
45
visibility::NoFrustumCulling,
46
visibility::VisibilitySystems,
47
};
48
use bevy_mesh::{Mesh, Mesh2d};
49
#[cfg(feature = "bevy_picking")]
50
pub use picking_backend::*;
51
pub use sprite::*;
52
pub use sprite_mesh::*;
53
#[cfg(feature = "bevy_text")]
54
pub use text2d::*;
55
pub use texture_slice::*;
56
57
use bevy_app::prelude::*;
58
use bevy_asset::prelude::AssetChanged;
59
use bevy_camera::visibility::NoAutoAabb;
60
use bevy_ecs::prelude::*;
61
use bevy_image::{Image, TextureAtlasLayout, TextureAtlasPlugin};
62
use bevy_math::Vec2;
63
64
/// Adds support for 2D sprites.
65
#[derive(Default)]
66
pub struct SpritePlugin;
67
68
/// System set for sprite rendering.
69
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
70
pub enum SpriteSystems {
71
ExtractSprites,
72
ComputeSlices,
73
}
74
75
impl Plugin for SpritePlugin {
76
fn build(&self, app: &mut App) {
77
if !app.is_plugin_added::<TextureAtlasPlugin>() {
78
app.add_plugins(TextureAtlasPlugin);
79
}
80
app.add_systems(
81
PostUpdate,
82
(calculate_bounds_2d, calculate_bounds_2d_sprite_mesh)
83
.chain()
84
.in_set(VisibilitySystems::CalculateBounds),
85
);
86
87
#[cfg(feature = "bevy_text")]
88
app.add_systems(
89
PostUpdate,
90
(
91
bevy_text::detect_text_needs_rerender::<Text2d>,
92
update_text2d_layout.after(bevy_camera::CameraUpdateSystems),
93
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
94
)
95
.chain()
96
.after(bevy_text::load_font_assets_into_fontdb_system)
97
.in_set(bevy_text::Text2dUpdateSystems)
98
.after(bevy_app::AnimationSystems),
99
);
100
101
#[cfg(feature = "bevy_picking")]
102
app.add_plugins(SpritePickingPlugin);
103
}
104
}
105
106
/// System calculating and inserting an [`Aabb`] component to entities with either:
107
/// - a `Mesh2d` component,
108
/// - a `Sprite` and `Handle<Image>` components,
109
/// and without a [`NoFrustumCulling`] component.
110
///
111
/// Used in system set [`VisibilitySystems::CalculateBounds`].
112
pub fn calculate_bounds_2d(
113
mut commands: Commands,
114
meshes: Res<Assets<Mesh>>,
115
images: Res<Assets<Image>>,
116
atlases: Res<Assets<TextureAtlasLayout>>,
117
new_mesh_aabb: Query<
118
(Entity, &Mesh2d),
119
(
120
Without<Aabb>,
121
Without<NoFrustumCulling>,
122
Without<NoAutoAabb>,
123
Without<SpriteMesh>, // temporary before merging SpriteMesh into Sprite,
124
),
125
>,
126
mut update_mesh_aabb: Query<
127
(&Mesh2d, &mut Aabb),
128
(
129
Or<(AssetChanged<Mesh2d>, Changed<Mesh2d>)>,
130
Without<NoFrustumCulling>,
131
Without<NoAutoAabb>,
132
Without<SpriteMesh>, // temporary before merging SpriteMesh into Sprite,
133
Without<Sprite>, // disjoint mutable query
134
),
135
>,
136
new_sprite_aabb: Query<
137
(Entity, &Sprite, &Anchor),
138
(
139
Without<Aabb>,
140
Without<NoFrustumCulling>,
141
Without<NoAutoAabb>,
142
),
143
>,
144
mut update_sprite_aabb: Query<
145
(&Sprite, &mut Aabb, &Anchor),
146
(
147
Or<(Changed<Sprite>, Changed<Anchor>)>,
148
Without<NoFrustumCulling>,
149
Without<NoAutoAabb>,
150
Without<Mesh2d>, // disjoint mutable query
151
),
152
>,
153
) {
154
// New meshes require inserting a component
155
for (entity, mesh_handle) in &new_mesh_aabb {
156
if let Some(mesh) = meshes.get(mesh_handle)
157
&& let Some(aabb) = mesh.compute_aabb()
158
{
159
commands.entity(entity).try_insert(aabb);
160
}
161
}
162
163
// Updated meshes can take the fast path with parallel component mutation
164
update_mesh_aabb
165
.par_iter_mut()
166
.for_each(|(mesh_handle, mut aabb)| {
167
if let Some(new_aabb) = meshes.get(mesh_handle).and_then(MeshAabb::compute_aabb) {
168
aabb.set_if_neq(new_aabb);
169
}
170
});
171
172
// Sprite helper
173
let sprite_size = |sprite: &Sprite| -> Option<Vec2> {
174
sprite
175
.custom_size
176
.or_else(|| sprite.rect.map(|rect| rect.size()))
177
.or_else(|| match &sprite.texture_atlas {
178
// We default to the texture size for regular sprites
179
None => images.get(&sprite.image).map(Image::size_f32),
180
// We default to the drawn rect for atlas sprites
181
Some(atlas) => atlas
182
.texture_rect(&atlases)
183
.map(|rect| rect.size().as_vec2()),
184
})
185
};
186
187
// New sprites require inserting a component
188
for (size, (entity, anchor)) in new_sprite_aabb
189
.iter()
190
.filter_map(|(entity, sprite, anchor)| sprite_size(sprite).zip(Some((entity, anchor))))
191
{
192
let aabb = Aabb {
193
center: (-anchor.as_vec() * size).extend(0.0).into(),
194
half_extents: (0.5 * size).extend(0.0).into(),
195
};
196
commands.entity(entity).try_insert(aabb);
197
}
198
199
// Updated sprites can take the fast path with parallel component mutation
200
update_sprite_aabb
201
.par_iter_mut()
202
.for_each(|(sprite, mut aabb, anchor)| {
203
if let Some(size) = sprite_size(sprite) {
204
aabb.set_if_neq(Aabb {
205
center: (-anchor.as_vec() * size).extend(0.0).into(),
206
half_extents: (0.5 * size).extend(0.0).into(),
207
});
208
}
209
});
210
}
211
212
// Temporarily added this to calculate aabb for sprite meshes.
213
// Will eventually be merged with Sprite in the system above.
214
//
215
// NOTE: this is separate from Mesh2d because sprites change their size
216
// inside the vertex shader which isn't recognized by calculate_aabb().
217
fn calculate_bounds_2d_sprite_mesh(
218
mut commands: Commands,
219
images: Res<Assets<Image>>,
220
atlases: Res<Assets<TextureAtlasLayout>>,
221
new_sprite_aabb: Query<
222
(Entity, &SpriteMesh, &Anchor),
223
(
224
Without<Aabb>,
225
Without<NoFrustumCulling>,
226
Without<NoAutoAabb>,
227
),
228
>,
229
mut update_sprite_aabb: Query<
230
(&SpriteMesh, &mut Aabb, &Anchor),
231
(
232
Or<(Changed<SpriteMesh>, Changed<Anchor>)>,
233
Without<NoFrustumCulling>,
234
Without<NoAutoAabb>,
235
),
236
>,
237
) {
238
// Sprite helper
239
let sprite_size = |sprite: &SpriteMesh| -> Option<Vec2> {
240
sprite
241
.custom_size
242
.or_else(|| sprite.rect.map(|rect| rect.size()))
243
.or_else(|| match &sprite.texture_atlas {
244
// We default to the texture size for regular sprites
245
None => images.get(&sprite.image).map(Image::size_f32),
246
// We default to the drawn rect for atlas sprites
247
Some(atlas) => atlas
248
.texture_rect(&atlases)
249
.map(|rect| rect.size().as_vec2()),
250
})
251
};
252
253
// New sprites require inserting a component
254
for (size, (entity, anchor)) in new_sprite_aabb
255
.iter()
256
.filter_map(|(entity, sprite, anchor)| sprite_size(sprite).zip(Some((entity, anchor))))
257
{
258
let aabb = Aabb {
259
center: (-anchor.as_vec() * size).extend(0.0).into(),
260
half_extents: (0.5 * size).extend(0.0).into(),
261
};
262
commands.entity(entity).try_insert(aabb);
263
}
264
265
// Updated sprites can take the fast path with parallel component mutation
266
update_sprite_aabb
267
.par_iter_mut()
268
.for_each(|(sprite, mut aabb, anchor)| {
269
if let Some(size) = sprite_size(sprite) {
270
aabb.set_if_neq(Aabb {
271
center: (-anchor.as_vec() * size).extend(0.0).into(),
272
half_extents: (0.5 * size).extend(0.0).into(),
273
});
274
}
275
});
276
}
277
278
#[cfg(test)]
279
mod test {
280
use super::*;
281
use bevy_math::{Rect, Vec2, Vec3A};
282
283
#[test]
284
fn calculate_bounds_2d_create_aabb_for_image_sprite_entity() {
285
// Setup app
286
let mut app = App::new();
287
288
// Add resources and get handle to image
289
let mut image_assets = Assets::<Image>::default();
290
let image_handle = image_assets.add(Image::default());
291
app.insert_resource(image_assets);
292
let mesh_assets = Assets::<Mesh>::default();
293
app.insert_resource(mesh_assets);
294
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
295
app.insert_resource(texture_atlas_assets);
296
297
// Add system
298
app.add_systems(Update, calculate_bounds_2d);
299
300
// Add entities
301
let entity = app.world_mut().spawn(Sprite::from_image(image_handle)).id();
302
303
// Verify that the entity does not have an AABB
304
assert!(!app
305
.world()
306
.get_entity(entity)
307
.expect("Could not find entity")
308
.contains::<Aabb>());
309
310
// Run system
311
app.update();
312
313
// Verify the AABB exists
314
assert!(app
315
.world()
316
.get_entity(entity)
317
.expect("Could not find entity")
318
.contains::<Aabb>());
319
}
320
321
#[test]
322
fn calculate_bounds_2d_update_aabb_when_sprite_custom_size_changes_to_some() {
323
// Setup app
324
let mut app = App::new();
325
326
// Add resources and get handle to image
327
let mut image_assets = Assets::<Image>::default();
328
let image_handle = image_assets.add(Image::default());
329
app.insert_resource(image_assets);
330
let mesh_assets = Assets::<Mesh>::default();
331
app.insert_resource(mesh_assets);
332
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
333
app.insert_resource(texture_atlas_assets);
334
335
// Add system
336
app.add_systems(Update, calculate_bounds_2d);
337
338
// Add entities
339
let entity = app
340
.world_mut()
341
.spawn(Sprite {
342
custom_size: Some(Vec2::ZERO),
343
image: image_handle,
344
..Sprite::default()
345
})
346
.id();
347
348
// Create initial AABB
349
app.update();
350
351
// Get the initial AABB
352
let first_aabb = *app
353
.world()
354
.get_entity(entity)
355
.expect("Could not find entity")
356
.get::<Aabb>()
357
.expect("Could not find initial AABB");
358
359
// Change `custom_size` of sprite
360
let mut binding = app
361
.world_mut()
362
.get_entity_mut(entity)
363
.expect("Could not find entity");
364
let mut sprite = binding
365
.get_mut::<Sprite>()
366
.expect("Could not find sprite component of entity");
367
sprite.custom_size = Some(Vec2::ONE);
368
369
// Re-run the `calculate_bounds_2d` system to get the new AABB
370
app.update();
371
372
// Get the re-calculated AABB
373
let second_aabb = *app
374
.world()
375
.get_entity(entity)
376
.expect("Could not find entity")
377
.get::<Aabb>()
378
.expect("Could not find second AABB");
379
380
// Check that the AABBs are not equal
381
assert_ne!(first_aabb, second_aabb);
382
}
383
384
#[test]
385
fn calculate_bounds_2d_correct_aabb_for_sprite_with_custom_rect() {
386
// Setup app
387
let mut app = App::new();
388
389
// Add resources and get handle to image
390
let mut image_assets = Assets::<Image>::default();
391
let image_handle = image_assets.add(Image::default());
392
app.insert_resource(image_assets);
393
let mesh_assets = Assets::<Mesh>::default();
394
app.insert_resource(mesh_assets);
395
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
396
app.insert_resource(texture_atlas_assets);
397
398
// Add system
399
app.add_systems(Update, calculate_bounds_2d);
400
401
// Add entities
402
let entity = app
403
.world_mut()
404
.spawn((
405
Sprite {
406
rect: Some(Rect::new(0., 0., 0.5, 1.)),
407
image: image_handle,
408
..Sprite::default()
409
},
410
Anchor::TOP_RIGHT,
411
))
412
.id();
413
414
// Create AABB
415
app.update();
416
417
// Get the AABB
418
let aabb = *app
419
.world_mut()
420
.get_entity(entity)
421
.expect("Could not find entity")
422
.get::<Aabb>()
423
.expect("Could not find AABB");
424
425
// Verify that the AABB is at the expected position
426
assert_eq!(aabb.center, Vec3A::new(-0.25, -0.5, 0.));
427
428
// Verify that the AABB has the expected size
429
assert_eq!(aabb.half_extents, Vec3A::new(0.25, 0.5, 0.));
430
}
431
}
432
433