Merge pull request #1098 from aws-lumberyard-dev/MultiplayerComponents
Multiplayer components
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Launcher.h>
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
@@ -22,6 +23,8 @@
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzFramework/Asset/AssetSystemBus.h>
|
||||
#include <AzFramework/IO/RemoteStorageDrive.h>
|
||||
#include <AzFramework/Windowing/NativeWindow.h>
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
|
||||
#include <AzGameFramework/Application/GameApplication.h>
|
||||
|
||||
@@ -45,6 +48,19 @@ extern "C" void CreateStaticModules(AZStd::vector<AZ::Module*>& modulesOut);
|
||||
|
||||
namespace
|
||||
{
|
||||
void OnViewportResize(const AZ::Vector2& value);
|
||||
|
||||
AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate,
|
||||
"The default size for the launcher viewport, 0 0 means full screen");
|
||||
|
||||
void OnViewportResize(const AZ::Vector2& value)
|
||||
{
|
||||
AzFramework::NativeWindowHandle windowHandle = nullptr;
|
||||
AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
|
||||
AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast<int32_t>(value.GetX()), aznumeric_cast<int32_t>(value.GetY()));
|
||||
AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize);
|
||||
}
|
||||
|
||||
void ExecuteConsoleCommandFile(AzFramework::Application& application)
|
||||
{
|
||||
const AZStd::string_view customConCmdKey = "console-command-file";
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Multiplayer
|
||||
using EntityMigrationStartEvent = AZ::Event<ClientInputId>;
|
||||
using EntityMigrationEndEvent = AZ::Event<>;
|
||||
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
|
||||
using EntityPreRenderEvent = AZ::Event<float, float>;
|
||||
|
||||
//! @class NetBindComponent
|
||||
//! @brief Component that provides net-binding to a networked entity.
|
||||
@@ -97,6 +98,7 @@ namespace Multiplayer
|
||||
void NotifyMigrationStart(ClientInputId migratedInputId);
|
||||
void NotifyMigrationEnd();
|
||||
void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId);
|
||||
void NotifyPreRender(float deltaTime, float blendFactor);
|
||||
|
||||
void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler);
|
||||
void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler);
|
||||
@@ -104,6 +106,7 @@ namespace Multiplayer
|
||||
void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler);
|
||||
void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler);
|
||||
void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler);
|
||||
void AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler);
|
||||
|
||||
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
|
||||
|
||||
@@ -152,6 +155,7 @@ namespace Multiplayer
|
||||
EntityMigrationStartEvent m_entityMigrationStartEvent;
|
||||
EntityMigrationEndEvent m_entityMigrationEndEvent;
|
||||
EntityServerMigrationEvent m_entityServerMigrationEvent;
|
||||
EntityPreRenderEvent m_entityPreRenderEvent;
|
||||
AZ::Event<> m_onRemove;
|
||||
RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle;
|
||||
AZ::Event<>::Handler m_handleMarkedDirty;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Source/AutoGen/NetworkTransformComponent.AutoComponent.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
|
||||
namespace Multiplayer
|
||||
@@ -32,13 +33,22 @@ namespace Multiplayer
|
||||
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
|
||||
|
||||
private:
|
||||
void OnPreRender(float deltaTime, float blendFactor);
|
||||
|
||||
void OnRotationChangedEvent(const AZ::Quaternion& rotation);
|
||||
void OnTranslationChangedEvent(const AZ::Vector3& translation);
|
||||
void OnScaleChangedEvent(float scale);
|
||||
void OnResetCountChangedEvent();
|
||||
|
||||
AZ::Transform m_previousTransform = AZ::Transform::CreateIdentity();
|
||||
AZ::Transform m_targetTransform = AZ::Transform::CreateIdentity();
|
||||
|
||||
AZ::Event<AZ::Quaternion>::Handler m_rotationEventHandler;
|
||||
AZ::Event<AZ::Vector3>::Handler m_translationEventHandler;
|
||||
AZ::Event<float>::Handler m_scaleEventHandler;
|
||||
AZ::Event<uint8_t>::Handler m_resetCountEventHandler;
|
||||
|
||||
EntityPreRenderEvent::Handler m_entityPreRenderEventHandler;
|
||||
};
|
||||
|
||||
class NetworkTransformComponentController
|
||||
|
||||
+4
-4
@@ -45,10 +45,10 @@ namespace Multiplayer
|
||||
static constexpr uint32_t MaxRecordBits = 2048;
|
||||
|
||||
ReplicationRecord() = default;
|
||||
ReplicationRecord(NetEntityRole netEntityRole);
|
||||
ReplicationRecord(NetEntityRole remoteNetEntityRole);
|
||||
|
||||
void SetNetworkRole(NetEntityRole netEntityRole);
|
||||
NetEntityRole GetNetworkRole() const;
|
||||
void SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole);
|
||||
NetEntityRole GetRemoteNetworkRole() const;
|
||||
|
||||
bool AreAllBitsConsumed() const;
|
||||
void ResetConsumedBits();
|
||||
@@ -92,6 +92,6 @@ namespace Multiplayer
|
||||
// Sequence number this ReplicationRecord was sent on
|
||||
AzNetworking::PacketId m_sentPacketId = AzNetworking::InvalidPacketId;
|
||||
|
||||
NetEntityRole m_netEntityRole = NetEntityRole::InvalidRole;;
|
||||
NetEntityRole m_remoteNetEntityRole = NetEntityRole::InvalidRole;;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Multiplayer
|
||||
AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay");
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
|
||||
AZ_CVAR(bool, cl_EnableDesyncDebugging, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs");
|
||||
#endif
|
||||
|
||||
AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs");
|
||||
@@ -214,11 +215,12 @@ namespace Multiplayer
|
||||
// Send correction
|
||||
SendClientInputCorrection(GetLastInputId(), correction);
|
||||
|
||||
#ifdef _DEBUG
|
||||
// In debug, show which states caused the correction
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
AZStd::string clientStateString;
|
||||
AZStd::string serverStateString;
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
// In debug, show which states caused the correction
|
||||
// Write in client state
|
||||
AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize());
|
||||
GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer);
|
||||
@@ -236,11 +238,13 @@ namespace Multiplayer
|
||||
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
|
||||
|
||||
AZStd::map<AZStd::string, AZStd::pair<AZStd::string, AZStd::string>> mapComparison;
|
||||
|
||||
// put the server value in the first part of the pair
|
||||
for (const auto& pair : serverValues.GetValueMap())
|
||||
{
|
||||
mapComparison[pair.first].first = pair.second;
|
||||
}
|
||||
|
||||
// put the client value in the second part of the pair
|
||||
for (const auto& pair : clientValues.GetValueMap())
|
||||
{
|
||||
@@ -266,12 +270,13 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
const AZStd::string clientStateString = "available in debug only";
|
||||
const AZStd::string serverStateString = "available in debug only";
|
||||
#endif
|
||||
|
||||
else
|
||||
{
|
||||
clientStateString = "available in debug only";
|
||||
serverStateString = "available in debug only";
|
||||
}
|
||||
AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +421,7 @@ namespace Multiplayer
|
||||
|
||||
ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const
|
||||
{
|
||||
return m_clientInputId;
|
||||
return m_lastClientInputId;
|
||||
}
|
||||
|
||||
HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const
|
||||
@@ -520,10 +525,13 @@ namespace Multiplayer
|
||||
|
||||
// In debug, send the entire client output state to the server to make it easier to debug desync issues
|
||||
AzNetworking::PacketEncodingBuffer processInputResult;
|
||||
#ifdef _DEBUG
|
||||
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity());
|
||||
GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer);
|
||||
processInputResult.Resize(processInputResultSerializer.GetSize());
|
||||
#ifndef AZ_RELEASE_BUILD
|
||||
if (cl_EnableDesyncDebugging)
|
||||
{
|
||||
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity());
|
||||
GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer);
|
||||
processInputResult.Resize(processInputResultSerializer.GetSize());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Save this input and discard move history outside our client rewind window
|
||||
|
||||
@@ -390,6 +390,11 @@ namespace Multiplayer
|
||||
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
|
||||
}
|
||||
|
||||
void NetBindComponent::NotifyPreRender(float deltaTime, float blendFactor)
|
||||
{
|
||||
m_entityPreRenderEvent.Signal(deltaTime, blendFactor);
|
||||
}
|
||||
|
||||
void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler)
|
||||
{
|
||||
eventHandler.Connect(m_entityStopEvent);
|
||||
@@ -420,6 +425,11 @@ namespace Multiplayer
|
||||
eventHandler.Connect(m_entityServerMigrationEvent);
|
||||
}
|
||||
|
||||
void NetBindComponent::AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler)
|
||||
{
|
||||
eventHandler.Connect(m_entityPreRenderEvent);
|
||||
}
|
||||
|
||||
bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
m_predictableRecord.ResetConsumedBits();
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Multiplayer
|
||||
: m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); })
|
||||
, m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); })
|
||||
, m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); })
|
||||
, m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); })
|
||||
, m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); })
|
||||
{
|
||||
;
|
||||
}
|
||||
@@ -47,6 +49,11 @@ namespace Multiplayer
|
||||
RotationAddEvent(m_rotationEventHandler);
|
||||
TranslationAddEvent(m_translationEventHandler);
|
||||
ScaleAddEvent(m_scaleEventHandler);
|
||||
ResetCountAddEvent(m_resetCountEventHandler);
|
||||
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
|
||||
|
||||
// When coming into relevance, reset all blending factors so we don't interpolate to our start position
|
||||
OnResetCountChangedEvent();
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
|
||||
@@ -56,23 +63,40 @@ namespace Multiplayer
|
||||
|
||||
void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation)
|
||||
{
|
||||
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
|
||||
worldTm.SetRotation(rotation);
|
||||
GetTransformComponent()->SetWorldTM(worldTm);
|
||||
m_previousTransform.SetRotation(m_targetTransform.GetRotation());
|
||||
m_targetTransform.SetRotation(rotation);
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation)
|
||||
{
|
||||
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
|
||||
worldTm.SetTranslation(translation);
|
||||
GetTransformComponent()->SetWorldTM(worldTm);
|
||||
m_previousTransform.SetTranslation(m_targetTransform.GetTranslation());
|
||||
m_targetTransform.SetTranslation(translation);
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnScaleChangedEvent(float scale)
|
||||
{
|
||||
AZ::Transform worldTm = GetTransformComponent()->GetWorldTM();
|
||||
worldTm.SetUniformScale(scale);
|
||||
GetTransformComponent()->SetWorldTM(worldTm);
|
||||
m_previousTransform.SetUniformScale(m_targetTransform.GetUniformScale());
|
||||
m_targetTransform.SetUniformScale(scale);
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnResetCountChangedEvent()
|
||||
{
|
||||
m_targetTransform.SetRotation(GetRotation());
|
||||
m_targetTransform.SetTranslation(GetTranslation());
|
||||
m_targetTransform.SetUniformScale(GetScale());
|
||||
m_previousTransform = m_targetTransform;
|
||||
}
|
||||
|
||||
void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime, float blendFactor)
|
||||
{
|
||||
if (!HasController())
|
||||
{
|
||||
AZ::Transform blendTransform;
|
||||
blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor));
|
||||
blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor));
|
||||
blendTransform.SetUniformScale(AZ::Lerp(m_previousTransform.GetUniformScale(), m_targetTransform.GetUniformScale(), blendFactor));
|
||||
GetTransformComponent()->SetWorldTM(blendTransform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,11 +120,8 @@ namespace Multiplayer
|
||||
|
||||
void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm)
|
||||
{
|
||||
if (IsAuthority())
|
||||
{
|
||||
SetRotation(worldTm.GetRotation());
|
||||
SetTranslation(worldTm.GetTranslation());
|
||||
SetScale(worldTm.GetUniformScale());
|
||||
}
|
||||
SetRotation(worldTm.GetRotation());
|
||||
SetTranslation(worldTm.GetTranslation());
|
||||
SetScale(worldTm.GetUniformScale());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <Multiplayer/MultiplayerConstants.h>
|
||||
#include <Multiplayer/Components/MultiplayerComponent.h>
|
||||
|
||||
#include <MultiplayerSystemComponent.h>
|
||||
#include <ConnectionData/ClientToServerConnectionData.h>
|
||||
#include <ConnectionData/ServerToClientConnectionData.h>
|
||||
@@ -24,14 +23,23 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Console/ILogger.h>
|
||||
#include <AzCore/Math/ShapeIntersection.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <AzFramework/Components/CameraBus.h>
|
||||
#include <AzFramework/Visibility/IVisibilitySystem.h>
|
||||
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
#include <AzNetworking/Framework/INetworking.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace AZ::ConsoleTypeHelpers
|
||||
{
|
||||
template <>
|
||||
@@ -74,6 +82,7 @@ namespace Multiplayer
|
||||
AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking");
|
||||
AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server");
|
||||
AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything");
|
||||
AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update");
|
||||
AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects");
|
||||
|
||||
void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
@@ -156,10 +165,26 @@ namespace Multiplayer
|
||||
AZ::TickBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
||||
void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
||||
{
|
||||
AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
|
||||
AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs();
|
||||
const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
|
||||
const AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs();
|
||||
const AZ::TimeMs serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs);
|
||||
const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f;
|
||||
|
||||
TickVisibleNetworkEntities(deltaTime, serverRateSeconds);
|
||||
|
||||
if (GetAgentType() == MultiplayerAgentType::ClientServer
|
||||
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
|
||||
{
|
||||
m_serverSendAccumulator += deltaTime;
|
||||
if (m_serverSendAccumulator < serverRateSeconds)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_serverSendAccumulator -= serverRateSeconds;
|
||||
m_networkTime.IncrementHostFrameId();
|
||||
}
|
||||
|
||||
// Handle deferred local rpc messages that were generated during the updates
|
||||
m_networkEntityManager.DispatchLocalDeferredRpcMessages();
|
||||
@@ -365,13 +390,21 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
EntityReplicationManager& replicationManager = reinterpret_cast<IConnectionData*>(connection->GetUserData())->GetReplicationManager();
|
||||
|
||||
// Ignore a_Request.GetServerGameTimePoint(), clients can't affect the server gametime
|
||||
|
||||
if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId))
|
||||
{
|
||||
// Update client to latest server time
|
||||
m_renderBlendFactor = 0.0f;
|
||||
m_lastReplicatedHostTimeMs = packet.GetHostTimeMs();
|
||||
m_lastReplicatedHostFrameId = packet.GetHostFrameId();
|
||||
m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId);
|
||||
}
|
||||
|
||||
for (AZStd::size_t i = 0; i < packet.GetEntityMessages().size(); ++i)
|
||||
{
|
||||
const NetworkEntityUpdateMessage& updateMessage = packet.GetEntityMessages()[i];
|
||||
handledAll &= replicationManager.HandleEntityUpdateMessage(connection, packetHeader, updateMessage);
|
||||
AZ_Assert(handledAll, "GameServerToClientNetworkRequestHandler EntityUpdates Did not handle all updates");
|
||||
AZ_Assert(handledAll, "EntityUpdates did not handle all update messages");
|
||||
}
|
||||
|
||||
return handledAll;
|
||||
@@ -440,7 +473,7 @@ namespace Multiplayer
|
||||
|
||||
// Hosts will spawn a new default player prefab for the user that just connected
|
||||
if (GetAgentType() == MultiplayerAgentType::ClientServer
|
||||
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
|
||||
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
|
||||
{
|
||||
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab();
|
||||
if (controlledEntity.Exists())
|
||||
@@ -602,6 +635,74 @@ namespace Multiplayer
|
||||
AZLOG_INFO("Total RPCs received bytes: %llu", aznumeric_cast<AZ::u64>(rpcsRecv.m_totalBytes));
|
||||
}
|
||||
|
||||
void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds)
|
||||
{
|
||||
const float targetAdjustBlend = AZStd::clamp(deltaTime / serverRateSeconds, 0.0f, 1.0f);
|
||||
m_renderBlendFactor += targetAdjustBlend;
|
||||
|
||||
// Linear close to the origin, but asymptote at y = 1
|
||||
const float adjustedBlendFactor = 1.0f - (std::pow(0.2f, m_renderBlendFactor));
|
||||
AZLOG(NET_Blending, "Computed blend factor of %f", adjustedBlendFactor);
|
||||
|
||||
if (Camera::ActiveCameraRequestBus::HasHandlers())
|
||||
{
|
||||
// If there's a camera, update only what's visible
|
||||
AZ::Transform activeCameraTransform;
|
||||
Camera::Configuration activeCameraConfiguration;
|
||||
Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform);
|
||||
Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration);
|
||||
|
||||
const AZ::ViewFrustumAttributes frustumAttributes
|
||||
(
|
||||
activeCameraTransform,
|
||||
activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth,
|
||||
activeCameraConfiguration.m_fovRadians,
|
||||
activeCameraConfiguration.m_nearClipDistance,
|
||||
activeCameraConfiguration.m_farClipDistance
|
||||
);
|
||||
const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes);
|
||||
|
||||
// Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system
|
||||
AZStd::vector<NetBindComponent*> gatheredEntities;
|
||||
AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get();
|
||||
AZ::Interface<AzFramework::IVisibilitySystem>::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum,
|
||||
[&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData)
|
||||
{
|
||||
gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size());
|
||||
for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries)
|
||||
{
|
||||
if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity)
|
||||
{
|
||||
AZ::Entity* entity = static_cast<AZ::Entity*>(visEntry->m_userData);
|
||||
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
|
||||
if (netBindComponent != nullptr)
|
||||
{
|
||||
gatheredEntities.push_back(netBindComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (NetBindComponent* netBindComponent : gatheredEntities)
|
||||
{
|
||||
netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's no camera, fall back to updating all net entities
|
||||
for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker()))
|
||||
{
|
||||
AZ::Entity* entity = iter.second;
|
||||
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
|
||||
if (netBindComponent != nullptr)
|
||||
{
|
||||
netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerSystemComponent::OnConsoleCommandInvoked
|
||||
(
|
||||
AZStd::string_view command,
|
||||
|
||||
@@ -102,6 +102,7 @@ namespace Multiplayer
|
||||
|
||||
private:
|
||||
|
||||
void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds);
|
||||
void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom);
|
||||
void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands);
|
||||
NetworkEntityHandle SpawnDefaultPlayerPrefab();
|
||||
@@ -124,6 +125,9 @@ namespace Multiplayer
|
||||
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
|
||||
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
|
||||
|
||||
double m_serverSendAccumulator = 0.0;
|
||||
float m_renderBlendFactor = 0.0f;
|
||||
|
||||
#if !defined(AZ_RELEASE_BUILD)
|
||||
MultiplayerEditorConnection m_editorConnectionListener;
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ namespace Multiplayer
|
||||
EntityReplicatorList replicatorUpdatedList;
|
||||
MultiplayerPackets::EntityUpdates entityUpdatePacket;
|
||||
entityUpdatePacket.SetHostTimeMs(hostTimeMs);
|
||||
entityUpdatePacket.SetHostFrameId(InvalidHostFrameId);
|
||||
entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId());
|
||||
// Serialize everything
|
||||
while (!toSendList.empty())
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Multiplayer
|
||||
, m_sentRecords(net_EntityReplicatorRecordsMax)
|
||||
{
|
||||
AZ_Assert(m_netBindComponent, "NetBindComponent is nullptr");
|
||||
m_pendingRecord.SetNetworkRole(remoteNetworkRole);
|
||||
m_pendingRecord.SetRemoteNetworkRole(remoteNetworkRole);
|
||||
}
|
||||
|
||||
bool PropertyPublisher::IsDeleting() const
|
||||
@@ -67,7 +67,7 @@ namespace Multiplayer
|
||||
|
||||
void PropertyPublisher::SetRebasing()
|
||||
{
|
||||
AZ_Assert(m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity");
|
||||
AZ_Assert(m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity");
|
||||
m_replicatorState = EntityReplicatorState::Rebasing;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Multiplayer
|
||||
m_sentRecords.clear();
|
||||
m_netBindComponent->FillTotalReplicationRecord(m_pendingRecord);
|
||||
// Don't send predictable properties back to the Autonomous unless we correct them
|
||||
if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous)
|
||||
if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous)
|
||||
{
|
||||
m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord());
|
||||
}
|
||||
@@ -137,7 +137,7 @@ namespace Multiplayer
|
||||
// We need to clear out old records, and build up a list of everything that has changed since the last acked packet
|
||||
m_sentRecords.push_front(m_pendingRecord);
|
||||
auto iter = m_sentRecords.begin();
|
||||
++iter; // consider everything after the record we are going to send
|
||||
++iter; // Consider everything after the record we are going to send
|
||||
for (; iter != m_sentRecords.end(); ++iter)
|
||||
{
|
||||
// Sequence wasn't acked, so we need to send these bits again
|
||||
@@ -145,7 +145,7 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
// Don't send predictable properties back to the Autonomous unless we correct them
|
||||
if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous)
|
||||
if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous)
|
||||
{
|
||||
m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord());
|
||||
}
|
||||
|
||||
+13
-13
@@ -49,19 +49,19 @@ namespace Multiplayer
|
||||
}
|
||||
|
||||
ReplicationRecord::ReplicationRecord(NetEntityRole netEntityRole)
|
||||
: m_netEntityRole(netEntityRole)
|
||||
: m_remoteNetEntityRole(netEntityRole)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
void ReplicationRecord::SetNetworkRole(NetEntityRole netEntityRole)
|
||||
void ReplicationRecord::SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole)
|
||||
{
|
||||
m_netEntityRole = netEntityRole;
|
||||
m_remoteNetEntityRole = remoteNetEntityRole;
|
||||
}
|
||||
|
||||
NetEntityRole ReplicationRecord::GetNetworkRole() const
|
||||
NetEntityRole ReplicationRecord::GetRemoteNetworkRole() const
|
||||
{
|
||||
return m_netEntityRole;
|
||||
return m_remoteNetEntityRole;
|
||||
}
|
||||
|
||||
bool ReplicationRecord::AreAllBitsConsumed() const
|
||||
@@ -196,26 +196,26 @@ namespace Multiplayer
|
||||
|
||||
bool ReplicationRecord::ContainsAuthorityToClientBits() const
|
||||
{
|
||||
return (m_netEntityRole != NetEntityRole::Authority)
|
||||
|| (m_netEntityRole == NetEntityRole::InvalidRole);
|
||||
return (m_remoteNetEntityRole != NetEntityRole::Authority)
|
||||
|| (m_remoteNetEntityRole == NetEntityRole::InvalidRole);
|
||||
}
|
||||
|
||||
bool ReplicationRecord::ContainsAuthorityToServerBits() const
|
||||
{
|
||||
return (m_netEntityRole == NetEntityRole::Server)
|
||||
|| (m_netEntityRole == NetEntityRole::InvalidRole);
|
||||
return (m_remoteNetEntityRole == NetEntityRole::Server)
|
||||
|| (m_remoteNetEntityRole == NetEntityRole::InvalidRole);
|
||||
}
|
||||
|
||||
bool ReplicationRecord::ContainsAuthorityToAutonomousBits() const
|
||||
{
|
||||
return (m_netEntityRole == NetEntityRole::Autonomous || m_netEntityRole == NetEntityRole::Server)
|
||||
|| (m_netEntityRole == NetEntityRole::InvalidRole);
|
||||
return (m_remoteNetEntityRole == NetEntityRole::Autonomous || m_remoteNetEntityRole == NetEntityRole::Server)
|
||||
|| (m_remoteNetEntityRole == NetEntityRole::InvalidRole);
|
||||
}
|
||||
|
||||
bool ReplicationRecord::ContainsAutonomousToAuthorityBits() const
|
||||
{
|
||||
return (m_netEntityRole == NetEntityRole::Authority)
|
||||
|| (m_netEntityRole == NetEntityRole::InvalidRole);
|
||||
return (m_remoteNetEntityRole == NetEntityRole::Authority)
|
||||
|| (m_remoteNetEntityRole == NetEntityRole::InvalidRole);
|
||||
}
|
||||
|
||||
uint32_t ReplicationRecord::GetRemainingAuthorityToClientBits() const
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
|
||||
#include <PhysX/SystemComponentBus.h>
|
||||
#include <PhysX/MathConversion.h>
|
||||
@@ -183,9 +184,7 @@ namespace PhysXDebug
|
||||
void SystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, const SSystemInitParams&)
|
||||
{
|
||||
InitPhysXColorMappings();
|
||||
RegisterCommands();
|
||||
ConfigurePhysXVisualizationParameters();
|
||||
|
||||
}
|
||||
|
||||
void SystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
@@ -537,12 +536,13 @@ namespace PhysXDebug
|
||||
}
|
||||
}
|
||||
|
||||
static void CmdEnableWireFrame([[maybe_unused]] IConsoleCmdArgs* args)
|
||||
static void physx_CullingBox([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::ToggleCullingWireFrame);
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(physx_CullingBox, AZ::ConsoleFunctorFlags::DontReplicate, "Enables physx wireframe view");
|
||||
|
||||
static void CmdConnectToPvd([[maybe_unused]] IConsoleCmdArgs* args)
|
||||
static void physx_PvdConnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
auto* debug = AZ::Interface<PhysX::Debug::PhysXDebugInterface>::Get();
|
||||
if (debug)
|
||||
@@ -550,8 +550,9 @@ namespace PhysXDebug
|
||||
debug->ConnectToPvd();
|
||||
}
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(physx_PvdConnect, AZ::ConsoleFunctorFlags::DontReplicate, "Connects to the physx visual debugger");
|
||||
|
||||
static void CmdDisconnectFromPvd([[maybe_unused]] IConsoleCmdArgs* args)
|
||||
static void physx_PvdDisconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
auto* debug = AZ::Interface<PhysX::Debug::PhysXDebugInterface>::Get();
|
||||
if (debug)
|
||||
@@ -559,13 +560,14 @@ namespace PhysXDebug
|
||||
debug->DisconnectFromPvd();
|
||||
}
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(physx_PvdDisconnect, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects from the physx visual debugger");
|
||||
|
||||
static void CmdSetPhysXDebugCullingBoxSize(IConsoleCmdArgs* args)
|
||||
static void physx_CullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
const int argumentCount = args->GetArgCount();
|
||||
const int argumentCount = arguments.size();
|
||||
if (argumentCount == 2)
|
||||
{
|
||||
float newCullingBoxSize = (float)strtol(args->GetArg(1), nullptr, 10);
|
||||
float newCullingBoxSize = (float)strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10);
|
||||
PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::SetCullingBoxSize, newCullingBoxSize);
|
||||
}
|
||||
else
|
||||
@@ -574,16 +576,17 @@ namespace PhysXDebug
|
||||
"Please use physx_SetDebugCullingBoxSize <boxSize> e.g. physx_SetDebugCullingBoxSize 100.");
|
||||
}
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(physx_CullingBoxSize, AZ::ConsoleFunctorFlags::DontReplicate, "Sets physx debug culling box size");
|
||||
|
||||
static void CmdTogglePhysXDebugVisualization(IConsoleCmdArgs* args)
|
||||
static void physx_Debug([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
using namespace CryStringUtils;
|
||||
|
||||
const int argumentCount = args->GetArgCount();
|
||||
const int argumentCount = arguments.size();
|
||||
|
||||
if (argumentCount == 2)
|
||||
{
|
||||
const auto userPreference = static_cast<DebugCVarValues>(strtol(args->GetArg(1), nullptr, 10));
|
||||
const auto userPreference = static_cast<DebugCVarValues>(strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10));
|
||||
|
||||
switch (userPreference)
|
||||
{
|
||||
@@ -609,29 +612,7 @@ namespace PhysXDebug
|
||||
AZ_Warning("PhysXDebug", false, "Invalid physx_Debug Arguments. Please use physx_Debug 1 to enable, physx_Debug 0 to disable or physx_Debug 2 to enable all configuration settings.");
|
||||
}
|
||||
}
|
||||
|
||||
void SystemComponent::RegisterCommands()
|
||||
{
|
||||
if (m_registered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (gEnv)
|
||||
{
|
||||
IConsole* console = gEnv->pSystem->GetIConsole();
|
||||
if (console)
|
||||
{
|
||||
console->AddCommand("physx_Debug", CmdTogglePhysXDebugVisualization);
|
||||
console->AddCommand("physx_CullingBox", CmdEnableWireFrame);
|
||||
console->AddCommand("physx_CullingBoxSize", CmdSetPhysXDebugCullingBoxSize);
|
||||
console->AddCommand("physx_PvdConnect", CmdConnectToPvd);
|
||||
console->AddCommand("physx_PvdDisconnect", CmdDisconnectFromPvd);
|
||||
}
|
||||
|
||||
m_registered = true;
|
||||
}
|
||||
}
|
||||
AZ_CONSOLEFREEFUNC(physx_Debug, AZ::ConsoleFunctorFlags::DontReplicate, "Toggles physx debug visualization");
|
||||
|
||||
void SystemComponent::ConfigurePhysXVisualizationParameters()
|
||||
{
|
||||
|
||||
@@ -161,9 +161,6 @@ namespace PhysXDebug
|
||||
/// Initialise the PhysX debug draw colors based on defaults.
|
||||
void InitPhysXColorMappings();
|
||||
|
||||
/// Register debug drawing PhysX commands with Open 3D Engine console during game mode.
|
||||
void RegisterCommands();
|
||||
|
||||
/// Draw the culling box being used by the viewport.
|
||||
/// @param cullingBoxAabb culling box Aabb to debug draw.
|
||||
void DrawDebugCullingBox(const AZ::Aabb& cullingBoxAabb);
|
||||
|
||||
Reference in New Issue
Block a user