动画基础
概述
学习目标:
- 理解 Bevy 动画系统的基本概念
- 掌握动画网格的创建和播放
- 了解动画变换的使用
- 学会使用动画播放器和动画图
前置知识要求:
- Bevy 快速入门
- ECS 基础
- 3D 开发基础
- 资源管理基础
核心概念
什么是动画系统?
动画系统是 Bevy 中用于创建和播放动画的功能。Bevy 的动画系统支持网格动画、变换动画、UI 动画等多种类型的动画。
为什么需要动画系统?
- 视觉效果:动画可以增强游戏的视觉效果
- 用户体验:动画可以改善用户体验
- 游戏性:动画是游戏性的重要组成部分
- 艺术表现:动画可以增强艺术表现力
动画系统的核心组件
Bevy 动画系统包含以下核心组件:
- AnimationClip:动画片段,包含动画数据
- AnimationGraph:动画图,用于组织和管理动画
- AnimationPlayer:动画播放器,用于播放动画
- AnimationTarget:动画目标,用于指定动画作用的对象
基础用法
动画网格
创建和播放动画网格。
源代码文件:bevy/examples/animation/animated_mesh.rs
代码示例:
use bevy::{light::CascadeShadowConfigBuilder, prelude::*, scene::SceneInstanceReady};
// 包含网格和动画的示例资源
const GLTF_PATH: &str = "models/animated/Fox.glb";
fn main() {
App::new()
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 2000.,
..default()
})
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup_mesh_and_animation)
.add_systems(Startup, setup_camera_and_environment)
.run();
}
// 一个存储我们要播放的动画引用的组件。这在我们开始加载网格时创建
// (参见 `setup_mesh_and_animation`),并在网格生成时读取(参见 `play_animation_when_ready`)。
#[derive(Component)]
struct AnimationToPlay {
graph_handle: Handle<AnimationGraph>,
index: AnimationNodeIndex,
}
fn setup_mesh_and_animation(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
// 创建一个包含单个动画的动画图。我们想要示例资源中的"run"动画,其索引为二。
let (graph, index) = AnimationGraph::from_clip(
asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
);
// 将动画图存储为资源。
let graph_handle = graphs.add(graph);
// 创建一个存储我们动画引用的组件。
let animation_to_play = AnimationToPlay {
graph_handle,
index,
};
// 开始将资源作为场景加载,并将其引用存储在 SceneRoot 组件中。
// 此组件将在资源加载后自动生成包含我们网格的场景。
let mesh_scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
// 生成一个包含我们组件的实体,并将其连接到一个观察者,
// 该观察者将在场景加载并生成时触发。
commands
.spawn((animation_to_play, mesh_scene))
.observe(play_animation_when_ready);
}
fn play_animation_when_ready(
scene_ready: On<SceneInstanceReady>,
mut commands: Commands,
children: Query<&Children>,
animations_to_play: Query<&AnimationToPlay>,
mut players: Query<&mut AnimationPlayer>,
) {
// 我们在 `setup_mesh_and_animation` 中生成的实体是触发器的目标。
// 首先找到我们添加到该实体的 AnimationToPlay 组件。
if let Ok(animation_to_play) = animations_to_play.get(scene_ready.entity) {
// SceneRoot 组件将场景生成为我们实体的子实体的层次结构。
// 由于资源包含蒙皮网格和动画,它还会生成一个动画播放器组件。
// 搜索我们实体的后代以找到动画播放器。
for child in children.iter_descendants(scene_ready.entity) {
if let Ok(mut player) = players.get_mut(child) {
// 告诉动画播放器开始动画并保持重复播放。
//
// 如果你想尝试停止和切换动画,请参阅 `animated_mesh_control.rs` 示例。
player.play(animation_to_play.index).repeat();
// 添加动画图。这只需要执行一次即可将动画播放器连接到网格。
commands
.entity(child)
.insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
}
}
}
}关键要点:
- 使用
AnimationGraph::from_clip从动画片段创建动画图 - 使用
AnimationPlayer播放动画 - 使用
AnimationGraphHandle将动画图连接到实体 - 使用
SceneInstanceReady事件在场景加载后播放动画
说明: 动画网格是动画系统的基础。通过加载包含动画的 glTF 文件,可以创建和播放动画网格。
动画变换
创建和播放动画变换。
源代码文件:bevy/examples/animation/animated_transform.rs
代码示例:
use bevy::{
animation::{animated_field, AnimationTarget, AnimationTargetId},
prelude::*,
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(AmbientLight {
color: Color::WHITE,
brightness: 150.0,
..default()
})
.add_systems(Startup, setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut animations: ResMut<Assets<AnimationClip>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
// 相机
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// 光源
commands.spawn((
PointLight {
intensity: 500_000.0,
..default()
},
Transform::from_xyz(0.0, 2.5, 0.0),
));
// 让我们使用 `Name` 组件来定位实体。我们可以使用任何我们喜欢的东西,
// 但名称很方便。
let planet = Name::new("planet");
let orbit_controller = Name::new("orbit_controller");
let satellite = Name::new("satellite");
// 创建动画
let mut animation = AnimationClip::default();
// 曲线可以修改变换的单个部分:这里,平移。
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
animation.add_curve_to_target(
planet_animation_target_id,
AnimatableCurve::new(
animated_field!(Transform::translation),
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
Vec3::new(1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, 1.0),
Vec3::new(-1.0, 0.0, -1.0),
Vec3::new(1.0, 0.0, -1.0),
// 如果需要无缝循环,最后一帧应该与第一帧相同
Vec3::new(1.0, 0.0, 1.0),
]))
.expect("应该能够构建平移曲线,因为我们传入有效的样本"),
),
);
// 或者它可以修改变换的旋转。
// 为了找到要修改的实体,将遍历层次结构,在每个级别查找具有正确名称的实体。
let orbit_controller_animation_target_id =
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
animation.add_curve_to_target(
orbit_controller_animation_target_id,
AnimatableCurve::new(
animated_field!(Transform::rotation),
UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
Quat::IDENTITY,
Quat::from_axis_angle(Vec3::Y, PI / 2.),
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
Quat::IDENTITY,
]))
.expect("无法构建旋转曲线"),
),
);
// 创建动画图
let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation));
// 创建动画播放器,并设置为重复播放
let mut player = AnimationPlayer::default();
player.play(animation_index).repeat();
// 创建将被动画化的场景
// 第一个实体是行星
let planet_entity = commands
.spawn((
Mesh3d(meshes.add(Sphere::default())),
MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
// 添加动画图和播放器
planet,
AnimationGraphHandle(graphs.add(graph)),
player,
))
.id();
commands.entity(planet_entity).insert((
AnimationTarget {
id: planet_animation_target_id,
player: planet_entity,
},
children![(
Transform::default(),
Visibility::default(),
orbit_controller,
AnimationTarget {
id: orbit_controller_animation_target_id,
player: planet_entity,
},
children![(
Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.9, 0.3))),
Transform::from_xyz(1.5, 0.0, 0.0),
AnimationTarget {
id: satellite_animation_target_id,
player: planet_entity,
},
satellite,
)],
)],
));
}关键要点:
- 使用
AnimationClip创建动画片段 - 使用
AnimatableCurve创建动画曲线 - 使用
animated_field!宏指定动画字段 - 使用
AnimationTarget指定动画目标
说明: 动画变换是动画系统的重要功能。通过创建动画曲线,可以动画化实体的变换属性。
进阶用法
动画播放器
使用动画播放器控制动画播放。
关键信息:
- 使用
AnimationPlayer::play()播放动画 - 使用
.repeat()重复播放动画 - 使用
.once()播放一次动画 - 使用
.pause()暂停动画 - 使用
.resume()恢复动画
说明: 动画播放器是动画系统的核心组件。通过使用动画播放器,可以控制动画的播放状态。
实际应用
在游戏开发中的应用场景
动画系统在游戏开发中有广泛的应用:
- 角色动画:创建角色行走、跑步、跳跃等动画
- 物体动画:创建物体移动、旋转、缩放等动画
- UI 动画:创建 UI 元素的动画效果
- 特效动画:创建特效的动画效果
常见问题
问题 1:如何加载动画网格?
解决方案:
- 使用
AssetServer加载 glTF 文件 - 使用
GltfAssetLabel::Animation加载动画 - 使用
GltfAssetLabel::Scene加载场景 - 使用
SceneInstanceReady事件在场景加载后播放动画
问题 2:如何创建动画变换?
解决方案:
- 使用
AnimationClip创建动画片段 - 使用
AnimatableCurve创建动画曲线 - 使用
animated_field!宏指定动画字段 - 使用
AnimationTarget指定动画目标
问题 3:如何控制动画播放?
解决方案:
- 使用
AnimationPlayer::play()播放动画 - 使用
.repeat()重复播放动画 - 使用
.pause()暂停动画 - 使用
.resume()恢复动画
性能考虑
- 动画图:使用动画图组织和管理动画
- 动画目标:使用动画目标指定动画作用的对象
- 动画播放器:合理使用动画播放器控制动画播放
相关资源
相关源代码文件:
bevy/examples/animation/animated_mesh.rs- 动画网格示例bevy/examples/animation/animated_transform.rs- 动画变换示例
官方文档链接:
进一步学习建议:
- 学习动画进阶,了解动画图、动画事件等高级功能
- 学习 UI 动画,了解 UI 动画的创建和使用
索引:返回上级目录