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