Version:

第34章 简单的Player出生点

第34章 简单的Player出生点

介绍

注意:
本章随附的源代码和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch34_player_spawner

Gem Multiplayer 希望我们为服务器上的每个传入连接分配一个玩家实体。

IMultiplayerSpawner 是一个抽象接口,定义在 C:\git\o3de\Gems\Multiplayer\Code\Include\Multiplayer\IMultiplayerSpawner.h 中。由我们来实现它并将其作为 AZ::Interface 提供。它的基本方法是 OnPlayerJoin:

Multiplayer::NetworkEntityHandle OnPlayerJoin(
uint64_t userId,
const Multiplayer::MultiplayerAgentDatum& agentDatum)

OnPlayerJoin 需要返回一个 NetworkEntityHandle,它是网络实体的引用句柄。您可以通过将指针传递给具有 Network Binding 组件的 AZ::Entity 来创建 NetworkEntityHandle。

NetworkEntityHandle(AZ::Entity*)
提示:
尽管接口的名称是多人游戏生成器,但其 API 中没有任何内容会迫使我们设计如何创建玩家实体。我们当然可以按需生成,但是,在本章中,我将通过在关卡上预先创建玩家实体作为预制件来简化它,当 Editor 作为客户端加入时,我将获取该预制件并从中返回实体。

设计

我将创建两个新组件:Chicken Spawn Component 和 Chicken Component。Chicken Spawn 组件将负责从 IMultiplayerSpawner 继承并实现 OnPlayerJoin。Chicken Component 将添加到主 chicken 实体中,并在服务器上激活时向 Chicken Spawn Component 报告自身。然后,Chicken Spawn 组件会将此实体分配给玩家连接

鸡通知总线

Chicken 组件和 Chicken Spawner 组件将使用通知总线 Chicken NotificationBus 进行通信。

例 34.1.鸡巴士.h

 #pragma once
 #include <AzCore/Component/ComponentBus.h>
 namespace MyGem
 {
 class ChickenNotifications
        : public AZ::ComponentBus
    {
 public:
 ...
 virtual void OnChickenCreated(
            [[maybe_unused]] AZ::Entity* e) {}
    };
 using ChickenNotificationBus = AZ::EBus<ChickenNotifications>;
 }

在关卡中生成鸡的预制件时,鸡的实体将调用 Chicken Spawner 组件预期的 OnChickenCreated。

鸡组件

这将是我们的第一个多人游戏控制器实现。我们希望 chicken 组件仅在服务器上向 Chicken Spawn Component 注册自身,因为客户端不参与玩家实体创建。毕竟我们是服务器权威的!

第一步是创建覆盖控制器的 auto-component 的 XML 定义。

例 34.2.第一个 ChickenComponent.AutoComponent.xml

<?xml version="1.0"?>
 <Component
   Name="ChickenComponent"
   Namespace="MyGem"
   OverrideComponent="false"
   OverrideController="true"
   OverrideInclude="Source/Multiplayer/ChickenComponent.h"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 </Component>

接下来,我们按照 第 32 章 自动组件 中的步骤创建 ChickenComponent.h 和 ChickenComponent.cpp。

例 34.3.ChickenComponent.h

 #pragma once
 #include <Source/AutoGen/ChickenComponent.AutoComponent.h>
 namespace MyGem
 {
 class ChickenComponentController
        : 
public ChickenComponentControllerBase
    {
 public:
        ChickenComponentController(ChickenComponent& parent);
 void OnActivate(Multiplayer::EntityIsMigrating) override;
 void OnDeactivate(Multiplayer::EntityIsMigrating) override {}
    };
 }

例 34.4. ChickenComponent.cpp

 #include <Multiplayer/ChickenComponent.h>
 #include <MyGem/ChickenBus.h>
 namespace MyGem
 {
    ChickenComponentController::
      ChickenComponentController(ChickenComponent& p)
        : ChickenComponentControllerBase(p) {}
 void ChickenComponentController::OnActivate(
        Multiplayer::EntityIsMigrating)
    {
 if (!IsAuthority()) return;
        ChickenNotificationBus::Broadcast(
            &ChickenNotificationBus::Events::OnChickenCreated,
                GetEntity());
                }
 }

什么是 IsAuthority?这是基类 MultiplayerController 中的一种方法,仅当控制器位于权威服务器上时,该方法才为 true。

 //! Returns true if this controller has authority.
 //! @return boolean true if this controller has authority
 bool IsAuthority() const;

如果控制器对组件具有写入权限,则认为它对它具有权限。简单来说,控制器只有两个选项。控制器可以具有权限(如果它位于服务器上),也可以是客户端上的自治控制器,用于控制鸡并可以在本地预测鸡的运动。否则,不会为组件创建控制器。当我们实现多人鸡移动时,我们将解决自主控制器的问题。

提示:
正如你所看到的,编写网络组件比编写完全编写的 AZ::Component 需要的代码行少得多。许多样板由代码生成器处理。

Chicken 组件位于关卡中 chicken 结构的根实体上。

Chicken Spawn 组件

现在,我们有一个 chicken 实体在服务器上激活时报告其存在,我们需要 Chicken Spawn Component 来接收它。

例 34.5. Snippet from ChickenSpawnComponent.h

 class ChickenSpawnComponent
 : 
