本文内容
第25章 介绍动画
第25章 介绍动画
EMotionFX 简介
在我们的游戏开发阶段,鸡卡在了空闲动画中。我们要进行的下一个改进是在鸡移动时播放行走动画,在鸡不移动时播放空闲动画。
O3DE 附带一个动画 Gem EMotionFX。在本章中,我将向您展示如何使用 EMotionFX 构建状态机,以混合空闲和行走动画。
注意:本章的关卡和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch25_animation
Simple Motion Component
如果不需要混合动画或其他复杂的动画交互,可以使用可以播放一个动画的 Simple Motion 组件。
例 25.1.Simple Motion 组件
此组件具有各种配置选项,但我们将将其从实体中删除Chicken_Actor并将其替换为 Anim Graph 组件。
注意:Simple Motion (简单运动) 和 Animation Graph (动画图形) 组件都需要 Actor (角色) 组件。Chicken_Actor实体已经从 C:\O3DE\21.11.2\Gems\NvCloth\Assets\Objects\cloth\Chicken\Actor\chicken.fbx 分配了一个实体。角色组件为动画提供角色网格和骨骼信息。
Anim Graph Component
Animation Graph 组件将允许我们将动画图形分配给 actor,并允许我们使用我们选择的参数来控制动画。
首次添加此组件时,其字段为空。首先,我们必须创建一个 Anim graph 资产。通过主菜单 Tools → Animation Editor,或者通过单击组件上 Anim graph 属性旁边的最右侧图标打开 Animation Editor。
构建动画图
提示:Animation Editor 应该打开一个空工作区。如果没有,请使用 File → New Workspace 重置它。
动画图是定义动画及其交互的逻辑和行为的各种节点的集合,例如将多个动画混合在一起。在高层次上,我们将构建一个状态机,它混合了 chickenidle.fbx 的空闲运动和 chickenwalking.fbx 的行走运动。这两个文件都附带了 O3DE 安装中的 NvCloth gem,位于 C:\O3DE \21.11.2\Gems\NvCloth\Assets\Objects\cloth\Chicken\Motions。
指定预览角色
为了在处理动画图形时提供视觉指导,请将角色加载到 Animation Editor 中。
- 转到 Actor Manager 选项卡。(如果它未打开,请使用主菜单:View → Actor Manager。
- 单击文件夹图标。
- 选择 Load actor
- 从 NvCloth Gem 的位置中选择 chicken.actor。这会将鸡 actor 加载到 OpenGL Render Window 中。它将作为动画图形的试验场。
测试动作
在我们花时间构建图形之前,请测试该运动是否适用于鸡。
- 转到“运动”选项卡,查看→运动。
- 点击加号“+”图标。
- Pick Emotion FX Motion 对话框将打开,选择 chickenidle.motion。
在动作列表中,双击 chickenidle 以测试动作。
重复 chickenwalking.motion 的步骤。
这将在 OpenGL Render Window 中播放动画,以便您查看动画的外观以及它们是否与角色匹配。
创建运动集
在开始构建动画图形之前,我们需要创建一个运动集来保存我们将要使用的运动的集合。
- 转到 Motion Sets 选项卡,如果未打开,请使用 View → Motions Sets。
- 点击加号“+”图标,创建新的动作集 MotionSet0。
- 在“运动集”部分,有文件夹图标,单击它并选择 chickenidle.motion
提示:您可以双击 chickenidle 动作,在渲染窗口中对其进行测试
- 重复 chickenwalking.motion 的步骤。
- 使用软盘图标保存动作集。例如,我将其保存到 C:\git\book\Gems\MyGem\Assets\Animation\chicken.motionset。
Anim Graph
我们已准备好开始构建一个动画图形,该图形将混合行走和空闲运动。
- 转到 Anim Graph 选项卡,如果未打开,请使用 View → Anim Graph。
- 单击加号“+”图标以创建新的空 Animgraph。
- 在 Anim Graph 的空白工作空间中,右键单击并选择 Create Node → Sources → BlendTree。
提示:您还可以从 Anim Graph Palette 的 Sources 组中拖放 BlendTree 节点。
- 双击 BlendTree0 节点以查看其配置。默认情况下,混合树只有一个 final 节点,没有其他节点。此节点将接收两个运动的混合。
提示:您可以在 Attributes (属性) 面板中重命名节点名称。
- 右键单击 BlendTwo,将 Blend Two 节点添加到 FinalNode0 的左侧→→ BlendTwo → Blending 。此节点会将两个运动混合在一起,并将结果分配到 FinalNode0 中。
- 在 BlendTwo0 的左侧,创建两个 Motion 节点,分别→ Sources → Motion。
- 选择 Motion0 节点。
- 转到“属性”面板。
- 在 Motions 属性的右侧,单击加号“+”按钮。
- Motion Selection 窗口打开,从 MotionSet0 中选择 chickenidle motion。
- 对于 Motion1 节点,以相同的方式选择 chickenwalking motion。
- 在 Motion 节点上,有 Output Pose 输出。从 Output Pose to Pose 1 input on BlendTwo0 (BlendTwo0) 节点上的 Output Pose to Pose 1 input (输出姿势到姿势 1) 输入旁边的方块中拖动一条线。
- 将 BlendTwo0 节点的 Outpost 姿势与 FinalNode0 的 Input Pose 连接起来。动画图形应开始播放。如果没有,Anim Graph 的工具栏中有一个白色箭头播放按钮。
动画混合
图 25.1.两个动画的第一次混合
让我们来看看发生了什么。我们创建了一个 blend 节点。在节点内部,我们声明了两个运动源,这两个源与 Blend Two 节点混合在一起,该节点将结果输出到最终节点。目前,唯一实际播放的动作是空闲动作。这是因为 BlendTwo0 节点的 Weight input(权重输入)决定了两个运动之间的混合量。
由于我们在空闲和行走动作之间进行混合,因此根据鸡的速度进行混合是有意义的。如果速度为零,则应播放 idle motion。当小鸡开始移动时,动画图形应在两个运动之间混合,直到仅播放行走运动。
参数
为了控制混合,我们将添加一个可以从动画图形外部指定的参数。
有一个 C++ API 可以从组件执行此作,但在开始之前,我们将创建一个参数并在 Animation Editor 中对其进行测试。
- 转到 Parameters 窗口,使用 View → Parameter Window(如果它未打开)。
- 点击加号“+”图标。
- 选择 Add parameter(添加参数)。
- 对于 Name(名称),输入 Speed(速度)。
- 对于“最大值”,输入 6 (6.0),因为在第 18 章“角色移动”中,鸡的最大速度设置为 6。
- 单击“创建”以创建参数。
速度参数 7. 在 Anim graph space(动画图形空间)中,选择 Create Node → Sources → Parameters(创建节点参数)。 8. 选择 Parameters0 节点后,转到“属性”面板,然后单击“选择参数”。 9. 选择 Speed 参数。
注意:我们的速度参数范围设置为 0 到 6 之间。但 BlendTwo0 的 Weight input (权重) 输入值的范围是从 0 到 1。我们必须使用 Range Remapper 节点重新映射范围以匹配 Weight 要求。
- Create Node → Math → Smoothing.
11. Create Node → Math → Range Remapper. 12. 选择 RangeRemapper0 节点后,转到“属性”面板,将“输入最大值”更改为六 (6) 以匹配“速度”参数的范围。 13. 将 Parameters0 连接到 Smoothing0,将 Smoothing0 连接到 RangeRemapper0,将 RangeRemapper0 连接到 BlendTwo0 的权重输入。提示:Smoothing 节点是可选的,但它允许我们平滑地提高参数的值,从而随着时间的推移在运动之间混合,而无需在 C++ 组件中执行此工作。但是,如果您需要精确控制动画输出,则必须将 Speed 参数驱动 自我。
- 现在,您可以通过更改 Speed 参数值来测试动画图形。
- 将动画图形另存为 C:\git\book\Gems\MyGem\Assets\Animation\chicken.animgraph。
- 将工作区另存为 C:\git\book\Gems\MyGem\Assets\Animation\chicken.emfxworkspace。
运动调整
我们有一个小鸡的行走动画,但对于我们的游戏来说太慢了。我们可以解决这个问题,加快行走动画的播放速度。
- 转到分配了 chickenwalking.motion 的 Motion1 节点。
- 在“属性”面板中,将“播放速度”更改为三 (3)。
- 通过修改 Speed 参数来测试动画的外观。
- 保存动画图形。
指定 Anim Graph
此时,我们已经完成了 Animation Editor,回到 Editor,我们可以在 Anim Graph 组件中分配新的图形。
- 将 Anim graph 设置为 chicken.animgraph。
- 将 Motion set asset 设置为 chicken.motionset。
- 设置动作集后,“动作动作集”属性将自行更新为 MotinoSet0。
注意:Anim Graph (动画图形) 组件读取我们分配的动画图形,找到 Speed (速度) 参数并将其反映到 Editor 中。您可以通过在组件上手动输入值并使用 CTRL+G 播放关卡来再次测试动画
驱动速度参数
剩下的唯一工作是根据鸡的速度设置 Speed 参数。这可以通过计算鸡的速度,然后调用 SetNamedParameterFloat 来完成。
using namespace EMotionFX::Integration;
AnimGraphComponentRequestBus::Event(m_actor,
&AnimGraphComponentRequests::SetNamedParameterFloat,
"Speed", s);
为了将关注点与我们目前编写的其他组件区分开来,我创建了一个新组件 ChickenAnimationComponent,它通过 ChickenNotificationBus 从 ChickenControllerComponent 接收速度通知。
void ChickenControllerComponent::UpdateVelocity(
const ChickenInput& input)
{
...
ChickenNotificationBus::Event(GetEntityId(),
&ChickenNotificationBus::Events::OnChickenSpeedChanged,
m_velocity.GetLength());
}
OnChickenSpeedChanged 将分配 Speed 参数并完成本章的工作。
void ChickenAnimationComponent::OnChickenSpeedChanged(float s)
{
using namespace EMotionFX::Integration;
AnimGraphComponentRequestBus::Event(m_actor,
&AnimGraphComponentRequests::SetNamedParameterFloat,
"Speed", s);
}
例 25.2.小鸡在舞会上奔跑
源代码
注意:本章的关卡和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch25_animation
例 25.3. ChickenBus.h
#pragma once
#include <AzCore/Component/ComponentBus.h>
namespace MyGem
{
class ChickenNotifications
:
public AZ::ComponentBus
{
public:
virtual void OnChickenSpeedChanged(float speed) = 0;
};
using ChickenNotificationBus = AZ::EBus<ChickenNotifications>;
}
例 25.4. ChickenAnimationComponent.h
#pragma once
#include <AzCore/Component/Component.h>
#include <MyGem/ChickenBus.h>
namespace MyGem
{
class ChickenAnimationComponent
:
public AZ::Component
,
public ChickenNotificationBus::Handler
{
public:
AZ_COMPONENT(ChickenAnimationComponent,
"{ED8B6A79-AA47-44B2-91B6-64A78439B037}");
static void Reflect(AZ::ReflectContext* rc);
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
// ChickenNotificationBus interface
void OnChickenSpeedChanged(float s) override;
private:
AZ::EntityId m_actor;
};
} // namespace MyGem
例 25.5. ChickenAnimationComponent.h
#include <ChickenAnimationComponent.h>
#include <AzCore/Serialization/EditContext.h>
#include <Integration/AnimGraphComponentBus.h>
namespace MyGem
{
void ChickenAnimationComponent::Reflect(AZ::ReflectContext* rc)
{
if (auto sc = azrtti_cast<AZ::SerializeContext*>(rc))
{
sc->Class<ChickenAnimationComponent, AZ::Component>()
->Field("Actor", &ChickenAnimationComponent::m_actor)
->Version(1);
if (AZ::EditContext* ec = sc->GetEditContext())
{
using namespace AZ::Edit;
ec->Class<ChickenAnimationComponent>(
"Chicken Animation",
"[Player controlled chicken]")
->ClassElement(ClassElements::EditorData, "")
->Attribute(
Attributes::AppearsInAddComponentMenu,
AZ_CRC_CE("Game"))
->DataElement(nullptr,
&ChickenAnimationComponent::m_actor,
"Actor", "Entity with chicken actor.");
}
}
}
void ChickenAnimationComponent::Activate()
{
ChickenNotificationBus::Handler::BusConnect(GetEntityId());
}
void ChickenAnimationComponent::Deactivate()
{
ChickenNotificationBus::Handler::BusDisconnect();
}
void ChickenAnimationComponent::OnChickenSpeedChanged(float s)
{
using namespace EMotionFX::Integration;
AnimGraphComponentRequestBus::Event(m_actor,
&AnimGraphComponentRequests::SetNamedParameterFloat,
"Speed", s);
}
} // namespace MyGem