Version:

第39章 团队出生点

39章 团队出生点

介绍

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

我们已将大部分游戏逻辑转换为多人游戏,但尚无法支持多个玩家。如果有多个玩家加入服务器,他们最终会争夺控制同一只鸡。

这不会很顺利。本章将在第 34 章 Simple Player Spawner 的基础上,添加更多的鸡,并为每个新玩家提供来自两个不同团队的不同鸡。

图 39.1.团队生成器设计

  1. 关卡中会有一些新的鸡。
  2. 每只鸡都将使用游戏中已有的 ChickenNotificationBus 报告其激活情况和为其分配的团队。
  3. 当多人游戏系统为传入客户端请求玩家实体时,我们将从其中一个团队中选择一个鸡实体。
注意:
如果我们只是向关卡添加更多的 chicken 预制件,我们将发现设计中的缺陷。我们将在关卡中部署太多摄像机。Chicken.prefab 中有一个 Camera (摄像机) 组件,这意味着对于我们在关卡中创建的每只新鸡,将添加另一个 Camera (摄像机) 组件。本章将摄像机与 chicken 预制件分离并将其移动到关卡,而不是处理多个摄像机。在下一章中,我们将创建 Chicken Camera 组件来正确处理相机。

增加团队价值

为了在不同团队之间保持 chickens 的分离,Chicken 组件 XML 定义将为团队编号添加一个 non network 属性。

<Component Name="ChickenComponent" >
<ArchetypeProperty Type="int" Name="Team" Init="0"
ExposeToEditor="true" />

将有两支球队。我们将在 Editor 中将 team 值设置为 0 或 1。

图 39.2.具有 Team Value 的 Chicken 组件

添加更多鸡

吃更多肌肉 — 一个受欢迎的食品广告

我们将放弃 Chicken.prefab 并创建两个新的 chicken 预制件。Chicken_Team_0.prefab 用于第一个鸡队,Chicken_Team_1.prefab 用于第二个团队。

提示:
您可以使用 Detach Prefab 命令拆分预制件。这将从关卡中删除预制件,但留下其所有实体。

创建 Team Chicken 预制件

  1. 将 Camera 组件移出 Chicken.prefab。
  2. 使用其余实体创建 Chicken_Team_0.prefab 和 Chicken_Team_1.prefab。

  1. 在 Chicken entity (鸡实体) 上,找到 Chicken component (鸡组件) 并将 team value (团队值) 设置为零 (0)。

  1. 将 Chicken_Team_1.prefab 的 team value 设置为一 (1)。
  2. 在 Chicken_Team_0.prefab 的 Chicken 实体上,找到 Material 组件并将“chicken_body_mat”更改为黄色材质,例如 Atom Gem 中的 16_yellow_tex.azmaterial。

  1. 在 Chicken_Team_1.prefab 的 Chicken 实体上,找到 Material 组件并将“chicken_body_mat”更改为蓝色材质,例如 Atom Gem 中的 13_blue_tex.azmaterial。

图 39.3.两只鸡的故事

这将为我们提供两种类型的鸡,它们将形成相反的团队。

在 Chicken Spawn entity (鸡生成实体) 下,为每个团队创建一定数量的鸡。我为每个团队创建了四个。

注意:
摄像机不再属于 chicken 预制件

对 Chicken 组件的更改

激活后,鸡将使用现有的事件总线报告他们的团队。

virtual void OnChickenCreated(
[[maybe_unused]] AZ::Entity* e,
[[maybe_unused]] int team) {}

我们已经有一个 Chicken 组件控制器。唯一的更改是将 GetTeam() 用于新添加的 Team 网络属性。

void ChickenComponentController::OnActivate(
Multiplayer::EntityIsMigrating)
{
if (!IsAuthority()) return;
ChickenNotificationBus::Broadcast(
 &ChickenNotificationBus::Events::OnChickenCreated,
                GetEntity(), GetTeam());
    }

对 Chicken Spawn 组件的更改

  1. Chicken Spawn Component 将存储两个鸡实体容器,而不是像第 34 章 Simple Player Spawner 中那样只存储一个鸡实体指针。
AZStd::vector<AZ::Entity*> m_teams[2] = {};
AZ::Entity* GetNextChicken();
  1. 当 chicken 激活通知传入时,它们将被存储到适当的实体团队容器中。
void ChickenSpawnComponent::OnChickenCreated(
AZ::Entity* e, int team)
{
if (team >= 0 && team <= 1)
{
m_teams[team].push_back(e);
}
}
  1. GetNextChicken() 方法将从剩余更多鸡的团队容器中选取,以保持团队均匀。
AZ::Entity* ChickenSpawnComponent::GetNextChicken()
{
AZStd::vector<AZ::Entity*>& team =
m_teams[0].size() > m_teams[1].size() ?
m_teams[0] : m_teams[1];
if (team.empty()) return nullptr;
AZ::Entity* newChicken = team.back();
team.pop_back();
return newChicken;
}
  1. OnPlayerJoin 是多人游戏系统调用并期望接收网络实体的现有方法。
NetworkEntityHandle ChickenSpawnComponent::OnPlayerJoin(
[[maybe_unused]] uint64_t userId,
const Multiplayer::MultiplayerAgentDatum&)
{
return NetworkEntityHandle{ GetNextChicken() };
}

小结

本章升级了鸡生成器组件,通过提前在关卡中放置多只鸡来支持多个玩家。当新玩家加入时,他们将控制关卡中的一只鸡。

您可以生成玩家实体,但这种方法允许我在每一侧设置鸡,以使足球场看起来更受欢迎。

图 39.4.蓝鸡队

我们游戏的多人游戏部分还有最后一个缺陷需要注意。相机没有跟随正确的鸡。事实上,摄像机现在根本没有跟踪任何鸡。下一章将讨论这个问题。

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

Chicken Spawn 组件不是多人游戏组件,因此它没有 XML 定义。

例 39.1.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, int team) override;
 // IMultiplayerSpawner overrides
        Multiplayer::NetworkEntityHandle OnPlayerJoin(
            uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&) override;
 void OnPlayerLeave(
            Multiplayer::ConstNetworkEntityHandle,
 const Multiplayer::ReplicationSet&,
            AzNetworking::DisconnectReason) override {}
 private:
        AZStd::vector<AZ::Entity*> m_teams[2] = {};
        AZ::Entity* GetNextChicken();
    };
 } // namespace MyGem

例 39.2. 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, int team)
    {
 if (team >= 0 && team <= 1)
        {
            m_teams[team].push_back(e);
        }
    }
    NetworkEntityHandle ChickenSpawnComponent::OnPlayerJoin(
        [[maybe_unused]] uint64_t userId,
 const Multiplayer::MultiplayerAgentDatum&)
    {
 return NetworkEntityHandle{ GetNextChicken() };
    }
    AZ::Entity* ChickenSpawnComponent::GetNextChicken()
    {
        AZStd::vector<AZ::Entity*>& team =
            m_teams[0].size() > m_teams[1].size() ?
            m_teams[0] : m_teams[1];
 if (team.empty()) return nullptr;
        AZ::Entity* newChicken = team.back();
        team.pop_back();
 return newChicken;
    }
 } // namespace MyGem