-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathevents.rs
293 lines (243 loc) · 8.69 KB
/
events.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// This example shows how to react to animations reaching points of interest with events.
//
// - We'll create a few animations for our character (idle, run, shoot) in a setup system
//
// - We'll add markers on interesting frames of our animations:
// - when a character's foot touches the ground
// - when the character shoots their gun
//
// - We'll setup a UI that shows all the animation events that exist.
// Events received at each update will be highlighted.
//
// - We'll spawn special effects when a marker is hit:
// - A bullet when the character shoots their gun
// - A shockwave when their feet hit the ground
#[path = "./common/mod.rs"]
pub mod common;
use std::collections::HashSet;
use bevy::{
color::palettes::css::{DEEP_PINK, GRAY, YELLOW},
prelude::*,
};
use bevy_spritesheet_animation::prelude::*;
fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(ImagePlugin::default_nearest()),
SpritesheetAnimationPlugin::default(),
))
.add_systems(Startup, (spawn_character, create_ui))
.add_systems(
Update,
(
show_triggered_events,
spawn_visual_effects,
animate_bullets,
animate_footsteps,
),
)
.run();
}
fn spawn_character(
mut commands: Commands,
mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
mut library: ResMut<AnimationLibrary>,
assets: Res<AssetServer>,
) {
commands.spawn(Camera2d);
// Create a running clip
let spritesheet = Spritesheet::new(8, 8);
let foot_touches_ground_marker = library.new_marker();
library
.name_marker(foot_touches_ground_marker, "foot touches ground")
.unwrap();
let run_clip = Clip::from_frames(spritesheet.row(3))
.with_repetitions(4)
// The character's foot touches the ground on frame 1...
.with_marker(foot_touches_ground_marker, 1)
// ... and then again on frame 5
.with_marker(foot_touches_ground_marker, 5);
let run_clip_id = library.register_clip(run_clip);
// Create a shooting clip
let bullet_out_marker = library.new_marker();
library
.name_marker(bullet_out_marker, "bullet goes out")
.unwrap();
let shoot_clip = Clip::from_frames(spritesheet.horizontal_strip(0, 5, 5))
// The character shoots their gun on frame 1
.with_marker(bullet_out_marker, 1);
let shoot_clip_id = library.register_clip(shoot_clip);
// Create the final animation
let animation = Animation::from_clips([run_clip_id, shoot_clip_id]);
let animation_id = library.register_animation(animation);
// Spawn a sprite using the animation
let image = assets.load("character.png");
let atlas = TextureAtlas {
layout: atlas_layouts.add(Spritesheet::new(8, 8).atlas_layout(96, 96)),
..default()
};
commands.spawn((
Sprite::from_atlas_image(image, atlas),
SpritesheetAnimation::from_id(animation_id),
));
}
fn create_ui(mut commands: Commands) {
commands
// Full-screen container
.spawn(Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
..default()
})
.with_children(|parent| {
let mut add_event = |event_type: EventType| {
parent
// Row
.spawn(Node {
margin: UiRect::all(Val::Px(10.0)),
align_items: AlignItems::Center,
..default()
})
.with_children(|parent| {
// Colored square
parent.spawn((
Node {
width: Val::Px(50.0),
height: Val::Px(50.0),
margin: UiRect::right(Val::Px(10.0)),
..default()
},
event_type,
));
// Event name
parent.spawn((
Text(format!("{event_type:?}")),
TextFont::from_font_size(30.0),
Label,
));
});
};
add_event(EventType::MarkerHit);
add_event(EventType::ClipRepetitionEnd);
add_event(EventType::ClipEnd);
add_event(EventType::RepetitionEnd);
add_event(EventType::End);
});
}
// Component attached to a UI square to be highlighted when the given event type is received
#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)]
enum EventType {
MarkerHit,
ClipRepetitionEnd,
ClipEnd,
RepetitionEnd,
End,
}
fn show_triggered_events(
mut events: EventReader<AnimationEvent>,
mut squares: Query<(&mut BackgroundColor, &EventType)>,
) {
// Collect the events that were just received
let mut triggered_events: HashSet<EventType> = HashSet::new();
for event in events.read() {
match event {
AnimationEvent::MarkerHit { .. } => {
triggered_events.insert(EventType::MarkerHit);
}
AnimationEvent::ClipRepetitionEnd { .. } => {
triggered_events.insert(EventType::ClipRepetitionEnd);
}
AnimationEvent::ClipEnd { .. } => {
triggered_events.insert(EventType::ClipEnd);
}
AnimationEvent::AnimationRepetitionEnd { .. } => {
triggered_events.insert(EventType::RepetitionEnd);
}
AnimationEvent::AnimationEnd { .. } => {
triggered_events.insert(EventType::End);
}
}
}
// Color the squares for the events that were just received
for (mut color, event_type) in &mut squares {
if triggered_events.contains(event_type) {
color.0 = Color::from(DEEP_PINK);
} else {
color.0 = Color::from(GRAY);
}
}
}
// Spawns footsteps & bullets when the marked frames are played
fn spawn_visual_effects(
mut commands: Commands,
library: Res<AnimationLibrary>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut events: EventReader<AnimationEvent>,
) {
for event in events.read() {
match event {
AnimationEvent::MarkerHit { marker_id, .. } => {
// Spawn a shockwave at each footstep
if library.is_marker_name(*marker_id, "foot touches ground") {
commands.spawn((
Mesh2d(meshes.add(Circle { radius: 1.0 })),
MeshMaterial2d(materials.add(ColorMaterial::default())),
Transform::from_xyz(0.0, -30.0, -1.0),
Footstep,
));
}
// Spawn a bullet when firing
if library.is_marker_name(*marker_id, "bullet goes out") {
commands.spawn((
Mesh2d(meshes.add(Circle { radius: 3.0 })),
MeshMaterial2d(materials.add(Color::from(YELLOW))),
Transform::from_xyz(20.0, 15.0, 0.0),
Bullet,
));
}
}
_ => (),
}
}
}
#[derive(Component)]
struct Bullet;
fn animate_bullets(
time: Res<Time>,
mut commands: Commands,
mut bullets: Query<(Entity, &mut Transform), With<Bullet>>,
) {
for (entity, mut transform) in &mut bullets {
// Move horizontally
transform.translation.x += time.delta_secs() * 400.0;
// Despawn when far away
if transform.translation.x > 5000.0 {
commands.entity(entity).despawn();
}
}
}
#[derive(Component)]
struct Footstep;
fn animate_footsteps(
time: Res<Time>,
mut commands: Commands,
mut materials: ResMut<Assets<ColorMaterial>>,
mut footsteps: Query<(Entity, &mut Transform, &MeshMaterial2d<ColorMaterial>), With<Footstep>>,
) {
for (entity, mut transform, material_handle) in &mut footsteps {
// Grow
transform.scale += time.delta_secs() * Vec3::splat(100.0);
// Fade away
if let Some(material) = materials.get_mut(material_handle) {
material
.color
.set_alpha(material.color.alpha() - time.delta_secs() * 4.0);
// Despawn when transparent
if material.color.alpha() <= 0.0 {
commands.entity(entity).despawn();
}
}
}
}