动画事件
动画不只是视觉变化——角色的脚步声、攻击判定帧、特效触发点,都需要在动画的特定时刻执行逻辑。Bevy 的动画事件机制就是为此设计的。
定义动画事件
动画事件是一个实现了 AnimationEvent trait 的类型。最简单的方式是 derive:
rust
#[derive(AnimationEvent, Clone)]
struct Footstep {
foot: String,
}AnimationEvent 要求类型同时满足 Clone + Event,derive 宏自动处理这些约束。
注册事件到时间线
用 AnimationClip::add_event(time, event) 把事件绑定到动画的某个时间点:
rust
clip.add_event(0.0, Footstep { foot: "left".into() });
clip.add_event(2.0, Footstep { foot: "right".into() });当动画播放到指定时间时,Bevy 会自动触发该事件。事件触发时,AnimationEventTrigger 会提供触发事件的实体信息。
监听事件
用 Bevy 的 Observer 机制监听动画事件:
rust
fn on_footstep(event: On<Footstep>) {
info!("Footstep: {}", event.foot);
}
App::new()
.add_observer(on_footstep)
.run();完整的播放与事件触发示例:
rust
use bevy::{
animation::{
animated_field, animation_curves::AnimatableCurve, AnimatedBy, AnimationEvent,
AnimationTargetId,
},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_observer(on_footstep)
.run();
}
#[derive(AnimationEvent, Clone)]
struct Footstep {
foot: String,
}
fn on_footstep(event: On<Footstep>) {
info!("Footstep: {}", event.foot);
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut clips: ResMut<Assets<AnimationClip>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 3.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
));
commands.spawn((
PointLight {
intensity: 500_000.0,
..default()
},
Transform::from_xyz(2.0, 4.0, 2.0),
));
let target_name = Name::new("walker");
let target_id = AnimationTargetId::from_name(&target_name);
let mut clip = AnimationClip::default();
clip.add_curve_to_target(
target_id,
AnimatableCurve::new(
animated_field!(Transform::translation),
AnimatableKeyframeCurve::new([
(0.0, Vec3::new(-2.0, 0.0, 0.0)),
(1.0, Vec3::new(0.0, 0.3, 0.0)),
(2.0, Vec3::new(2.0, 0.0, 0.0)),
(3.0, Vec3::new(0.0, 0.3, 0.0)),
(4.0, Vec3::new(-2.0, 0.0, 0.0)),
])
.unwrap(),
),
);
clip.add_event(
0.0,
Footstep {
foot: "left".into(),
},
);
clip.add_event(
2.0,
Footstep {
foot: "right".into(),
},
);
let (graph, node_index) = AnimationGraph::from_clip(clips.add(clip));
let mut player = AnimationPlayer::default();
player.play(node_index).repeat();
let entity = commands
.spawn((
Mesh3d(meshes.add(Cuboid::new(0.6, 1.0, 0.6))),
MeshMaterial3d(materials.add(Color::srgb(0.8, 0.5, 0.2))),
target_name,
AnimationGraphHandle(graphs.add(graph)),
player,
))
.id();
commands
.entity(entity)
.insert((target_id, AnimatedBy(entity)));
}Listing 30-4:动画事件——在指定时间触发脚步声(examples/listing-30-04.rs)
console
cargo run -p ch30-animation --example listing-30-04控制台每隔 2 秒打印一次脚步信息,交替输出 "left" 和 "right"。
事件的作用域
add_event 触发在 AnimationPlayer 实体上。如果你需要事件触发在特定的骨骼实体上(比如让脚部骨骼触发脚步声),可以用 add_event_to_target(target_id, time, event),它会在匹配 AnimationTargetId 的实体上触发事件。
事件的时机
动画事件在动画系统的 PostUpdate 阶段触发。如果动画被暂停,事件不会触发。如果动画循环播放,每轮都会在相同时间点触发事件。如果动画被 seek 跳过,seek_to 方法会确保跳过的事件在下一帧被触发,而 set_seek_time 则不会。