本文内容
第39章 团队出生点
39章 团队出生点
介绍
注意:本章随附的源代码和资源可以在 GitHub 上找到: https://github.com/AMZN-Olex/O3DEBookCode2111/tree/ch39_team_spawner
我们已将大部分游戏逻辑转换为多人游戏,但尚无法支持多个玩家。如果有多个玩家加入服务器,他们最终会争夺控制同一只鸡。
这不会很顺利。本章将在第 34 章 Simple Player Spawner 的基础上,添加更多的鸡,并为每个新玩家提供来自两个不同团队的不同鸡。
图 39.1.团队生成器设计
- 关卡中会有一些新的鸡。
- 每只鸡都将使用游戏中已有的 ChickenNotificationBus 报告其激活情况和为其分配的团队。
- 当多人游戏系统为传入客户端请求玩家实体时,我们将从其中一个团队中选择一个鸡实体。
注意:如果我们只是向关卡添加更多的 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 预制件
- 将 Camera 组件移出 Chicken.prefab。
- 使用其余实体创建 Chicken_Team_0.prefab 和 Chicken_Team_1.prefab。
- 在 Chicken entity (鸡实体) 上,找到 Chicken component (鸡组件) 并将 team value (团队值) 设置为零 (0)。
- 将 Chicken_Team_1.prefab 的 team value 设置为一 (1)。
- 在 Chicken_Team_0.prefab 的 Chicken 实体上,找到 Material 组件并将“chicken_body_mat”更改为黄色材质,例如 Atom Gem 中的 16_yellow_tex.azmaterial。
- 在 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 组件的更改
- Chicken Spawn Component 将存储两个鸡实体容器,而不是像第 34 章 Simple Player Spawner 中那样只存储一个鸡实体指针。
AZStd::vector<AZ::Entity*> m_teams[2] = {};
AZ::Entity* GetNextChicken();
- 当 chicken 激活通知传入时,它们将被存储到适当的实体团队容器中。
void ChickenSpawnComponent::OnChickenCreated(
AZ::Entity* e, int team)
{
if (team >= 0 && team <= 1)
{
m_teams[team].push_back(e);
}
}
- 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;
}
- 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