Version:

第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.多人鸡动画组件的设计

  1. 上下文从输入处理未更改的服务器开始。
  2. 在处理输入逻辑期间更新 Velocity 网络属性。
  3. 多人游戏系统通过网络将速度变化复制到客户端。
  4. 这是本章的第一个新变化。Chicken Animation 组件注册并收到速度已更改的通知。
  5. 将速度值应用于动画图形的方式与第 26 章 动画状态机中执行的作相同。

监听 Velocity 变化

提示:
到目前为止,我们监听了同一组件上的网络属性更改,但我们也可以监听来自同一实体上另一个组件甚至不同实体的更改。

以下是在客户端上注册速度更改的步骤。

  1. 声明 Chicken Animation 组件对 Chicken Movement 组件的依赖关系。
<Component
Name="ChickenAnimationComponent" ...>
<ComponentRelation Constraint="Required" HasController="false"
Name="ChickenMovementComponent" Namespace="MyGem"
Include="Multiplayer/ChickenMovementComponent.h"/>
  1. 添加事件处理程序和回调。
AZ::Event<AZ::Vector3>::Handler m_velocityChangedEvent;
void OnVelocityChanged(AZ::Vector3 velocity);
  1. 将回调分配给组件构造函数中的处理程序。
ChickenAnimationComponent::ChickenAnimationComponent()
: m_velocityChangedEvent([this](AZ::Vector3 velocity)
{
OnVelocityChanged(velocity);
})
{}
  1. 将处理程序连接到 Chicken Movement 组件中的事件。
void ChickenAnimationComponent::OnActivate(
Multiplayer::EntityIsMigrating)
{
GetChickenMovementComponent()->VelocityAddEvent(
m_velocityChangedEvent);
}
  1. 从回调驱动动画图形参数。
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 组件。

  1. 从 Chicken 实体中删除旧的 Chicken Animation 组件。
  2. 将新的 Chicken Animation 组件添加到顶级 Chicken 实体。
  3. 将所有组件从Chicken_Actor实体移动到根 Chicken 实体。(除了 Network Binding 和 Network Transform 组件外,它们已经存在于 Chicken 实体上。
  4. 删除Chicken_Actor实体。
  5. 删除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());
    }
 }