Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite/src/lib.rs
6598 views
1
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
2
#![cfg_attr(docsrs, feature(doc_auto_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_sprite_picking_backend")]
14
mod picking_backend;
15
mod sprite;
16
#[cfg(feature = "bevy_text")]
17
mod text2d;
18
mod texture_slice;
19
20
/// The sprite prelude.
21
///
22
/// This includes the most common types in this crate, re-exported for your convenience.
23
pub mod prelude {
24
#[cfg(feature = "bevy_sprite_picking_backend")]
25
#[doc(hidden)]
26
pub use crate::picking_backend::{
27
SpritePickingCamera, SpritePickingMode, SpritePickingPlugin, SpritePickingSettings,
28
};
29
#[cfg(feature = "bevy_text")]
30
#[doc(hidden)]
31
pub use crate::text2d::{Text2d, Text2dReader, Text2dWriter};
32
#[doc(hidden)]
33
pub use crate::{
34
sprite::{Sprite, SpriteImageMode},
35
texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer},
36
ScalingMode,
37
};
38
}
39
40
use bevy_asset::Assets;
41
use bevy_camera::{
42
primitives::{Aabb, MeshAabb},
43
visibility::NoFrustumCulling,
44
visibility::VisibilitySystems,
45
};
46
use bevy_mesh::{Mesh, Mesh2d};
47
#[cfg(feature = "bevy_sprite_picking_backend")]
48
pub use picking_backend::*;
49
pub use sprite::*;
50
#[cfg(feature = "bevy_text")]
51
pub use text2d::*;
52
pub use texture_slice::*;
53
54
use bevy_app::prelude::*;
55
use bevy_ecs::prelude::*;
56
use bevy_image::{Image, TextureAtlasLayout, TextureAtlasPlugin};
57
58
/// Adds support for 2D sprites.
59
#[derive(Default)]
60
pub struct SpritePlugin;
61
62
/// System set for sprite rendering.
63
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
64
pub enum SpriteSystems {
65
ExtractSprites,
66
ComputeSlices,
67
}
68
69
/// Deprecated alias for [`SpriteSystems`].
70
#[deprecated(since = "0.17.0", note = "Renamed to `SpriteSystems`.")]
71
pub type SpriteSystem = SpriteSystems;
72
73
impl Plugin for SpritePlugin {
74
fn build(&self, app: &mut App) {
75
if !app.is_plugin_added::<TextureAtlasPlugin>() {
76
app.add_plugins(TextureAtlasPlugin);
77
}
78
app.add_systems(
79
PostUpdate,
80
calculate_bounds_2d.in_set(VisibilitySystems::CalculateBounds),
81
);
82
83
#[cfg(feature = "bevy_text")]
84
app.add_systems(
85
PostUpdate,
86
(
87
bevy_text::detect_text_needs_rerender::<Text2d>,
88
update_text2d_layout
89
.after(bevy_camera::CameraUpdateSystems)
90
.after(bevy_text::remove_dropped_font_atlas_sets),
91
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
92
)
93
.chain()
94
.in_set(bevy_text::Text2dUpdateSystems)
95
.after(bevy_app::AnimationSystems),
96
);
97
98
#[cfg(feature = "bevy_sprite_picking_backend")]
99
app.add_plugins(SpritePickingPlugin);
100
}
101
}
102
103
/// System calculating and inserting an [`Aabb`] component to entities with either:
104
/// - a `Mesh2d` component,
105
/// - a `Sprite` and `Handle<Image>` components,
106
/// and without a [`NoFrustumCulling`] component.
107
///
108
/// Used in system set [`VisibilitySystems::CalculateBounds`].
109
pub fn calculate_bounds_2d(
110
mut commands: Commands,
111
meshes: Res<Assets<Mesh>>,
112
images: Res<Assets<Image>>,
113
atlases: Res<Assets<TextureAtlasLayout>>,
114
meshes_without_aabb: Query<(Entity, &Mesh2d), (Without<Aabb>, Without<NoFrustumCulling>)>,
115
sprites_to_recalculate_aabb: Query<
116
(Entity, &Sprite, &Anchor),
117
(
118
Or<(Without<Aabb>, Changed<Sprite>, Changed<Anchor>)>,
119
Without<NoFrustumCulling>,
120
),
121
>,
122
) {
123
for (entity, mesh_handle) in &meshes_without_aabb {
124
if let Some(mesh) = meshes.get(&mesh_handle.0)
125
&& let Some(aabb) = mesh.compute_aabb()
126
{
127
commands.entity(entity).try_insert(aabb);
128
}
129
}
130
for (entity, sprite, anchor) in &sprites_to_recalculate_aabb {
131
if let Some(size) = sprite
132
.custom_size
133
.or_else(|| sprite.rect.map(|rect| rect.size()))
134
.or_else(|| match &sprite.texture_atlas {
135
// We default to the texture size for regular sprites
136
None => images.get(&sprite.image).map(Image::size_f32),
137
// We default to the drawn rect for atlas sprites
138
Some(atlas) => atlas
139
.texture_rect(&atlases)
140
.map(|rect| rect.size().as_vec2()),
141
})
142
{
143
let aabb = Aabb {
144
center: (-anchor.as_vec() * size).extend(0.0).into(),
145
half_extents: (0.5 * size).extend(0.0).into(),
146
};
147
commands.entity(entity).try_insert(aabb);
148
}
149
}
150
}
151
152
#[cfg(test)]
153
mod test {
154
use super::*;
155
use bevy_math::{Rect, Vec2, Vec3A};
156
157
#[test]
158
fn calculate_bounds_2d_create_aabb_for_image_sprite_entity() {
159
// Setup app
160
let mut app = App::new();
161
162
// Add resources and get handle to image
163
let mut image_assets = Assets::<Image>::default();
164
let image_handle = image_assets.add(Image::default());
165
app.insert_resource(image_assets);
166
let mesh_assets = Assets::<Mesh>::default();
167
app.insert_resource(mesh_assets);
168
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
169
app.insert_resource(texture_atlas_assets);
170
171
// Add system
172
app.add_systems(Update, calculate_bounds_2d);
173
174
// Add entities
175
let entity = app.world_mut().spawn(Sprite::from_image(image_handle)).id();
176
177
// Verify that the entity does not have an AABB
178
assert!(!app
179
.world()
180
.get_entity(entity)
181
.expect("Could not find entity")
182
.contains::<Aabb>());
183
184
// Run system
185
app.update();
186
187
// Verify the AABB exists
188
assert!(app
189
.world()
190
.get_entity(entity)
191
.expect("Could not find entity")
192
.contains::<Aabb>());
193
}
194
195
#[test]
196
fn calculate_bounds_2d_update_aabb_when_sprite_custom_size_changes_to_some() {
197
// Setup app
198
let mut app = App::new();
199
200
// Add resources and get handle to image
201
let mut image_assets = Assets::<Image>::default();
202
let image_handle = image_assets.add(Image::default());
203
app.insert_resource(image_assets);
204
let mesh_assets = Assets::<Mesh>::default();
205
app.insert_resource(mesh_assets);
206
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
207
app.insert_resource(texture_atlas_assets);
208
209
// Add system
210
app.add_systems(Update, calculate_bounds_2d);
211
212
// Add entities
213
let entity = app
214
.world_mut()
215
.spawn(Sprite {
216
custom_size: Some(Vec2::ZERO),
217
image: image_handle,
218
..Sprite::default()
219
})
220
.id();
221
222
// Create initial AABB
223
app.update();
224
225
// Get the initial AABB
226
let first_aabb = *app
227
.world()
228
.get_entity(entity)
229
.expect("Could not find entity")
230
.get::<Aabb>()
231
.expect("Could not find initial AABB");
232
233
// Change `custom_size` of sprite
234
let mut binding = app
235
.world_mut()
236
.get_entity_mut(entity)
237
.expect("Could not find entity");
238
let mut sprite = binding
239
.get_mut::<Sprite>()
240
.expect("Could not find sprite component of entity");
241
sprite.custom_size = Some(Vec2::ONE);
242
243
// Re-run the `calculate_bounds_2d` system to get the new AABB
244
app.update();
245
246
// Get the re-calculated AABB
247
let second_aabb = *app
248
.world()
249
.get_entity(entity)
250
.expect("Could not find entity")
251
.get::<Aabb>()
252
.expect("Could not find second AABB");
253
254
// Check that the AABBs are not equal
255
assert_ne!(first_aabb, second_aabb);
256
}
257
258
#[test]
259
fn calculate_bounds_2d_correct_aabb_for_sprite_with_custom_rect() {
260
// Setup app
261
let mut app = App::new();
262
263
// Add resources and get handle to image
264
let mut image_assets = Assets::<Image>::default();
265
let image_handle = image_assets.add(Image::default());
266
app.insert_resource(image_assets);
267
let mesh_assets = Assets::<Mesh>::default();
268
app.insert_resource(mesh_assets);
269
let texture_atlas_assets = Assets::<TextureAtlasLayout>::default();
270
app.insert_resource(texture_atlas_assets);
271
272
// Add system
273
app.add_systems(Update, calculate_bounds_2d);
274
275
// Add entities
276
let entity = app
277
.world_mut()
278
.spawn((
279
Sprite {
280
rect: Some(Rect::new(0., 0., 0.5, 1.)),
281
image: image_handle,
282
..Sprite::default()
283
},
284
Anchor::TOP_RIGHT,
285
))
286
.id();
287
288
// Create AABB
289
app.update();
290
291
// Get the AABB
292
let aabb = *app
293
.world_mut()
294
.get_entity(entity)
295
.expect("Could not find entity")
296
.get::<Aabb>()
297
.expect("Could not find AABB");
298
299
// Verify that the AABB is at the expected position
300
assert_eq!(aabb.center, Vec3A::new(-0.25, -0.5, 0.));
301
302
// Verify that the AABB has the expected size
303
assert_eq!(aabb.half_extents, Vec3A::new(0.25, 0.5, 0.));
304
}
305
}
306
307