本文内容
第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 转换为多人预制件的步骤。
对于每个鸡实体,添加 Net Binding 和 Network Transform 组件。
注意:如果您不添加这两个组件,则这些实体根本不会显示在客户端上。Net Binding 组件将实体标记为网络实体。Network Transform 组件指定在客户端上生成实体的位置,并在服务器移动实体时更新客户端位置。将 Local Prediction Player Input Component 添加到父实体 Chicken。在下一章中,它将用于处理玩家的输入。
- 选择所有鸡实体并从中创建一个预制件。
- 为了更好地组织,我将预制件移动到 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