Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/texture_atlas.rs
6592 views
1
//! In this example we generate four texture atlases (sprite sheets) from a folder containing
2
//! individual sprites.
3
//!
4
//! The texture atlases are generated with different padding and sampling to demonstrate the
5
//! effect of these settings, and how bleeding issues can be resolved by padding the sprites.
6
//!
7
//! Only one padded and one unpadded texture atlas are rendered to the screen.
8
//! An upscaled sprite from each of the four atlases are rendered to the screen.
9
10
use bevy::{asset::LoadedFolder, image::ImageSampler, prelude::*};
11
12
fn main() {
13
App::new()
14
.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // fallback to nearest sampling
15
.init_state::<AppState>()
16
.add_systems(OnEnter(AppState::Setup), load_textures)
17
.add_systems(Update, check_textures.run_if(in_state(AppState::Setup)))
18
.add_systems(OnEnter(AppState::Finished), setup)
19
.run();
20
}
21
22
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, States)]
23
enum AppState {
24
#[default]
25
Setup,
26
Finished,
27
}
28
29
#[derive(Resource, Default)]
30
struct RpgSpriteFolder(Handle<LoadedFolder>);
31
32
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
33
// Load multiple, individual sprites from a folder
34
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder("textures/rpg")));
35
}
36
37
fn check_textures(
38
mut next_state: ResMut<NextState<AppState>>,
39
rpg_sprite_folder: Res<RpgSpriteFolder>,
40
mut events: EventReader<AssetEvent<LoadedFolder>>,
41
) {
42
// Advance the `AppState` once all sprite handles have been loaded by the `AssetServer`
43
for event in events.read() {
44
if event.is_loaded_with_dependencies(&rpg_sprite_folder.0) {
45
next_state.set(AppState::Finished);
46
}
47
}
48
}
49
50
fn setup(
51
mut commands: Commands,
52
rpg_sprite_handles: Res<RpgSpriteFolder>,
53
asset_server: Res<AssetServer>,
54
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
55
loaded_folders: Res<Assets<LoadedFolder>>,
56
mut textures: ResMut<Assets<Image>>,
57
) {
58
let loaded_folder = loaded_folders.get(&rpg_sprite_handles.0).unwrap();
59
60
// Create texture atlases with different padding and sampling
61
62
let (texture_atlas_linear, linear_sources, linear_texture) = create_texture_atlas(
63
loaded_folder,
64
None,
65
Some(ImageSampler::linear()),
66
&mut textures,
67
);
68
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear);
69
70
let (texture_atlas_nearest, nearest_sources, nearest_texture) = create_texture_atlas(
71
loaded_folder,
72
None,
73
Some(ImageSampler::nearest()),
74
&mut textures,
75
);
76
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);
77
78
let (texture_atlas_linear_padded, linear_padded_sources, linear_padded_texture) =
79
create_texture_atlas(
80
loaded_folder,
81
Some(UVec2::new(6, 6)),
82
Some(ImageSampler::linear()),
83
&mut textures,
84
);
85
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());
86
87
let (texture_atlas_nearest_padded, nearest_padded_sources, nearest_padded_texture) =
88
create_texture_atlas(
89
loaded_folder,
90
Some(UVec2::new(6, 6)),
91
Some(ImageSampler::nearest()),
92
&mut textures,
93
);
94
let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded);
95
96
commands.spawn(Camera2d);
97
98
// Padded textures are to the right, unpadded to the left
99
100
// Draw unpadded texture atlas
101
commands.spawn((
102
Sprite::from_image(linear_texture.clone()),
103
Transform {
104
translation: Vec3::new(-250.0, -160.0, 0.0),
105
scale: Vec3::splat(0.5),
106
..default()
107
},
108
));
109
110
// Draw padded texture atlas
111
commands.spawn((
112
Sprite::from_image(linear_padded_texture.clone()),
113
Transform {
114
translation: Vec3::new(250.0, -160.0, 0.0),
115
scale: Vec3::splat(0.5),
116
..default()
117
},
118
));
119
120
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
121
122
// Padding label text style
123
let text_style: TextFont = TextFont {
124
font: font.clone(),
125
font_size: 42.0,
126
..default()
127
};
128
129
// Labels to indicate padding
130
131
// No padding
132
create_label(
133
&mut commands,
134
(-250.0, 250.0, 0.0),
135
"No padding",
136
text_style.clone(),
137
);
138
139
// Padding
140
create_label(&mut commands, (250.0, 250.0, 0.0), "Padding", text_style);
141
142
// Get handle to a sprite to render
143
let vendor_handle: Handle<Image> = asset_server
144
.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png")
145
.unwrap();
146
147
// Configuration array to render sprites through iteration
148
let configurations: [(
149
&str,
150
Handle<TextureAtlasLayout>,
151
TextureAtlasSources,
152
Handle<Image>,
153
f32,
154
); 4] = [
155
(
156
"Linear",
157
atlas_linear_handle,
158
linear_sources,
159
linear_texture,
160
-350.0,
161
),
162
(
163
"Nearest",
164
atlas_nearest_handle,
165
nearest_sources,
166
nearest_texture,
167
-150.0,
168
),
169
(
170
"Linear",
171
atlas_linear_padded_handle,
172
linear_padded_sources,
173
linear_padded_texture,
174
150.0,
175
),
176
(
177
"Nearest",
178
atlas_nearest_padded_handle,
179
nearest_padded_sources,
180
nearest_padded_texture,
181
350.0,
182
),
183
];
184
185
// Label text style
186
let sampling_label_style = TextFont {
187
font,
188
font_size: 25.0,
189
..default()
190
};
191
192
let base_y = 80.0; // y position of the sprites
193
194
for (sampling, atlas_handle, atlas_sources, atlas_texture, x) in configurations {
195
// Render a sprite from the texture_atlas
196
create_sprite_from_atlas(
197
&mut commands,
198
(x, base_y, 0.0),
199
atlas_texture,
200
atlas_sources,
201
atlas_handle,
202
&vendor_handle,
203
);
204
205
// Render a label to indicate the sampling setting
206
create_label(
207
&mut commands,
208
(x, base_y + 110.0, 0.0), // Offset to y position of the sprite
209
sampling,
210
sampling_label_style.clone(),
211
);
212
}
213
}
214
215
/// Create a texture atlas with the given padding and sampling settings
216
/// from the individual sprites in the given folder.
217
fn create_texture_atlas(
218
folder: &LoadedFolder,
219
padding: Option<UVec2>,
220
sampling: Option<ImageSampler>,
221
textures: &mut ResMut<Assets<Image>>,
222
) -> (TextureAtlasLayout, TextureAtlasSources, Handle<Image>) {
223
// Build a texture atlas using the individual sprites
224
let mut texture_atlas_builder = TextureAtlasBuilder::default();
225
texture_atlas_builder.padding(padding.unwrap_or_default());
226
for handle in folder.handles.iter() {
227
let id = handle.id().typed_unchecked::<Image>();
228
let Some(texture) = textures.get(id) else {
229
warn!(
230
"{} did not resolve to an `Image` asset.",
231
handle.path().unwrap()
232
);
233
continue;
234
};
235
236
texture_atlas_builder.add_texture(Some(id), texture);
237
}
238
239
let (texture_atlas_layout, texture_atlas_sources, texture) =
240
texture_atlas_builder.build().unwrap();
241
let texture = textures.add(texture);
242
243
// Update the sampling settings of the texture atlas
244
let image = textures.get_mut(&texture).unwrap();
245
image.sampler = sampling.unwrap_or_default();
246
247
(texture_atlas_layout, texture_atlas_sources, texture)
248
}
249
250
/// Create and spawn a sprite from a texture atlas
251
fn create_sprite_from_atlas(
252
commands: &mut Commands,
253
translation: (f32, f32, f32),
254
atlas_texture: Handle<Image>,
255
atlas_sources: TextureAtlasSources,
256
atlas_handle: Handle<TextureAtlasLayout>,
257
vendor_handle: &Handle<Image>,
258
) {
259
commands.spawn((
260
Transform {
261
translation: Vec3::new(translation.0, translation.1, translation.2),
262
scale: Vec3::splat(3.0),
263
..default()
264
},
265
Sprite::from_atlas_image(
266
atlas_texture,
267
atlas_sources.handle(atlas_handle, vendor_handle).unwrap(),
268
),
269
));
270
}
271
272
/// Create and spawn a label (text)
273
fn create_label(
274
commands: &mut Commands,
275
translation: (f32, f32, f32),
276
text: &str,
277
text_style: TextFont,
278
) {
279
commands.spawn((
280
Text2d::new(text),
281
text_style,
282
TextLayout::new_with_justify(Justify::Center),
283
Transform {
284
translation: Vec3::new(translation.0, translation.1, translation.2),
285
..default()
286
},
287
));
288
}
289
290