public AZ::Component
        , 
 public Multiplayer::IMultiplayerSpawner
        , 
 public ChickenNotificationBus::Handler
    {
 public:
 ...
 // ChickenNotificationBus
 void OnChickenCreated(AZ::Entity* e) override;
 // IMultiplayerSpawner overrides
        Multiplayer::NetworkEntityHandle OnPlayerJoin(
            uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&) override;
 ...
 private:
        AZ::Entity* m_chicken = nullptr;

ChickenSpawnComponent 继承自 IMultiplayerSpawner 并实现 OnPlayerJoin。

OnChickenCreated 保存实体指针,并在客户端加入时通过 OnPlayerJoin 返回该指针。

例 34.6.ChickenSpawnComponent.cpp 中的代码段

void ChickenSpawnComponent::OnChickenCreated(AZ::Entity* e)
    {
        m_chicken = e;
    }
    NetworkEntityHandle ChickenSpawnComponent::OnPlayerJoin(
        [[maybe_unused]] uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&)
    {
 return NetworkEntityHandle{ m_chicken };
    }

Chicken Spawn 组件不是多人游戏组件,因此可以直接放置在关卡中,例如在新实体 Chicken Spawn 下。

创建 Player 预制件

玩家实体必须是网络实体,因此它位于预制件 Chicken.prefab 内。以下是将我们当前的 chicken 转换为多人预制件的步骤。

  1. 对于每个鸡实体,添加 Net Binding 和 Network Transform 组件。

    注意:
    如果您不添加这两个组件,则这些实体根本不会显示在客户端上。Net Binding 组件将实体标记为网络实体。Network Transform 组件指定在客户端上生成实体的位置,并在服务器移动实体时更新客户端位置。

  2. 将 Local Prediction Player Input Component 添加到父实体 Chicken。在下一章中,它将用于处理玩家的输入。

  1. 选择所有鸡实体并从中创建一个预制件。

  1. 为了更好地组织,我将预制件移动到 Chicken Spawn 实体下。

小结

注意:
本章随附的源代码和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch34_player_spawner

通过这些更改,当您按 CTRL+G 时,您最终会看到您的相机跟随一只鸡。此外,您还将看到 Editor 连接到服务器时显示多人游戏实体。

注意:
服务器启动和 Editor 连接需要几秒钟时间。如果您可以看到相机开关跟随鸡,则一切都在按预期工作。服务器等待客户端加入,然后为其分配一个由 Chicken Spawn 组件指定的要控制的实体。
注意:
本章中的实现非常有限。它仅支持服务器上的一个播放器。在第 39 章 Team Spawner 中,我们将增强 Chicken Spawn 组件以支持多个客户端,并让它们加入不同的团队。

例 34.7.ChickenSpawnComponent.h 的完整代码

 #pragma once
 #include <AzCore/Component/Component.h>
 #include <Multiplayer/IMultiplayerSpawner.h>
 #include <MyGem/ChickenBus.h>
 namespace MyGem
 {
 class ChickenSpawnComponent
        : 
public AZ::Component
        , 
        , 
public Multiplayer::IMultiplayerSpawner
 public ChickenNotificationBus::Handler
    {
 public:
        AZ_COMPONENT(ChickenSpawnComponent,
 "{814BAF21-10E4-4BE9-8380-C23B0EC27205}");
 static void Reflect(AZ::ReflectContext* rc);
 // AZ::Component interface implementation
 void Activate() override;
 void Deactivate() override;
 // ChickenNotificationBus
 void OnChickenCreated(AZ::Entity* e) override;
 // IMultiplayerSpawner overrides
        Multiplayer::NetworkEntityHandle OnPlayerJoin(
            uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&) override;
 void OnPlayerLeave(
            Multiplayer::ConstNetworkEntityHandle entityHandle,
 const Multiplayer::ReplicationSet& replicationSet,
            AzNetworking::DisconnectReason reason) override {}
 private:
        AZ::Entity* m_chicken = nullptr;
    };
 } // namespace MyGem

例 34.8.ChickenSpawnComponent.cpp 的完整代码

 #include <ChickenSpawnComponent.h>
 #include <AzCore/Component/Entity.h>
 #include <AzCore/Interface/Interface.h>
 #include <AzCore/Serialization/EditContext.h>
 #include <Multiplayer/IMultiplayer.h>
  namespace MyGem
 {
 using namespace Multiplayer;
 void ChickenSpawnComponent::Reflect(AZ::ReflectContext* rc)
    {
 if (auto sc = azrtti_cast<AZ::SerializeContext*>(rc))
        {
            sc->Class<ChickenSpawnComponent, AZ::Component>()
                ->Version(1);
 if (AZ::EditContext* ec = sc->GetEditContext())
            {
 using namespace AZ::Edit;
                ec->Class<ChickenSpawnComponent>(
 "Chicken Spawn",
 "[Player controlled chickens]")
                    ->ClassElement(ClassElements::EditorData, "")
                    ->Attribute(
                        Attributes::AppearsInAddComponentMenu,
                        AZ_CRC_CE("Game"));
            }
        }
    }
 void ChickenSpawnComponent::Activate()
    {
        AZ::Interface<IMultiplayerSpawner>::Register(this);
        ChickenNotificationBus::Handler::BusConnect(GetEntityId());
    }
 void ChickenSpawnComponent::Deactivate()
    {
        ChickenNotificationBus::Handler::BusDisconnect();
        AZ::Interface<IMultiplayerSpawner>::Unregister(this);
    }
 void ChickenSpawnComponent::OnChickenCreated(AZ::Entity* e)
    {
        m_chicken = e;
    }
    NetworkEntityHandle ChickenSpawnComponent::OnPlayerJoin(
        [[maybe_unused]] uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&)
    {
 return NetworkEntityHandle{ m_chicken };
    }
 } // namespace MyGem