动画进阶

概述

学习目标

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

前置知识要求

  • 动画基础
  • 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

代码示例

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

代码示例

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

代码示例

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

代码示例

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

代码示例

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

索引返回上级目录