动画进阶

概述

学习目标

  • 掌握动画图的创建和使用
  • 理解动画混合的工作原理
  • 学会使用动画事件
  • 了解动画遮罩的使用
  • 掌握缓动函数的使用

前置知识要求

  • 动画基础
  • ECS 基础
  • 3D 开发基础

核心概念

动画图(Animation Graph)

动画图是 Bevy 中用于组织和管理动画的图形结构。它允许你创建复杂的动画混合和过渡。

为什么需要动画图?

  1. 动画混合:可以在多个动画之间进行平滑过渡
  2. 动画组织:可以更好地组织和管理复杂的动画
  3. 性能优化:可以优化动画播放的性能
  4. 灵活性:可以动态调整动画权重

动画事件(Animation Events)

动画事件允许你在动画播放的特定时间点触发自定义事件。

为什么需要动画事件?

  1. 同步:可以在动画播放时同步其他系统
  2. 交互:可以在动画播放时触发交互
  3. 反馈:可以在动画播放时提供反馈
  4. 控制:可以更好地控制动画播放流程

动画遮罩(Animation Masks)

动画遮罩允许你限制动画的作用范围,只对特定的骨骼或目标应用动画。

为什么需要动画遮罩?

  1. 局部动画:可以对特定部位应用动画
  2. 动画组合:可以组合多个局部动画
  3. 性能优化:可以减少不必要的动画计算
  4. 灵活性:可以更灵活地控制动画

缓动函数(Easing Functions)

缓动函数控制动画的加速和减速曲线,使动画更加自然。

为什么需要缓动函数?

  1. 自然感:可以使动画更加自然
  2. 视觉效果:可以增强视觉效果
  3. 用户体验:可以改善用户体验
  4. 艺术表现:可以增强艺术表现力

基础用法

动画图

创建和使用动画图。

源代码文件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 格式
  • 可以从文件加载动画图
  • 序列化可以保存复杂的动画配置
  • 可以编辑序列化的动画图

说明
动画图序列化是保存和加载动画配置的重要功能。通过序列化,可以保存复杂的动画配置,并在需要时加载它们。

实际应用

在游戏开发中的应用场景

动画进阶功能在游戏开发中有广泛的应用:

  1. 角色动画:使用动画混合创建平滑的角色动画过渡
  2. 交互反馈:使用动画事件触发交互反馈
  3. 局部动画:使用动画遮罩创建局部动画效果
  4. UI 动画:使用缓动函数创建自然的 UI 动画

常见问题

问题 1:如何创建平滑的动画过渡?

解决方案:使用动画混合和缓动函数。通过混合多个动画并应用缓动函数,可以创建平滑的动画过渡。

问题 2:如何在动画播放时触发其他系统?

解决方案:使用动画事件。通过定义自定义动画事件并在动画播放时触发它们,可以同步动画和其他系统。

问题 3:如何只对特定部位应用动画?

解决方案:使用动画遮罩。通过创建遮罩组并应用它们,可以限制动画的作用范围。

性能考虑

  1. 动画图优化:尽量减少动画图中的节点数量
  2. 遮罩优化:只对需要的部位应用遮罩
  3. 事件优化:避免在动画事件中执行耗时操作
  4. 缓动函数优化:选择简单的缓动函数以提高性能

相关资源

相关源代码文件

  • 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 模型

索引返回上级目录