Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/sprite_scale.rs
6592 views
1
//! Shows how to use sprite scaling to fill and fit textures into the sprite.
2
3
use bevy::prelude::*;
4
5
fn main() {
6
App::new()
7
.add_plugins(DefaultPlugins)
8
.add_systems(Startup, (setup_sprites, setup_texture_atlas, setup_camera))
9
.add_systems(Update, animate_sprite)
10
.run();
11
}
12
13
fn setup_camera(mut commands: Commands) {
14
commands.spawn(Camera2d);
15
}
16
17
fn setup_sprites(mut commands: Commands, asset_server: Res<AssetServer>) {
18
let square = asset_server.load("textures/slice_square_2.png");
19
let banner = asset_server.load("branding/banner.png");
20
21
let rects = [
22
Rect {
23
size: Vec2::new(100., 225.),
24
text: "Stretched".to_string(),
25
transform: Transform::from_translation(Vec3::new(-570., 230., 0.)),
26
texture: square.clone(),
27
image_mode: SpriteImageMode::Auto,
28
},
29
Rect {
30
size: Vec2::new(100., 225.),
31
text: "Fill Center".to_string(),
32
transform: Transform::from_translation(Vec3::new(-450., 230., 0.)),
33
texture: square.clone(),
34
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
35
},
36
Rect {
37
size: Vec2::new(100., 225.),
38
text: "Fill Start".to_string(),
39
transform: Transform::from_translation(Vec3::new(-330., 230., 0.)),
40
texture: square.clone(),
41
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
42
},
43
Rect {
44
size: Vec2::new(100., 225.),
45
text: "Fill End".to_string(),
46
transform: Transform::from_translation(Vec3::new(-210., 230., 0.)),
47
texture: square.clone(),
48
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
49
},
50
Rect {
51
size: Vec2::new(300., 100.),
52
text: "Fill Start Horizontal".to_string(),
53
transform: Transform::from_translation(Vec3::new(10., 290., 0.)),
54
texture: square.clone(),
55
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
56
},
57
Rect {
58
size: Vec2::new(300., 100.),
59
text: "Fill End Horizontal".to_string(),
60
transform: Transform::from_translation(Vec3::new(10., 155., 0.)),
61
texture: square.clone(),
62
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
63
},
64
Rect {
65
size: Vec2::new(200., 200.),
66
text: "Fill Center".to_string(),
67
transform: Transform::from_translation(Vec3::new(280., 230., 0.)),
68
texture: banner.clone(),
69
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
70
},
71
Rect {
72
size: Vec2::new(200., 100.),
73
text: "Fill Center".to_string(),
74
transform: Transform::from_translation(Vec3::new(500., 230., 0.)),
75
texture: square.clone(),
76
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
77
},
78
Rect {
79
size: Vec2::new(100., 100.),
80
text: "Stretched".to_string(),
81
transform: Transform::from_translation(Vec3::new(-570., -40., 0.)),
82
texture: banner.clone(),
83
image_mode: SpriteImageMode::Auto,
84
},
85
Rect {
86
size: Vec2::new(200., 200.),
87
text: "Fit Center".to_string(),
88
transform: Transform::from_translation(Vec3::new(-400., -40., 0.)),
89
texture: banner.clone(),
90
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
91
},
92
Rect {
93
size: Vec2::new(200., 200.),
94
text: "Fit Start".to_string(),
95
transform: Transform::from_translation(Vec3::new(-180., -40., 0.)),
96
texture: banner.clone(),
97
image_mode: SpriteImageMode::Scale(ScalingMode::FitStart),
98
},
99
Rect {
100
size: Vec2::new(200., 200.),
101
text: "Fit End".to_string(),
102
transform: Transform::from_translation(Vec3::new(40., -40., 0.)),
103
texture: banner.clone(),
104
image_mode: SpriteImageMode::Scale(ScalingMode::FitEnd),
105
},
106
Rect {
107
size: Vec2::new(100., 200.),
108
text: "Fit Center".to_string(),
109
transform: Transform::from_translation(Vec3::new(210., -40., 0.)),
110
texture: banner.clone(),
111
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
112
},
113
];
114
115
for rect in rects {
116
commands.spawn((
117
Sprite {
118
image: rect.texture,
119
custom_size: Some(rect.size),
120
image_mode: rect.image_mode,
121
..default()
122
},
123
rect.transform,
124
children![(
125
Text2d::new(rect.text),
126
TextLayout::new_with_justify(Justify::Center),
127
TextFont::from_font_size(15.),
128
Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.),
129
bevy::sprite::Anchor::TOP_CENTER,
130
)],
131
));
132
}
133
}
134
135
fn setup_texture_atlas(
136
mut commands: Commands,
137
asset_server: Res<AssetServer>,
138
mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
139
) {
140
let gabe = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
141
let animation_indices_gabe = AnimationIndices { first: 0, last: 6 };
142
let gabe_atlas = TextureAtlas {
143
layout: texture_atlas_layouts.add(TextureAtlasLayout::from_grid(
144
UVec2::splat(24),
145
7,
146
1,
147
None,
148
None,
149
)),
150
index: animation_indices_gabe.first,
151
};
152
153
let sprite_sheets = [
154
SpriteSheet {
155
size: Vec2::new(120., 50.),
156
text: "Stretched".to_string(),
157
transform: Transform::from_translation(Vec3::new(-570., -200., 0.)),
158
texture: gabe.clone(),
159
image_mode: SpriteImageMode::Auto,
160
atlas: gabe_atlas.clone(),
161
indices: animation_indices_gabe.clone(),
162
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
163
},
164
SpriteSheet {
165
size: Vec2::new(120., 50.),
166
text: "Fill Center".to_string(),
167
transform: Transform::from_translation(Vec3::new(-570., -300., 0.)),
168
texture: gabe.clone(),
169
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
170
atlas: gabe_atlas.clone(),
171
indices: animation_indices_gabe.clone(),
172
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
173
},
174
SpriteSheet {
175
size: Vec2::new(120., 50.),
176
text: "Fill Start".to_string(),
177
transform: Transform::from_translation(Vec3::new(-430., -200., 0.)),
178
texture: gabe.clone(),
179
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
180
atlas: gabe_atlas.clone(),
181
indices: animation_indices_gabe.clone(),
182
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
183
},
184
SpriteSheet {
185
size: Vec2::new(120., 50.),
186
text: "Fill End".to_string(),
187
transform: Transform::from_translation(Vec3::new(-430., -300., 0.)),
188
texture: gabe.clone(),
189
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
190
atlas: gabe_atlas.clone(),
191
indices: animation_indices_gabe.clone(),
192
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
193
},
194
SpriteSheet {
195
size: Vec2::new(50., 120.),
196
text: "Fill Center".to_string(),
197
transform: Transform::from_translation(Vec3::new(-300., -250., 0.)),
198
texture: gabe.clone(),
199
image_mode: SpriteImageMode::Scale(ScalingMode::FillCenter),
200
atlas: gabe_atlas.clone(),
201
indices: animation_indices_gabe.clone(),
202
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
203
},
204
SpriteSheet {
205
size: Vec2::new(50., 120.),
206
text: "Fill Start".to_string(),
207
transform: Transform::from_translation(Vec3::new(-190., -250., 0.)),
208
texture: gabe.clone(),
209
image_mode: SpriteImageMode::Scale(ScalingMode::FillStart),
210
atlas: gabe_atlas.clone(),
211
indices: animation_indices_gabe.clone(),
212
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
213
},
214
SpriteSheet {
215
size: Vec2::new(50., 120.),
216
text: "Fill End".to_string(),
217
transform: Transform::from_translation(Vec3::new(-90., -250., 0.)),
218
texture: gabe.clone(),
219
image_mode: SpriteImageMode::Scale(ScalingMode::FillEnd),
220
atlas: gabe_atlas.clone(),
221
indices: animation_indices_gabe.clone(),
222
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
223
},
224
SpriteSheet {
225
size: Vec2::new(120., 50.),
226
text: "Fit Center".to_string(),
227
transform: Transform::from_translation(Vec3::new(20., -200., 0.)),
228
texture: gabe.clone(),
229
image_mode: SpriteImageMode::Scale(ScalingMode::FitCenter),
230
atlas: gabe_atlas.clone(),
231
indices: animation_indices_gabe.clone(),
232
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
233
},
234
SpriteSheet {
235
size: Vec2::new(120., 50.),
236
text: "Fit Start".to_string(),
237
transform: Transform::from_translation(Vec3::new(20., -300., 0.)),
238
texture: gabe.clone(),
239
image_mode: SpriteImageMode::Scale(ScalingMode::FitStart),
240
atlas: gabe_atlas.clone(),
241
indices: animation_indices_gabe.clone(),
242
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
243
},
244
SpriteSheet {
245
size: Vec2::new(120., 50.),
246
text: "Fit End".to_string(),
247
transform: Transform::from_translation(Vec3::new(160., -200., 0.)),
248
texture: gabe.clone(),
249
image_mode: SpriteImageMode::Scale(ScalingMode::FitEnd),
250
atlas: gabe_atlas.clone(),
251
indices: animation_indices_gabe.clone(),
252
timer: AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
253
},
254
];
255
256
for sprite_sheet in sprite_sheets {
257
commands.spawn((
258
Sprite {
259
image_mode: sprite_sheet.image_mode,
260
custom_size: Some(sprite_sheet.size),
261
..Sprite::from_atlas_image(sprite_sheet.texture.clone(), sprite_sheet.atlas.clone())
262
},
263
sprite_sheet.indices,
264
sprite_sheet.timer,
265
sprite_sheet.transform,
266
children![(
267
Text2d::new(sprite_sheet.text),
268
TextLayout::new_with_justify(Justify::Center),
269
TextFont::from_font_size(15.),
270
Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.),
271
bevy::sprite::Anchor::TOP_CENTER,
272
)],
273
));
274
}
275
}
276
277
struct Rect {
278
size: Vec2,
279
text: String,
280
transform: Transform,
281
texture: Handle<Image>,
282
image_mode: SpriteImageMode,
283
}
284
285
struct SpriteSheet {
286
size: Vec2,
287
text: String,
288
transform: Transform,
289
texture: Handle<Image>,
290
image_mode: SpriteImageMode,
291
atlas: TextureAtlas,
292
indices: AnimationIndices,
293
timer: AnimationTimer,
294
}
295
296
#[derive(Component, Clone)]
297
struct AnimationIndices {
298
first: usize,
299
last: usize,
300
}
301
302
#[derive(Component, Deref, DerefMut)]
303
struct AnimationTimer(Timer);
304
305
fn animate_sprite(
306
time: Res<Time>,
307
mut query: Query<(&AnimationIndices, &mut AnimationTimer, &mut Sprite)>,
308
) {
309
for (indices, mut timer, mut sprite) in &mut query {
310
timer.tick(time.delta());
311
312
if timer.just_finished()
313
&& let Some(atlas) = &mut sprite.texture_atlas
314
{
315
atlas.index = if atlas.index == indices.last {
316
indices.first
317
} else {
318
atlas.index + 1
319
};
320
}
321
}
322
}
323
324