动画进阶
概述
学习目标:
- 掌握动画图的创建和使用
- 理解动画混合的工作原理
- 学会使用动画事件
- 了解动画遮罩的使用
- 掌握缓动函数的使用
前置知识要求:
核心概念
动画图(Animation Graph)
动画图是 Bevy 中用于组织和管理动画的图形结构。它允许你创建复杂的动画混合和过渡。
为什么需要动画图?
- 动画混合:可以在多个动画之间进行平滑过渡
- 动画组织:可以更好地组织和管理复杂的动画
- 性能优化:可以优化动画播放的性能
- 灵活性:可以动态调整动画权重
动画事件(Animation Events)
动画事件允许你在动画播放的特定时间点触发自定义事件。
为什么需要动画事件?
- 同步:可以在动画播放时同步其他系统
- 交互:可以在动画播放时触发交互
- 反馈:可以在动画播放时提供反馈
- 控制:可以更好地控制动画播放流程
动画遮罩(Animation Masks)
动画遮罩允许你限制动画的作用范围,只对特定的骨骼或目标应用动画。
为什么需要动画遮罩?
- 局部动画:可以对特定部位应用动画
- 动画组合:可以组合多个局部动画
- 性能优化:可以减少不必要的动画计算
- 灵活性:可以更灵活地控制动画
缓动函数(Easing Functions)
缓动函数控制动画的加速和减速曲线,使动画更加自然。
为什么需要缓动函数?
- 自然感:可以使动画更加自然
- 视觉效果:可以增强视觉效果
- 用户体验:可以改善用户体验
- 艺术表现:可以增强艺术表现力
基础用法
动画图
创建和使用动画图。
源代码文件:bevy/examples/animation/animation_graph.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
| use bevy::prelude::*;
fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .run(); }
fn setup( mut commands: Commands, asset_server: Res<AssetServer>, mut graphs: ResMut<Assets<AnimationGraph>>, ) { let clip_handle = asset_server.load("animations/fox_idle.gltf#Animation0"); let (graph, node_index) = AnimationGraph::from_clip(clip_handle); let graph_handle = graphs.add(graph); let mut player = AnimationPlayer::default(); player.play(node_index).repeat(); commands.spawn(( AnimationGraphHandle(graph_handle), player, )); }
|
关键要点:
- 动画图用于组织和管理动画
- 可以从动画片段创建动画图
- 动画播放器用于播放动画图中的节点
- 可以设置动画的重复模式
说明:
动画图是 Bevy 动画系统的核心。它允许你创建复杂的动画混合和过渡,使动画更加灵活和强大。
动画混合
在多个动画之间进行混合。
源代码文件:bevy/examples/animation/animation_graph.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
| use bevy::prelude::*;
fn setup_animation_graph( mut graphs: ResMut<Assets<AnimationGraph>>, mut clips: ResMut<Assets<AnimationClip>>, asset_server: Res<AssetServer>, ) { let idle_clip = asset_server.load("animations/fox_idle.gltf#Animation0"); let walk_clip = asset_server.load("animations/fox_walk.gltf#Animation0"); let run_clip = asset_server.load("animations/fox_run.gltf#Animation0"); let mut graph = AnimationGraph::default(); let idle_node = graph.add_clip(idle_clip); let walk_node = graph.add_clip(walk_clip); let run_node = graph.add_clip(run_clip); let blend_node = graph.add_blend("Root"); graph.connect(idle_node, blend_node); graph.connect(walk_node, blend_node); graph.connect(run_node, blend_node); graph.set_blend_weight(blend_node, idle_node, 0.5); graph.set_blend_weight(blend_node, walk_node, 0.3); graph.set_blend_weight(blend_node, run_node, 0.2); let graph_handle = graphs.add(graph); }
|
关键要点:
- 可以在多个动画之间进行混合
- 可以设置混合权重来控制混合比例
- 混合节点用于组合多个动画
- 可以动态调整混合权重
说明:
动画混合是创建平滑动画过渡的关键。通过混合多个动画,可以创建更加自然和流畅的动画效果。
动画事件
在动画播放时触发自定义事件。
源代码文件:bevy/examples/animation/animation_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
| use bevy::prelude::*;
#[derive(AnimationEvent, Clone)] struct SetMessage { value: String, color: Color, }
fn setup( mut commands: Commands, mut animations: ResMut<Assets<AnimationClip>>, mut graphs: ResMut<Assets<AnimationGraph>>, ) { let mut animation = AnimationClip::default(); animation.set_duration(2.0); animation.add_event( 0.0, SetMessage { value: "HELLO".into(), color: Color::srgb(0.0, 0.5, 1.0), }, ); animation.add_event( 1.0, SetMessage { value: "BYE".into(), color: Color::srgb(1.0, 0.0, 0.0), }, ); let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation)); let mut player = AnimationPlayer::default(); player.play(animation_index).repeat(); commands.spawn(( AnimationGraphHandle(graphs.add(graph)), player, )); }
fn on_set_message( set_message: On<SetMessage>, text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>, ) { let (mut text, mut color) = text.into_inner(); text.0 = set_message.value.clone(); color.0 = set_message.color; }
|
关键要点:
- 动画事件可以在动画播放的特定时间点触发
- 需要实现
AnimationEvent trait
- 可以使用
On<T> 观察者来监听动画事件
- 事件可以携带自定义数据
说明:
动画事件是同步动画和其他系统的重要机制。通过动画事件,可以在动画播放时触发自定义逻辑,使动画更加交互和动态。
动画遮罩
限制动画的作用范围。
源代码文件:bevy/examples/animation/animation_masks.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
| use bevy::prelude::*; use std::collections::HashSet;
const MASK_GROUP_HEAD: u32 = 0; const MASK_GROUP_LEFT_ARM: u32 = 1; const MASK_GROUP_RIGHT_ARM: u32 = 2;
fn setup_animation_with_mask( mut commands: Commands, mut graphs: ResMut<Assets<AnimationGraph>>, asset_server: Res<AssetServer>, ) { let clip_handle = asset_server.load("animations/character_walk.gltf#Animation0"); let (graph, node_index) = AnimationGraph::from_clip(clip_handle); let mut mask_group = HashSet::new(); mask_group.insert(AnimationTargetId::from_name(&Name::new("Head"))); mask_group.insert(AnimationTargetId::from_name(&Name::new("Neck"))); graph.set_mask(node_index, MASK_GROUP_HEAD, mask_group); let graph_handle = graphs.add(graph); let mut player = AnimationPlayer::default(); player.play(node_index).repeat(); commands.spawn(( AnimationGraphHandle(graph_handle), player, )); }
|
关键要点:
- 动画遮罩可以限制动画的作用范围
- 可以创建多个遮罩组
- 遮罩组由动画目标 ID 集合定义
- 可以动态启用或禁用遮罩组
说明:
动画遮罩是创建局部动画的重要工具。通过遮罩,可以对特定部位应用动画,而不会影响其他部位,使动画更加灵活和精细。
缓动函数
使用缓动函数控制动画曲线。
源代码文件:bevy/examples/animation/easing_functions.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
| use bevy::prelude::*;
fn setup_animation_with_easing( mut commands: Commands, mut animations: ResMut<Assets<AnimationClip>>, mut graphs: ResMut<Assets<AnimationGraph>>, ) { let mut animation = AnimationClip::default(); let ease_function = EaseFunction::CubicInOut; animation.add_curve_to_target( target_id, AnimatableCurve::new( animated_field!(Transform::translation), AnimatableKeyframeCurve::new( [0.0, 1.0, 2.0].into_iter().zip([ Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0), Vec3::ZERO, ]), ) .with_easing(ease_function) .expect("valid curve"), ), ); let (graph, node_index) = AnimationGraph::from_clip(animations.add(animation)); let mut player = AnimationPlayer::default(); player.play(node_index).repeat(); commands.spawn(( AnimationGraphHandle(graphs.add(graph)), player, )); }
|
关键要点:
- 缓动函数控制动画的加速和减速曲线
- Bevy 提供了多种内置缓动函数
- 可以在动画曲线上应用缓动函数
- 缓动函数使动画更加自然
说明:
缓动函数是创建自然动画的关键。通过使用不同的缓动函数,可以创建各种动画效果,从线性到弹性,从平滑到弹跳。
进阶用法
动态调整动画权重
在运行时动态调整动画混合权重。
源代码文件:bevy/examples/animation/animation_graph.rs
关键信息:
- 可以在运行时动态调整混合权重
- 权重调整会平滑过渡
- 可以基于游戏状态调整权重
- 权重总和应该为 1.0
说明:
动态调整动画权重是创建响应式动画的关键。通过根据游戏状态调整权重,可以创建更加动态和交互的动画效果。
动画图序列化
将动画图序列化为文件。
源代码文件:bevy/examples/animation/animation_graph.rs
关键信息:
- 动画图可以序列化为 RON 格式
- 可以从文件加载动画图
- 序列化可以保存复杂的动画配置
- 可以编辑序列化的动画图
说明:
动画图序列化是保存和加载动画配置的重要功能。通过序列化,可以保存复杂的动画配置,并在需要时加载它们。
实际应用
在游戏开发中的应用场景
动画进阶功能在游戏开发中有广泛的应用:
- 角色动画:使用动画混合创建平滑的角色动画过渡
- 交互反馈:使用动画事件触发交互反馈
- 局部动画:使用动画遮罩创建局部动画效果
- UI 动画:使用缓动函数创建自然的 UI 动画
常见问题
问题 1:如何创建平滑的动画过渡?
解决方案:使用动画混合和缓动函数。通过混合多个动画并应用缓动函数,可以创建平滑的动画过渡。
问题 2:如何在动画播放时触发其他系统?
解决方案:使用动画事件。通过定义自定义动画事件并在动画播放时触发它们,可以同步动画和其他系统。
问题 3:如何只对特定部位应用动画?
解决方案:使用动画遮罩。通过创建遮罩组并应用它们,可以限制动画的作用范围。
性能考虑
- 动画图优化:尽量减少动画图中的节点数量
- 遮罩优化:只对需要的部位应用遮罩
- 事件优化:避免在动画事件中执行耗时操作
- 缓动函数优化:选择简单的缓动函数以提高性能
相关资源
相关源代码文件:
bevy/examples/animation/animation_graph.rs - 动画图示例
bevy/examples/animation/animation_events.rs - 动画事件示例
bevy/examples/animation/animation_masks.rs - 动画遮罩示例
bevy/examples/animation/easing_functions.rs - 缓动函数示例
官方文档链接:
进一步学习建议:
- 学习 UI 动画,了解如何对 UI 元素应用动画
- 学习变形目标,了解如何使用变形目标动画
- 学习 3D 图形,了解如何将动画应用于 3D 模型
索引:返回上级目录