From 28ca59ae6112c99f5f142d8d5a2e284ec3e10a4e Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Mon, 13 Sep 2021 15:00:42 -0700 Subject: [PATCH 1/7] Moving NetworkHitVolumesComponent and NetworkRigidBodyComponent to the MultiplayerGem Signed-off-by: Gene Walters --- Gems/Multiplayer/Code/CMakeLists.txt | 2 + .../Components/NetworkHitVolumesComponent.h | 89 +++++++ .../Components/NetworkRigidBodyComponent.h | 68 ++++++ .../AutoGen/AutoComponentTypes_Source.jinja | 2 +- ...tworkHitVolumesComponent.AutoComponent.xml | 12 + ...etworkRigidBodyComponent.AutoComponent.xml | 18 ++ .../Components/NetworkHitVolumesComponent.cpp | 222 ++++++++++++++++++ .../Components/NetworkRigidBodyComponent.cpp | 147 ++++++++++++ Gems/Multiplayer/Code/multiplayer_files.cmake | 6 + 9 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index e8b38c8799..256f1bceeb 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -25,6 +25,8 @@ ly_add_target( AZ::AzCore AZ::AzFramework AZ::AzNetworking + PRIVATE + Gem::EMotionFXStaticLib AUTOGEN_RULES *.AutoPackets.xml,AutoPackets_Header.jinja,$path/$fileprefix.AutoPackets.h *.AutoPackets.xml,AutoPackets_Inline.jinja,$path/$fileprefix.AutoPackets.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h new file mode 100644 index 0000000000..74f6dcc571 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Physics +{ + class CharacterRequests; + class CharacterHitDetectionConfiguration; +} + +namespace Multiplayer +{ + class NetworkHitVolumesComponent + : public NetworkHitVolumesComponentBase + , private EMotionFX::Integration::ActorComponentNotificationBus::Handler + { + public: + struct AnimatedHitVolume final + { + AnimatedHitVolume + ( + AzNetworking::ConnectionId connectionId, + Physics::CharacterRequests* character, + const char* hitVolumeName, + const Physics::ColliderConfiguration* colliderConfig, + const Physics::ShapeConfiguration* shapeConfig, + const uint32_t jointIndex + ); + + ~AnimatedHitVolume() = default; + + void UpdateTransform(const AZ::Transform& transform); + void SyncToCurrentTransform(); + + Multiplayer::RewindableObject m_transform; + AZStd::shared_ptr m_physicsShape; + + // Cached so we don't have to do subsequent lookups by name + const Physics::ColliderConfiguration* m_colliderConfig = nullptr; + const Physics::ShapeConfiguration* m_shapeConfig = nullptr; + AZ::Transform m_colliderOffSetTransform; + const AZ::u32 m_jointIndex = 0; + }; + + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHitVolumesComponent, s_networkHitVolumesComponentConcreteUuid, Multiplayer::NetworkHitVolumesComponentBase); + + static void Reflect(AZ::ReflectContext* context); + + NetworkHitVolumesComponent(); + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnPreRender(float deltaTime, float blendFactor); + void OnTransformUpdate(const AZ::Transform& transform); + void OnSyncRewind(); + + void CreateHitVolumes(); + void DestroyHitVolumes(); + + //! ActorComponentNotificationBus::Handler + //! @{ + void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override; + void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override; + //! @} + + Physics::CharacterRequests* m_physicsCharacter = nullptr; + EMotionFX::Integration::ActorComponentRequests* m_actorComponent = nullptr; + const Physics::CharacterColliderConfiguration* m_hitDetectionConfig = nullptr; + + AZStd::vector m_animatedHitVolumes; + + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler; + Multiplayer::EntityPreRenderEvent::Handler m_preRenderHandler; + AZ::TransformChangedEvent::Handler m_transformChangedHandler; + }; +} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h new file mode 100644 index 0000000000..19379fc959 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkRigidBodyComponent.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include + +namespace Physics +{ + class RigidBodyRequests; +} + +namespace Multiplayer +{ + //! Bus for requests to the network rigid body component. + class NetworkRigidBodyRequests : public AZ::ComponentBus + { + }; + using NetworkRigidBodyRequestBus = AZ::EBus; + + class NetworkRigidBodyComponent final + : public NetworkRigidBodyComponentBase + , private NetworkRigidBodyRequestBus::Handler + { + friend class NetworkRigidBodyComponentController; + + public: + AZ_MULTIPLAYER_COMPONENT( + Multiplayer::NetworkRigidBodyComponent, s_networkRigidBodyComponentConcreteUuid, Multiplayer::NetworkRigidBodyComponentBase); + + static void Reflect(AZ::ReflectContext* context); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + NetworkRigidBodyComponent(); + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnTransformUpdate(const AZ::Transform& worldTm); + void OnSyncRewind(); + + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler; + AZ::TransformChangedEvent::Handler m_transformChangedHandler; + Physics::RigidBodyRequests* m_physicsRigidBodyComponent = nullptr; + Multiplayer::RewindableObject m_transform; + }; + + class NetworkRigidBodyComponentController + : public NetworkRigidBodyComponentControllerBase + { + public: + NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent); + + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + void HandleSendApplyImpulse(AzNetworking::IConnection* invokingConnection, const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) override; + }; +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja index 7bb4bdcc2c..c551c8bc20 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja @@ -14,7 +14,7 @@ {% set Namespace = dataFiles[0].attrib['Namespace'] %} {% for Component in dataFiles %} {% if Component.attrib['Namespace'] != Namespace %} -#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but found {{ Component.attrib['Namespace'] }}" +#error "mismatched component namespaces detected in declared multiplayer components, expected {{ Namespace }} but {{ Component.attrib['Name'] }} is using {{ Component.attrib['Namespace'] }} namespace." {% endif %} {% endfor %} namespace {{ Namespace }} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml new file mode 100644 index 0000000000..d3c31a5bc0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml new file mode 100644 index 0000000000..b6cdfca9c2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp new file mode 100644 index 0000000000..e95afbdc86 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + AZ_CVAR(bool, bg_DrawArticulatedHitVolumes, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables debug draw of articulated hit volumes"); + AZ_CVAR(float, bg_DrawDebugHitVolumeLifetime, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "The lifetime for hit volume draw-debug shapes"); + + AZ_CVAR(float, bg_RewindPositionTolerance, 0.0001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta position is less than this value"); + AZ_CVAR(float, bg_RewindOrientationTolerance, 0.001f, nullptr, AZ::ConsoleFunctorFlags::Null, "Don't sync the physx entity if the square of delta orientation is less than this value"); + + NetworkHitVolumesComponent::AnimatedHitVolume::AnimatedHitVolume + ( + AzNetworking::ConnectionId connectionId, + Physics::CharacterRequests* character, + const char* hitVolumeName, + const Physics::ColliderConfiguration* colliderConfig, + const Physics::ShapeConfiguration* shapeConfig, + const uint32_t jointIndex + ) + : m_colliderConfig(colliderConfig) + , m_shapeConfig(shapeConfig) + , m_jointIndex(jointIndex) + { + m_transform.SetOwningConnectionId(connectionId); + + m_colliderOffSetTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_colliderConfig->m_rotation, m_colliderConfig->m_position); + + if (m_colliderConfig->m_isExclusive) + { + Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, *m_colliderConfig, *m_shapeConfig); + } + else + { + Physics::ColliderConfiguration colliderConfiguration = *m_colliderConfig; + colliderConfiguration.m_isExclusive = true; + colliderConfiguration.m_isSimulated = false; + colliderConfiguration.m_isInSceneQueries = true; + Physics::SystemRequestBus::BroadcastResult(m_physicsShape, &Physics::SystemRequests::CreateShape, colliderConfiguration, *m_shapeConfig); + } + + if (m_physicsShape) + { + m_physicsShape->SetName(hitVolumeName); + character->GetCharacter()->AttachShape(m_physicsShape); + } + } + + void NetworkHitVolumesComponent::AnimatedHitVolume::UpdateTransform(const AZ::Transform& transform) + { + m_transform = transform; + m_physicsShape->SetLocalPose(transform.GetTranslation(), transform.GetRotation()); + } + + void NetworkHitVolumesComponent::AnimatedHitVolume::SyncToCurrentTransform() + { + AZ::Transform rewoundTransform; + const AZ::Transform& targetTransform = m_transform.Get(); + const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor(); + if (blendFactor < 1.f) + { + // If a blend factor was supplied, interpolate the transform appropriately + const AZ::Transform& previousTransform = m_transform.GetPrevious(); + rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor)); + rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor)); + rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor)); + } + else + { + rewoundTransform = m_transform.Get(); + } + + const AZ::Transform physicsTransform = AZ::Transform::CreateFromQuaternionAndTranslation(m_physicsShape->GetLocalPose().second, m_physicsShape->GetLocalPose().first); + + // Don't call SetLocalPose unless the transforms are actually different + const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation(); + const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation(); + + if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) || (orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance)) + { + m_physicsShape->SetLocalPose(rewoundTransform.GetTranslation(), rewoundTransform.GetRotation()); + } + } + + void NetworkHitVolumesComponent::NetworkHitVolumesComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + } + NetworkHitVolumesComponentBase::Reflect(context); + } + + NetworkHitVolumesComponent::NetworkHitVolumesComponent() + : m_syncRewindHandler([this]() { OnSyncRewind(); }) + , m_preRenderHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) + , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformUpdate(worldTm); }) + { + ; + } + + void NetworkHitVolumesComponent::OnInit() + { + ; + } + + void NetworkHitVolumesComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusConnect(GetEntityId()); + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + m_physicsCharacter = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId()); + GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + OnTransformUpdate(GetTransformComponent()->GetWorldTM()); + } + + void NetworkHitVolumesComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + DestroyHitVolumes(); + EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect(); + } + + void NetworkHitVolumesComponent::OnPreRender([[maybe_unused]] float deltaTime, [[maybe_unused]] float blendFactor) + { + if (m_animatedHitVolumes.size() <= 0) + { + CreateHitVolumes(); + } + + AZ::Vector3 position, scale; + AZ::Quaternion rotation; + for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes) + { + m_actorComponent->GetJointTransformComponents(hitVolume.m_jointIndex, EMotionFX::Integration::Space::ModelSpace, position, rotation, scale); + hitVolume.UpdateTransform(AZ::Transform::CreateFromQuaternionAndTranslation(rotation, position) * hitVolume.m_colliderOffSetTransform); + } + } + + void NetworkHitVolumesComponent::OnTransformUpdate([[maybe_unused]] const AZ::Transform& transform) + { + OnSyncRewind(); + } + + void NetworkHitVolumesComponent::OnSyncRewind() + { + if (m_physicsCharacter && m_physicsCharacter->GetCharacter()) + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + m_physicsCharacter->GetCharacter()->SetFrameId(frameId); + } + + for (AnimatedHitVolume& hitVolume : m_animatedHitVolumes) + { + hitVolume.SyncToCurrentTransform(); + } + } + + void NetworkHitVolumesComponent::CreateHitVolumes() + { + if (m_physicsCharacter == nullptr || m_actorComponent == nullptr) + { + return; + } + + const Physics::AnimationConfiguration* physicsConfig = m_actorComponent->GetPhysicsConfig(); + if (physicsConfig == nullptr) + { + return; + } + + m_hitDetectionConfig = &physicsConfig->m_hitDetectionConfig; + const AzNetworking::ConnectionId owningConnectionId = GetNetBindComponent()->GetOwningConnectionId(); + + m_animatedHitVolumes.reserve(m_hitDetectionConfig->m_nodes.size()); + for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : m_hitDetectionConfig->m_nodes) + { + const AZStd::size_t jointIndex = m_actorComponent->GetJointIndexByName(nodeConfig.m_name.c_str()); + if (jointIndex == EMotionFX::Integration::ActorComponentRequests::s_invalidJointIndex) + { + continue; + } + + for (const AzPhysics::ShapeColliderPair& coliderPair : nodeConfig.m_shapes) + { + const Physics::ColliderConfiguration* colliderConfig = coliderPair.first.get(); + Physics::ShapeConfiguration* shapeConfig = coliderPair.second.get(); + m_animatedHitVolumes.emplace_back(owningConnectionId, m_physicsCharacter, nodeConfig.m_name.c_str(), colliderConfig, shapeConfig, aznumeric_cast(jointIndex)); + } + } + } + + void NetworkHitVolumesComponent::DestroyHitVolumes() + { + m_animatedHitVolumes.clear(); + } + + void NetworkHitVolumesComponent::OnActorInstanceCreated([[maybe_unused]] EMotionFX::ActorInstance* actorInstance) + { + m_actorComponent = EMotionFX::Integration::ActorComponentRequestBus::FindFirstHandler(GetEntity()->GetId()); + } + + void NetworkHitVolumesComponent::OnActorInstanceDestroyed([[maybe_unused]] EMotionFX::ActorInstance* actorInstance) + { + m_actorComponent = nullptr; + } +} diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp new file mode 100644 index 0000000000..bcf855d834 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkRigidBodyComponent.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace Multiplayer +{ + AZ_CVAR_EXTERNED(float, bg_RewindPositionTolerance); + AZ_CVAR_EXTERNED(float, bg_RewindOrientationTolerance); + + void NetworkRigidBodyComponent::NetworkRigidBodyComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + NetworkRigidBodyComponentBase::Reflect(context); + } + + void NetworkRigidBodyComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("NetworkRigidBodyService")); + } + + void NetworkRigidBodyComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("PhysXRigidBodyService")); + } + + void NetworkRigidBodyComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + dependent.push_back(AZ_CRC_CE("TransformService")); + dependent.push_back(AZ_CRC_CE("PhysXRigidBodyService")); + } + + NetworkRigidBodyComponent::NetworkRigidBodyComponent() + : m_syncRewindHandler([this](){ OnSyncRewind(); }) + , m_transformChangedHandler([this]([[maybe_unused]] const AZ::Transform& localTm, const AZ::Transform& worldTm){ OnTransformUpdate(worldTm); }) + { + } + + void NetworkRigidBodyComponent::OnInit() + { + } + + void NetworkRigidBodyComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + NetworkRigidBodyRequestBus::Handler::BusConnect(GetEntityId()); + + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + GetEntity()->FindComponent()->BindTransformChangedEventHandler(m_transformChangedHandler); + + m_physicsRigidBodyComponent = + Physics::RigidBodyRequestBus::FindFirstHandler(GetEntity()->GetId()); + AZ_Assert(m_physicsRigidBodyComponent, "PhysX Rigid Body Component is required on entity %s", GetEntity()->GetName().c_str()); + + if (!HasController()) + { + m_physicsRigidBodyComponent->SetKinematic(true); + } + } + + void NetworkRigidBodyComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + NetworkRigidBodyRequestBus::Handler::BusDisconnect(); + } + + void NetworkRigidBodyComponent::OnTransformUpdate(const AZ::Transform& worldTm) + { + m_transform = worldTm; + + if (!HasController()) + { + m_physicsRigidBodyComponent->SetKinematicTarget(worldTm); + } + } + + void NetworkRigidBodyComponent::OnSyncRewind() + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + + AzPhysics::RigidBody* rigidBody = m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->SetFrameId(frameId); + + AZ::Transform rewoundTransform; + const AZ::Transform& targetTransform = m_transform.Get(); + const float blendFactor = Multiplayer::GetNetworkTime()->GetHostBlendFactor(); + if (blendFactor < 1.f) + { + // If a blend factor was supplied, interpolate the transform appropriately + const AZ::Transform& previousTransform = m_transform.GetPrevious(); + rewoundTransform.SetRotation(previousTransform.GetRotation().Slerp(targetTransform.GetRotation(), blendFactor)); + rewoundTransform.SetTranslation(previousTransform.GetTranslation().Lerp(targetTransform.GetTranslation(), blendFactor)); + rewoundTransform.SetUniformScale(AZ::Lerp(previousTransform.GetUniformScale(), targetTransform.GetUniformScale(), blendFactor)); + } + else + { + rewoundTransform = m_transform.Get(); + } + const AZ::Transform& physicsTransform = rigidBody->GetTransform(); + + // Don't call SetLocalPose unless the transforms are actually different + const AZ::Vector3 positionDelta = physicsTransform.GetTranslation() - rewoundTransform.GetTranslation(); + const AZ::Quaternion orientationDelta = physicsTransform.GetRotation() - rewoundTransform.GetRotation(); + + if ((positionDelta.GetLengthSq() >= bg_RewindPositionTolerance) || + (orientationDelta.GetLengthSq() >= bg_RewindOrientationTolerance)) + { + rigidBody->SetTransform(rewoundTransform); + } + } + + NetworkRigidBodyComponentController::NetworkRigidBodyComponentController(NetworkRigidBodyComponent& parent) + : NetworkRigidBodyComponentControllerBase(parent) + { + ; + } + + void NetworkRigidBodyComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkRigidBodyComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkRigidBodyComponentController::HandleSendApplyImpulse + ( + [[maybe_unused]] AzNetworking::IConnection* invokingConnection, + const AZ::Vector3& impulse, + const AZ::Vector3& worldPoint + ) + { + AzPhysics::RigidBody* rigidBody = GetParent().m_physicsRigidBodyComponent->GetRigidBody(); + rigidBody->ApplyLinearImpulseAtWorldPoint(impulse, worldPoint); + } +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 0b2adb1530..ca5a52579c 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -18,6 +18,8 @@ set(FILES Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkHitVolumesComponent.h + Include/Multiplayer/Components/NetworkRigidBodyComponent.h Include/Multiplayer/Components/NetworkTransformComponent.h Include/Multiplayer/ConnectionData/IConnectionData.h Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -51,12 +53,16 @@ set(FILES Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml + Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml + Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp Source/Components/MultiplayerComponent.cpp Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkHitVolumesComponent.cpp + Source/Components/NetworkRigidBodyComponent.cpp Source/Components/NetworkTransformComponent.cpp Source/ConnectionData/ClientToServerConnectionData.cpp Source/ConnectionData/ClientToServerConnectionData.h From 3973e1d3e5d9febff33ce3b473532bdf861c72c8 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Tue, 14 Sep 2021 09:14:55 -0700 Subject: [PATCH 2/7] Moved NetworkCharacterComponent out of MPSample and into MP gem Signed-off-by: Gene Walters --- Gems/Multiplayer/Code/CMakeLists.txt | 1 + .../Components/NetworkCharacterComponent.h | 69 ++++++ ...etworkCharacterComponent.AutoComponent.xml | 12 + .../Components/NetworkCharacterComponent.cpp | 214 ++++++++++++++++++ Gems/Multiplayer/Code/multiplayer_files.cmake | 3 + 5 files changed, 299 insertions(+) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h create mode 100644 Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml create mode 100644 Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 256f1bceeb..49b404fb1c 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -27,6 +27,7 @@ ly_add_target( AZ::AzNetworking PRIVATE Gem::EMotionFXStaticLib + Gem::PhysX.Static AUTOGEN_RULES *.AutoPackets.xml,AutoPackets_Header.jinja,$path/$fileprefix.AutoPackets.h *.AutoPackets.xml,AutoPackets_Inline.jinja,$path/$fileprefix.AutoPackets.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h new file mode 100644 index 0000000000..8939284d17 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace Physics +{ + class Character; +} + +namespace Multiplayer +{ + class NetworkCharacterComponent + : public NetworkCharacterComponentBase + , private PhysX::CharacterGameplayRequestBus::Handler + { + friend class NetworkCharacterComponentController; + + public: + AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkCharacterComponent, s_networkCharacterComponentConcreteUuid, Multiplayer::NetworkCharacterComponentBase) + + static void Reflect(AZ::ReflectContext* context); + + NetworkCharacterComponent(); + + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); + } + + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + private: + void OnTranslationChangedEvent(const AZ::Vector3& translation); + void OnSyncRewind(); + + // CharacterGameplayRequestBus + bool IsOnGround() const override; + float GetGravityMultiplier() const override { return {}; } + void SetGravityMultiplier([[maybe_unused]] float gravityMultiplier) override {} + AZ::Vector3 GetFallingVelocity() const override { return {}; } + void SetFallingVelocity([[maybe_unused]] const AZ::Vector3& fallingVelocity) override {} + + Physics::Character* m_physicsCharacter = nullptr; + Multiplayer::EntitySyncRewindEvent::Handler m_syncRewindHandler = Multiplayer::EntitySyncRewindEvent::Handler([this]() { OnSyncRewind(); }); + AZ::Event::Handler m_translationEventHandler; + }; + + class NetworkCharacterComponentController + : public NetworkCharacterComponentControllerBase + { + public: + NetworkCharacterComponentController(NetworkCharacterComponent& parent); + + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + + AZ::Vector3 TryMoveWithVelocity(const AZ::Vector3& velocity, float deltaTime); + }; +} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml new file mode 100644 index 0000000000..83e15800e0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp new file mode 100644 index 0000000000..31be4a490d --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Multiplayer +{ + + bool CollisionLayerBasedControllerFilter(const physx::PxController& controllerA, const physx::PxController& controllerB) + { + PHYSX_SCENE_READ_LOCK(controllerA.getActor()->getScene()); + physx::PxRigidDynamic* actorA = controllerA.getActor(); + physx::PxRigidDynamic* actorB = controllerB.getActor(); + + if (actorA && actorA->getNbShapes() > 0 && actorB && actorB->getNbShapes() > 0) + { + physx::PxShape* shapeA = nullptr; + actorA->getShapes(&shapeA, 1, 0); + physx::PxFilterData filterDataA = shapeA->getSimulationFilterData(); + physx::PxShape* shapeB = nullptr; + actorB->getShapes(&shapeB, 1, 0); + physx::PxFilterData filterDataB = shapeB->getSimulationFilterData(); + return PhysX::Utils::Collision::ShouldCollide(filterDataA, filterDataB); + } + + return true; + } + + physx::PxQueryHitType::Enum CollisionLayerBasedObjectPreFilter( + const physx::PxFilterData& filterData, + const physx::PxShape* shape, + const physx::PxRigidActor* actor, + [[maybe_unused]] physx::PxHitFlags& queryFlags) + { + // non-kinematic dynamic bodies should not impede the movement of the character + if (actor->getConcreteType() == physx::PxConcreteType::eRIGID_DYNAMIC) + { + const physx::PxRigidDynamic* rigidDynamic = static_cast(actor); + + bool isKinematic = (rigidDynamic->getRigidBodyFlags() & physx::PxRigidBodyFlag::eKINEMATIC); + if (isKinematic) + { + const PhysX::ActorData* actorData = PhysX::Utils::GetUserData(rigidDynamic); + if (actorData) + { + const AZ::EntityId entityId = actorData->GetEntityId(); + + if (Multiplayer::NetworkRigidBodyRequestBus::FindFirstHandler(entityId) != nullptr) + { + // Network rigid bodies are kinematic on the client but dynamic on the server, + // hence filtering treats these actors as dynamic to support client prediction and avoid desyncs + isKinematic = false; + } + } + } + + if (!isKinematic) + { + return physx::PxQueryHitType::eNONE; + } + } + + // all other cases should be determined by collision filters + if (PhysX::Utils::Collision::ShouldCollide(filterData, shape->getSimulationFilterData())) + { + return physx::PxQueryHitType::eBLOCK; + } + + return physx::PxQueryHitType::eNONE; + } + + void NetworkCharacterComponent::NetworkCharacterComponent::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + } + NetworkCharacterComponentBase::Reflect(context); + } + + NetworkCharacterComponent::NetworkCharacterComponent() + : m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) + { + ; + } + + void NetworkCharacterComponent::OnInit() + { + ; + } + + void NetworkCharacterComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + Physics::CharacterRequests* characterRequests = Physics::CharacterRequestBus::FindFirstHandler(GetEntityId()); + m_physicsCharacter = (characterRequests != nullptr) ? characterRequests->GetCharacter() : nullptr; + GetNetBindComponent()->AddEntitySyncRewindEventHandler(m_syncRewindHandler); + + if (m_physicsCharacter) + { + auto controller = static_cast(m_physicsCharacter); + controller->SetFilterFlags(physx::PxQueryFlag::eSTATIC | physx::PxQueryFlag::eDYNAMIC | physx::PxQueryFlag::ePREFILTER); + if (auto callbackManager = controller->GetCallbackManager()) + { + callbackManager->SetControllerFilter(CollisionLayerBasedControllerFilter); + callbackManager->SetObjectPreFilter(CollisionLayerBasedObjectPreFilter); + } + } + + if (!HasController()) + { + GetNetworkTransformComponent()->TranslationAddEvent(m_translationEventHandler); + } + } + + void NetworkCharacterComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkCharacterComponent::OnTranslationChangedEvent([[maybe_unused]] const AZ::Vector3& translation) + { + OnSyncRewind(); + } + + void NetworkCharacterComponent::OnSyncRewind() + { + if (m_physicsCharacter == nullptr) + { + return; + } + + const AZ::Vector3 currPosition = m_physicsCharacter->GetBasePosition(); + if (!currPosition.IsClose(GetNetworkTransformComponent()->GetTranslation())) + { + uint32_t frameId = static_cast(Multiplayer::GetNetworkTime()->GetHostFrameId()); + m_physicsCharacter->SetFrameId(frameId); + //m_physicsCharacter->SetBasePosition(GetNetworkTransformComponent()->GetTranslation()); + } + } + + bool NetworkCharacterComponent::IsOnGround() const + { + auto pxController = static_cast(m_physicsCharacter->GetNativePointer()); + if (!pxController) + { + return true; + } + + physx::PxControllerState state; + pxController->getState(state); + return state.touchedActor != nullptr || (state.collisionFlags & physx::PxControllerCollisionFlag::eCOLLISION_DOWN) != 0; + } + + NetworkCharacterComponentController::NetworkCharacterComponentController(NetworkCharacterComponent& parent) + : NetworkCharacterComponentControllerBase(parent) + { + ; + } + + void NetworkCharacterComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + void NetworkCharacterComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + ; + } + + AZ::Vector3 NetworkCharacterComponentController::TryMoveWithVelocity(const AZ::Vector3& velocity, [[maybe_unused]] float deltaTime) + { + // Ensure any entities that we might interact with are properly synchronized to their rewind state + if (IsAuthority()) + { + const AZ::Aabb entityStartBounds = AZ::Interface::Get()->GetEntityLocalBoundsUnion(GetEntity()->GetId()); + const AZ::Aabb entityFinalBounds = entityStartBounds.GetTranslated(velocity); + AZ::Aabb entitySweptBounds = entityStartBounds; + entitySweptBounds.AddAabb(entityFinalBounds); + Multiplayer::GetNetworkTime()->SyncEntitiesToRewindState(entitySweptBounds); + } + + if ((GetParent().m_physicsCharacter == nullptr) || (velocity.GetLengthSq() <= 0.0f)) + { + return GetEntity()->GetTransform()->GetWorldTranslation(); + } + GetParent().m_physicsCharacter->AddVelocity(velocity); + GetParent().m_physicsCharacter->ApplyRequestedVelocity(deltaTime); + GetEntity()->GetTransform()->SetWorldTranslation(GetParent().m_physicsCharacter->GetBasePosition()); + AZLOG + ( + NET_Movement, + "Moved to position %f x %f x %f", + GetParent().m_physicsCharacter->GetBasePosition().GetX(), + GetParent().m_physicsCharacter->GetBasePosition().GetY(), + GetParent().m_physicsCharacter->GetBasePosition().GetZ() + ); + return GetEntity()->GetTransform()->GetWorldTranslation(); + } +} diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index ca5a52579c..54311a0abc 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -18,6 +18,7 @@ set(FILES Include/Multiplayer/Components/MultiplayerController.h Include/Multiplayer/Components/MultiplayerComponentRegistry.h Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkCharacterComponent.h Include/Multiplayer/Components/NetworkHitVolumesComponent.h Include/Multiplayer/Components/NetworkRigidBodyComponent.h Include/Multiplayer/Components/NetworkTransformComponent.h @@ -53,6 +54,7 @@ set(FILES Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/MultiplayerEditor.AutoPackets.xml + Source/AutoGen/NetworkCharacterComponent.AutoComponent.xml Source/AutoGen/NetworkHitVolumesComponent.AutoComponent.xml Source/AutoGen/NetworkRigidBodyComponent.AutoComponent.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml @@ -61,6 +63,7 @@ set(FILES Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp Source/Components/NetBindComponent.cpp + Source/Components/NetworkCharacterComponent.cpp Source/Components/NetworkHitVolumesComponent.cpp Source/Components/NetworkRigidBodyComponent.cpp Source/Components/NetworkTransformComponent.cpp From 0cda54c84da922cc77d67c388dba43cf67d9562c Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 15 Sep 2021 09:08:06 -0700 Subject: [PATCH 3/7] Fixing copyright headers. Also removing some #includes that HitVolumes didn't need Signed-off-by: Gene Walters --- .../Multiplayer/Components/NetworkCharacterComponent.h | 5 +++-- .../Multiplayer/Components/NetworkHitVolumesComponent.h | 5 +++-- .../Code/Source/Components/NetworkCharacterComponent.cpp | 5 +++-- .../Code/Source/Components/NetworkHitVolumesComponent.cpp | 7 +++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 8939284d17..2599155346 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h index 74f6dcc571..1b80ba85c0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index 31be4a490d..583f01f92a 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp index e95afbdc86..0dd5fa8a01 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp @@ -1,6 +1,7 @@ /* - * Copyright (c) Contributors to the Open 3D Engine Project - * + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ @@ -11,10 +12,8 @@ #include #include #include -#include #include #include -#include namespace Multiplayer { From ae8ed4462fc697a883aafe7151688fb68a6522cd Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 15 Sep 2021 18:31:35 -0700 Subject: [PATCH 4/7] Small fix for Linux compiler Signed-off-by: Gene Walters --- .../Include/Multiplayer/Components/NetworkCharacterComponent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 2599155346..97dfcd4328 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -10,6 +10,7 @@ #include #include +#include namespace Physics { From 95a1b9c1a3cc83c80ce8972e6e0c8a33f847403f Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Fri, 17 Sep 2021 15:37:18 -0700 Subject: [PATCH 5/7] Minor commenting improvements based on PR feedback Signed-off-by: Gene Walters --- .../Components/NetworkCharacterComponent.h | 15 +++++++++++++-- .../Components/NetworkCharacterComponent.cpp | 6 ------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h index 97dfcd4328..478c925299 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkCharacterComponent.h @@ -19,6 +19,8 @@ namespace Physics namespace Multiplayer { + //! NetworkCharacterComponent + //! Provides multiplayer support for game-play player characters. class NetworkCharacterComponent : public NetworkCharacterComponentBase , private PhysX::CharacterGameplayRequestBus::Handler @@ -37,7 +39,8 @@ namespace Multiplayer incompatible.push_back(AZ_CRC_CE("NetworkRigidBodyService")); } - void OnInit() override; + // AZ::Component + void OnInit() override {} void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; @@ -46,7 +49,7 @@ namespace Multiplayer void OnSyncRewind(); // CharacterGameplayRequestBus - bool IsOnGround() const override; + bool IsOnGround() const override; float GetGravityMultiplier() const override { return {}; } void SetGravityMultiplier([[maybe_unused]] float gravityMultiplier) override {} AZ::Vector3 GetFallingVelocity() const override { return {}; } @@ -57,15 +60,23 @@ namespace Multiplayer AZ::Event::Handler m_translationEventHandler; }; + //! NetworkCharacterComponentController + //! This is the network controller for NetworkCharacterComponent. + //! Class provides the ability to move characters in physical space while keeping the network in-sync. class NetworkCharacterComponentController : public NetworkCharacterComponentControllerBase { public: NetworkCharacterComponentController(NetworkCharacterComponent& parent); + // NetworkCharacterComponentControllerBase void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + //! TryMoveWithVelocity + //! Will move this character entity kinematically through physical world while also ensuring the network stays in-sync. + //! Velocity will be applied over delta-time to determine the movement amount. + //! Returns this entity's world-space position after the move. AZ::Vector3 TryMoveWithVelocity(const AZ::Vector3& velocity, float deltaTime); }; } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp index 583f01f92a..b14fc8761f 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkCharacterComponent.cpp @@ -97,12 +97,6 @@ namespace Multiplayer NetworkCharacterComponent::NetworkCharacterComponent() : m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) { - ; - } - - void NetworkCharacterComponent::OnInit() - { - ; } void NetworkCharacterComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) From 465f91c7f5cc1204243ad40afc4ad255d7089de6 Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Mon, 20 Sep 2021 14:59:28 -0700 Subject: [PATCH 6/7] Fix compiler error because EntityPreRenderEvent only takes one float for deltaTime now Signed-off-by: Gene Walters --- .../Multiplayer/Components/NetworkHitVolumesComponent.h | 2 +- .../Code/Source/Components/NetworkHitVolumesComponent.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h index 1b80ba85c0..215359f209 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHitVolumesComponent.h @@ -64,7 +64,7 @@ namespace Multiplayer void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; private: - void OnPreRender(float deltaTime, float blendFactor); + void OnPreRender(float deltaTime); void OnTransformUpdate(const AZ::Transform& transform); void OnSyncRewind(); diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp index 0dd5fa8a01..c14bb643e1 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHitVolumesComponent.cpp @@ -109,7 +109,7 @@ namespace Multiplayer NetworkHitVolumesComponent::NetworkHitVolumesComponent() : m_syncRewindHandler([this]() { OnSyncRewind(); }) - , m_preRenderHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) + , m_preRenderHandler([this](float deltaTime) { OnPreRender(deltaTime); }) , m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformUpdate(worldTm); }) { ; @@ -135,7 +135,7 @@ namespace Multiplayer EMotionFX::Integration::ActorComponentNotificationBus::Handler::BusDisconnect(); } - void NetworkHitVolumesComponent::OnPreRender([[maybe_unused]] float deltaTime, [[maybe_unused]] float blendFactor) + void NetworkHitVolumesComponent::OnPreRender([[maybe_unused]] float deltaTime) { if (m_animatedHitVolumes.size() <= 0) { From 3685f1bff2a4939d21fbab2fe2f00cb1c945ae46 Mon Sep 17 00:00:00 2001 From: jromnoa <80134229+jromnoa@users.noreply.github.com> Date: Mon, 20 Sep 2021 15:21:23 -0700 Subject: [PATCH 7/7] move shader asset builder test into test_Atom_MainSuite_Optimized.py, update CMakeLists.txt, and move all imports inside the test class for hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges.py (#4205) Signed-off-by: jromnoa --- .../PythonTests/atom_renderer/CMakeLists.txt | 11 -- ...pilesShaderAsChainOfDependenciesChanges.py | 167 +++++++++--------- .../test_Atom_MainSuite_Optimized.py | 3 + .../test_Atom_ShaderBuildPipelineSuite.py | 19 -- 4 files changed, 86 insertions(+), 114 deletions(-) delete mode 100644 AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_ShaderBuildPipelineSuite.py diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt index f91423324d..117c710824 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt @@ -65,15 +65,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT COMPONENT Atom ) - ly_add_pytest( - NAME AutomatedTesting::AtomRenderer_HydraTests_ShaderBuildPipeline - TEST_SUITE main - PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_ShaderBuildPipelineSuite.py - TEST_SERIAL - TIMEOUT 600 - RUNTIME_DEPENDENCIES - AssetProcessor - AutomatedTesting.Assets - Editor - ) endif() diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges.py index a05420d960..f13227aa9d 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges.py @@ -3,78 +3,19 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT - """ -import os -import shutil - -def _copy_file(src_file, src_path, target_file, target_path): - # type: (str, str, str, str) -> None - """ - Copies the [src_file] located in [src_path] to the [target_file] located at [target_path]. - Leaves the [target_file] unlocked for reading and writing privileges - :param src_file: The source file to copy (file name) - :param src_path: The source file's path - :param target_file: The target file to copy into (file name) - :param target_path: The target file's path - :return: None - """ - target_file_path = os.path.join(target_path, target_file) - src_file_path = os.path.join(src_path, src_file) - if os.path.exists(target_file_path): - fs.unlock_file(target_file_path) - shutil.copyfile(src_file_path, target_file_path) - -def _copy_tmp_files_in_order(src_directory, file_list, dst_directory, wait_time_in_between = 0.0): - # type: (str, list, str, float) -> None - """ - This function assumes that for each file name listed in @file_list - there's file named "@filename.txt" which the original source file - but they will be copied with just the @filename (.txt removed). - """ - for filename in file_list: - src_name = f"{filename}.txt" - _copy_file(src_name, src_directory, filename, dst_directory) - if wait_time_in_between > 0.0: - print(f"Created {filename} in {dst_directory}") - general.idle_wait(wait_time_in_between) - - -def _remove_file(src_file, src_path): - # type: (str, str) -> None - """ - Removes the [src_file] located in [src_path]. - :param src_file: The source file to copy (file name) - :param src_path: The source file's path - :return: None - """ - src_file_path = os.path.join(src_path, src_file) - if os.path.exists(src_file_path): - fs.unlock_file(src_file_path) - os.remove(src_file_path) - - -def _remove_files(directory, file_list): - for filename in file_list: - _remove_file(filename, directory) - - -def _asset_exists(cache_relative_path): - asset_id = azasset.AssetCatalogRequestBus(azbus.Broadcast, "GetAssetIdByPath", cache_relative_path, azmath.Uuid(), False) - return asset_id.is_valid() - -# List of results that we want to check, this is not 100% necessary but it's a good -# practice to make it easier to debug tests. -# Here we define a tuple of tests -class Results(): - azshader_was_removed = ("azshader was removed", "Failed to remove azshader") - azshader_was_compiled = ("azshader was compiled", "Failed to compile azshader") +# fmt: off +class Tests(): + azshader_was_removed = ("azshader was removed", "Failed to remove azshader") + azshader_was_compiled = ("azshader was compiled", "Failed to compile azshader") + no_error_occurred = ("No errors detected", "Errors were detected") +# fmt: on def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): """ - This test validates [ATOM-5441] Shader Builders May Fail When Multiple New Files Are Added + This test validates: "Shader Builders May Fail When Multiple New Files Are Added" It creates source assets to compile a particular shader. 1- The first phase generates the source assets out of order and slowly. The AP should wakeup each time one of the source dependencies appears but will fail each time. Only when the @@ -82,6 +23,71 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): 2- The second phase is similar as above, except that all source assets will be created at once and We also expect that in the end the shader is built successfully. """ + import os + import shutil + + import azlmbr.asset as azasset + import azlmbr.bus as azbus + import azlmbr.legacy.general as general + import azlmbr.math as azmath + + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.utils import Tracer + import ly_test_tools.environment.file_system as fs + + def _copy_file(src_file, src_path, target_file, target_path): + # type: (str, str, str, str) -> None + """ + Copies the [src_file] located in [src_path] to the [target_file] located at [target_path]. + Leaves the [target_file] unlocked for reading and writing privileges + :param src_file: The source file to copy (file name) + :param src_path: The source file's path + :param target_file: The target file to copy into (file name) + :param target_path: The target file's path + :return: None + """ + target_file_path = os.path.join(target_path, target_file) + src_file_path = os.path.join(src_path, src_file) + if os.path.exists(target_file_path): + fs.unlock_file(target_file_path) + shutil.copyfile(src_file_path, target_file_path) + + def _copy_tmp_files_in_order(src_directory, file_list, dst_directory, wait_time_in_between=0.0): + # type: (str, list, str, float) -> None + """ + This function assumes that for each file name listed in @file_list + there's file named "@filename.txt" which the original source file + but they will be copied with just the @filename (.txt removed). + """ + for filename in file_list: + src_name = f"{filename}.txt" + _copy_file(src_name, src_directory, filename, dst_directory) + if wait_time_in_between > 0.0: + print(f"Created {filename} in {dst_directory}") + general.idle_wait(wait_time_in_between) + + def _remove_file(src_file, src_path): + # type: (str, str) -> None + """ + Removes the [src_file] located in [src_path]. + :param src_file: The source file to copy (file name) + :param src_path: The source file's path + :return: None + """ + src_file_path = os.path.join(src_path, src_file) + if os.path.exists(src_file_path): + fs.unlock_file(src_file_path) + os.remove(src_file_path) + + def _remove_files(directory, file_list): + for filename in file_list: + _remove_file(filename, directory) + + def _asset_exists(cache_relative_path): + asset_id = azasset.AssetCatalogRequestBus(azbus.Broadcast, "GetAssetIdByPath", cache_relative_path, + azmath.Uuid(), False) + return asset_id.is_valid() + # Required for automated tests helper.init_idle() @@ -115,14 +121,14 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): azshader_name = "assets/dependencyvalidation.azshader" helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0) - Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name)) _copy_tmp_files_in_order(src_assets_subdir, file_list, game_asset_path, 1.0) # Give enough time to AP to compile the shader helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0) - Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name)) # The first part was about compiling the shader under normal conditions. # Let's remove the files from the previous phase and will proceed @@ -130,7 +136,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): # ShaderAssetBuilder will only succeed when the last file becomes visible. _remove_files(game_asset_path, reverse_file_list) helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0) - Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name)) # Remark, if you are running this test manually from the Editor with "pyRunFile", # You'll notice how the AP issues notifications that it fails to compile the shader @@ -148,7 +154,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): # Give enough time to AP to compile the shader helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0) - Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name)) # The last phase of the test puts stress on potential race conditions # when all required files appear as soon as possible. @@ -157,7 +163,7 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): # Remove left over files. _remove_files(game_asset_path, reverse_file_list) helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0) - Report.critical_result(Results.azshader_was_removed, not _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_removed, not _asset_exists(azshader_name)) # Now let's copy all the source files to the "Assets" folder as fast as possible. _copy_tmp_files_in_order(src_assets_subdir, reverse_file_list, game_asset_path) @@ -165,24 +171,17 @@ def ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(): # Give enough time to AP to compile the shader helper.wait_for_condition(lambda: _asset_exists(azshader_name), 60.0) - Report.critical_result(Results.azshader_was_compiled, _asset_exists(azshader_name)) + Report.critical_result(Tests.azshader_was_compiled, _asset_exists(azshader_name)) # All good, let's cleanup leftover files before closing the test. _remove_files(game_asset_path, reverse_file_list) helper.wait_for_condition(lambda: not _asset_exists(azshader_name), 5.0) + # Look for errors to raise. + helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) + Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + if __name__ == "__main__": - # All exposed python bindings are in azlmbr - import azlmbr.legacy.general as general - import azlmbr.bus as azbus - import azlmbr.asset as azasset - import azlmbr.math as azmath - - # Import report and test helper utilities from editor_python_test_tools.utils import Report - from editor_python_test_tools.utils import TestHelper as helper - from editor_python_test_tools.utils import Tracer - import ly_test_tools.environment.file_system as fs - - Report.start_test(ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges) \ No newline at end of file + Report.start_test(ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges) diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite_Optimized.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite_Optimized.py index 329d3ecb91..e206e77d60 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite_Optimized.py @@ -41,3 +41,6 @@ class TestAutomation(EditorTestSuite): class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): from atom_renderer.atom_hydra_scripts import hydra_AtomEditorComponents_DisplayMapperAdded as test_module + + class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): + from .atom_hydra_scripts import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_ShaderBuildPipelineSuite.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_ShaderBuildPipelineSuite.py deleted file mode 100644 index 9ef93ea238..0000000000 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_ShaderBuildPipelineSuite.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT - -Main suite tests for the Shader Build Pipeline. -""" -import pytest -from ly_test_tools import LAUNCHERS -from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest - -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) -class TestShaderBuildPipelineMain(EditorTestSuite): - """Holds tests for Shader Build Pipeline validation""" - - class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSingleTest): - from .atom_hydra_scripts import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module \ No newline at end of file