本文内容
第38章 多人游戏动画
第38章 多人游戏动画
介绍
注意:本章随附的源代码和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch38_multiplayer_animation
我们在将大部分游戏逻辑转换为多人游戏方面取得了长足的进步。下一个任务是同步鸡的动画。在单人游戏中,鸡在移动时播放奔跑动画。
当我们转向多人游戏时,这种情况被打破了,因为客户端动画组件不再接收其速度的任何更新。
我们可以通过唱出 AZ::TransformBus 移动通知来计算客户端上鸡的速度来修复它,但这可能会导致问题。例如,当鸡的玩家输入结果与服务器不一致时,鸡可能会收到更正。在这种情况下,鸡可能会根据误差向后或向前弹出,我们的速度计算将是错误的。
更好的方法是让服务器将速度中继回所有客户端。
对 Chicken Movement 组件的更改
我们在第 36 章 多人游戏物理学 中,当我们需要客户端的 score 值时,我们将服务器值复制到客户端。现在我们要复制一个 AZ::Vector3 类型的速度场。
<NetworkProperty Type="AZ::Vector3" Name="Velocity"
ReplicateFrom="Authority" ReplicateTo="Client"
...
以前,我们将 m_velocity 作为 ChickenMovementComponentController 的成员。我们将用代码生成的字段替换它,这将为我们提供 GetVelocity 和 SetVelocity 方法。修改控制器的 ProcessInput 方法。
void ChickenMovementComponentController::ProcessInput(...)
{
...
GetNetworkCharacterComponentController()->
TryMoveWithVelocity(GetVelocity(), deltaTime);
}
修改我们保存速度变化的方式。
void ChickenMovementComponentController::UpdateVelocity(
const ChickenMovementComponentNetworkInput* input)
{
...
SetVelocity(AZ::Quaternion::CreateRotationZ(currentHeading).
TransformVector(combined) * GetWalkSpeed() +
AZ::Vector3::CreateAxisZ(GetGravity()));
}
启用 Generate Event Bindings 属性。
<NetworkProperty Type="AZ::Vector3" Name="Velocity"
GenerateEventBindings="true"
这将生成 VelocityAddEvent 方法,该方法用作注册速度更改的一种方式。
鸡动画组件
在本章中,我们将 Chicken Animation 组件转换为多人游戏组件,该组件将侦听客户端上 Chicken Movement 组件的速度变化,并将速度变化应用于动画图形。
图 38.1.多人鸡动画组件的设计
- 上下文从输入处理未更改的服务器开始。
- 在处理输入逻辑期间更新 Velocity 网络属性。
- 多人游戏系统通过网络将速度变化复制到客户端。
- 这是本章的第一个新变化。Chicken Animation 组件注册并收到速度已更改的通知。
- 将速度值应用于动画图形的方式与第 26 章 动画状态机中执行的作相同。
监听 Velocity 变化
提示:到目前为止,我们监听了同一组件上的网络属性更改,但我们也可以监听来自同一实体上另一个组件甚至不同实体的更改。
以下是在客户端上注册速度更改的步骤。
- 声明 Chicken Animation 组件对 Chicken Movement 组件的依赖关系。
<Component
Name="ChickenAnimationComponent" ...>
<ComponentRelation Constraint="Required" HasController="false"
Name="ChickenMovementComponent" Namespace="MyGem"
Include="Multiplayer/ChickenMovementComponent.h"/>
- 添加事件处理程序和回调。
AZ::Event<AZ::Vector3>::Handler m_velocityChangedEvent;
void OnVelocityChanged(AZ::Vector3 velocity);
- 将回调分配给组件构造函数中的处理程序。
ChickenAnimationComponent::ChickenAnimationComponent()
: m_velocityChangedEvent([this](AZ::Vector3 velocity)
{
OnVelocityChanged(velocity);
})
{}
- 将处理程序连接到 Chicken Movement 组件中的事件。
void ChickenAnimationComponent::OnActivate(
Multiplayer::EntityIsMigrating)
{
GetChickenMovementComponent()->VelocityAddEvent(
m_velocityChangedEvent);
}
- 从回调驱动动画图形参数。
void ChickenAnimationComponent::OnVelocityChanged(
AZ::Vector3 velocity)
{
velocity.SetZ(0);
using namespace EMotionFX::Integration;
AnimGraphComponentRequestBus::Event(GetEntityId(),
&AnimGraphComponentRequests::SetNamedParameterFloat,
"Speed", velocity.GetLength());
}
注意:我将速度的 Z 分量设置为零,因为跑步动画不应依赖于指向负 Z 轴的重力部分。
实体更改
在本章中,我们将 Chicken Animation 组件从单人游戏转换为多人游戏变体。
替换 Chicken.prefab 的根实体上的组件。
此外,我们需要对 Chicken.prefab 进行一些结构更改,因为 Chicken Animation 组件现在希望在同一实体上找到 Animation Graph 组件。
- 从 Chicken 实体中删除旧的 Chicken Animation 组件。
- 将新的 Chicken Animation 组件添加到顶级 Chicken 实体。
- 将所有组件从Chicken_Actor实体移动到根 Chicken 实体。(除了 Network Binding 和 Network Transform 组件外,它们已经存在于 Chicken 实体上。
- 删除Chicken_Actor实体。
- 删除Chicken_Rigidbody实体。这些实体现在是空的,没有任何用途。
图 38.2.更新了 Chicken.prefab
小结
注意:本章随附的源代码和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch38_multiplayer_animation
在本章中,我们使用 velocity 作为动画状态的主要来源,将动画状态从服务器复制到客户端。
例 38.1.ChickenAnimationComponent.AutoComponent.h
<?xml version="1.0"?>
<Component
Name="ChickenAnimationComponent"
Namespace="MyGem"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/ChickenAnimationComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="false"
Name="ChickenMovementComponent" Namespace="MyGem"
Include="Multiplayer/ChickenMovementComponent.h"/>
</Component>
例 38.2. ChickenAnimationComponent.h
#pragma once
#include <Source/AutoGen/ChickenAnimationComponent.AutoComponent.h>
namespace MyGem
{
class ChickenAnimationComponent
:
public ChickenAnimationComponentBase
{
public:
AZ_MULTIPLAYER_COMPONENT(MyGem::ChickenAnimationComponent,
s_chickenAnimationComponentConcreteUuid,
MyGem::ChickenAnimationComponentBase);
static void Reflect(AZ::ReflectContext* rc);
ChickenAnimationComponent();
void OnInit() override {}
void OnActivate(Multiplayer::EntityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating) override{}
private:
AZ::Event<AZ::Vector3>::Handler m_velocityChangedEvent;
void OnVelocityChanged(AZ::Vector3 velocity);
};
}
例 38.3. ChickenAnimationComponent.cpp
#include <AzCore/Serialization/SerializeContext.h>
#include <Integration/AnimGraphComponentBus.h>
#include <Multiplayer/ChickenAnimationComponent.h>
#include <Source/AutoGen/ChickenMovementComponent.AutoComponent.h>
namespace MyGem
{
void ChickenAnimationComponent::Reflect(AZ::ReflectContext* rc)
{
auto sc = azrtti_cast<AZ::SerializeContext*>(rc);
if (sc)
{
sc->Class<ChickenAnimationComponent,
ChickenAnimationComponentBase>()->Version(1);
}
ChickenAnimationComponentBase::Reflect(rc);
}
ChickenAnimationComponent::ChickenAnimationComponent()
: m_velocityChangedEvent([this](AZ::Vector3 velocity)
{
OnVelocityChanged(velocity);
})
{}
void ChickenAnimationComponent::OnActivate(
Multiplayer::EntityIsMigrating)
{
GetChickenMovementComponent()->VelocityAddEvent(
m_velocityChangedEvent);
}
void ChickenAnimationComponent::OnVelocityChanged(
AZ::Vector3 velocity)
{
velocity.SetZ(0);
using namespace EMotionFX::Integration;
AnimGraphComponentRequestBus::Event(GetEntityId(),
&AnimGraphComponentRequests::SetNamedParameterFloat,
"Speed", velocity.GetLength());
}
}