1use bevy::prelude::*;
4
5fn 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
13fn setup_camera(mut commands: Commands) {
14 commands.spawn(Camera2d);
15}
16
17fn 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
135fn 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
277struct Rect {
278 size: Vec2,
279 text: String,
280 transform: Transform,
281 texture: Handle<Image>,
282 image_mode: SpriteImageMode,
283}
284
285struct 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)]
297struct AnimationIndices {
298 first: usize,
299 last: usize,
300}
301
302#[derive(Component, Deref, DerefMut)]
303struct AnimationTimer(Timer);
304
305fn 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}