NetTransform replicates local transform for child entities

Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com>
This commit is contained in:
AMZN-Olex
2021-09-23 10:20:52 -04:00
parent 5ae35810ae
commit 3c46336ab0
6 changed files with 375 additions and 27 deletions
@@ -50,7 +50,7 @@ namespace Multiplayer
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
private:
void OnTransformChangedEvent(const AZ::Transform& worldTm);
void OnTransformChangedEvent(const AZ::Transform& localTm, const AZ::Transform& worldTm);
void OnParentIdChangedEvent(AZ::EntityId oldParent, AZ::EntityId newParent);
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
@@ -77,9 +77,19 @@ namespace Multiplayer
}
}
if (!GetTransformComponent()->GetWorldTM().IsClose(blendTransform))
if (GetParentEntityId() == InvalidNetEntityId)
{
GetTransformComponent()->SetWorldTM(blendTransform);
if (!GetTransformComponent()->GetWorldTM().IsClose(blendTransform))
{
GetTransformComponent()->SetWorldTM(blendTransform);
}
}
else
{
if (!GetTransformComponent()->GetLocalTM().IsClose(blendTransform))
{
GetTransformComponent()->SetLocalTM(blendTransform);
}
}
}
}
@@ -93,9 +103,19 @@ namespace Multiplayer
targetTransform.SetUniformScale(GetScale());
// Hard set the entities transform
if (!GetTransformComponent()->GetWorldTM().IsClose(targetTransform))
if (GetParentEntityId() == InvalidNetEntityId)
{
GetTransformComponent()->SetWorldTM(targetTransform);
if (!GetTransformComponent()->GetWorldTM().IsClose(targetTransform))
{
GetTransformComponent()->SetWorldTM(targetTransform);
}
}
else
{
if (!GetTransformComponent()->GetLocalTM().IsClose(targetTransform))
{
GetTransformComponent()->SetLocalTM(targetTransform);
}
}
}
@@ -117,7 +137,7 @@ namespace Multiplayer
NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent)
: NetworkTransformComponentControllerBase(parent)
, m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); })
, m_transformChangedHandler([this](const AZ::Transform& localTm, const AZ::Transform& worldTm) { OnTransformChangedEvent(localTm, worldTm); })
, m_parentIdChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId newParent) { OnParentIdChangedEvent(oldParent, newParent); })
{
;
@@ -125,11 +145,14 @@ namespace Multiplayer
void NetworkTransformComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM());
if (AzFramework::TransformComponent* parentTransform = GetParent().GetTransformComponent())
{
parentTransform->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformChangedEvent(parentTransform->GetLocalTM(), parentTransform->GetWorldTM());
GetParent().GetTransformComponent()->BindParentChangedEventHandler(m_parentIdChangedHandler);
OnParentIdChangedEvent(AZ::EntityId(), GetParent().GetTransformComponent()->GetParentId());
parentTransform->BindParentChangedEventHandler(m_parentIdChangedHandler);
OnParentIdChangedEvent(AZ::EntityId(), parentTransform->GetParentId());
}
}
void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -137,11 +160,12 @@ namespace Multiplayer
;
}
void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm)
void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& localTm, const AZ::Transform& worldTm)
{
SetRotation(worldTm.GetRotation());
SetTranslation(worldTm.GetTranslation());
SetScale(worldTm.GetUniformScale());
const AZ::Transform& localOrWorld = GetParentEntityId() == InvalidNetEntityId ? worldTm : localTm;
SetRotation(localOrWorld.GetRotation());
SetTranslation(localOrWorld.GetTranslation());
SetScale(localOrWorld.GetUniformScale());
}
void NetworkTransformComponentController::OnParentIdChangedEvent([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
@@ -150,7 +174,10 @@ namespace Multiplayer
if (parentEntity)
{
const ConstNetworkEntityHandle parentHandle(parentEntity, GetNetworkEntityTracker());
SetParentEntityId(parentHandle.GetNetEntityId());
if (parentHandle.Exists())
{
SetParentEntityId(parentHandle.GetNetEntityId());
}
}
}
}
@@ -11,6 +11,7 @@
#include <MockInterfaces.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Name/Name.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -248,17 +249,19 @@ namespace Multiplayer
void SetupEntity(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId netId, NetEntityRole role)
{
const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>();
EXPECT_NE(netBindComponent, nullptr);
netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role);
entity->Init();
if (const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>())
{
netBindComponent->PreInit(entity.get(), PrefabEntityId{ AZ::Name("test"), 1 }, netId, role);
entity->Init();
}
}
static void StopEntity(const AZStd::unique_ptr<AZ::Entity>& entity)
{
const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>();
EXPECT_NE(netBindComponent, nullptr);
netBindComponent->StopEntity();
if (const auto netBindComponent = entity->FindComponent<Multiplayer::NetBindComponent>())
{
netBindComponent->StopEntity();
}
}
static void StopAndDeactivateEntity(AZStd::unique_ptr<AZ::Entity>& entity)
@@ -311,6 +314,29 @@ namespace Multiplayer
entity->FindComponent<NetworkTransformComponent>()->NotifyStateDeltaChanges(notifyRecord);
}
void SetTranslationOnNetworkTransform(const AZStd::unique_ptr<AZ::Entity>& entity, AZ::Vector3 translation)
{
/* Derived from NetworkTransformComponent.AutoComponent.xml */
constexpr int totalBits = 6 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::Count*/;
constexpr int translationBit = 1 /*NetworkTransformComponentInternal::AuthorityToClientDirtyEnum::translation_DirtyFlag*/;
ReplicationRecord currentRecord;
currentRecord.m_authorityToClient.AddBits(totalBits);
currentRecord.m_authorityToClient.SetBit(translationBit, true);
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
static_cast<ISerializer*>(&inSerializer)->Serialize(translation,
"translation" /* Derived from NetworkTransformComponent.AutoComponent.xml */);
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
ReplicationRecord notifyRecord = currentRecord;
entity->FindComponent<NetworkTransformComponent>()->SerializeStateDeltaMessage(currentRecord, outSerializer);
entity->FindComponent<NetworkTransformComponent>()->NotifyStateDeltaChanges(notifyRecord);
}
template <typename Component>
void SetHierarchyRootFieldOnNetworkHierarchyChild(const AZStd::unique_ptr<AZ::Entity>& entity, NetEntityId value)
{
@@ -95,8 +95,9 @@ namespace UnitTest
TEST_F(MultiplayerSystemTests, TestConnectionDatum)
{
IMultiplayerConnectionMock connMock1 = IMultiplayerConnectionMock(aznumeric_cast<AzNetworking::ConnectionId>(10), AzNetworking::IpAddress(), AzNetworking::ConnectionRole::Acceptor);
IMultiplayerConnectionMock connMock2 = IMultiplayerConnectionMock(aznumeric_cast<AzNetworking::ConnectionId>(15), AzNetworking::IpAddress(), AzNetworking::ConnectionRole::Acceptor);
using namespace testing;
NiceMock<IMultiplayerConnectionMock> connMock1(aznumeric_cast<AzNetworking::ConnectionId>(10), AzNetworking::IpAddress(), AzNetworking::ConnectionRole::Acceptor);
NiceMock<IMultiplayerConnectionMock> connMock2(aznumeric_cast<AzNetworking::ConnectionId>(15), AzNetworking::IpAddress(), AzNetworking::ConnectionRole::Acceptor);
m_mpComponent->OnConnect(&connMock1);
m_mpComponent->OnConnect(&connMock2);
@@ -0,0 +1,293 @@
/*
* 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 <CommonHierarchySetup.h>
#include <MockInterfaces.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Console/Console.h>
#include <AzCore/Name/Name.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzTest/AzTest.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <NetworkEntity/EntityReplication/EntityReplicator.h>
namespace Multiplayer
{
using namespace testing;
using namespace ::UnitTest;
/*
* (Networked) Parent -> (Networked) Child
*/
class ServerNetTransformTests : public HierarchyTests
{
public:
void SetUp() override
{
HierarchyTests::SetUp();
m_root = AZStd::make_unique<EntityInfo>(1, "root", NetEntityId{ 1 }, EntityInfo::Role::Root);
m_child = AZStd::make_unique<EntityInfo>(2, "child", NetEntityId{ 2 }, EntityInfo::Role::Child);
CreateNetworkParentChild(*m_root, *m_child);
AZ::Transform rootTransform = AZ::Transform::CreateIdentity();
rootTransform.SetTranslation(AZ::Vector3::CreateOne());
m_root->m_entity->FindComponent<AzFramework::TransformComponent>()->SetWorldTM(rootTransform);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetWorldTM(rootTransform);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_root->m_entity->GetId());
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetLocalTM(AZ::Transform::CreateIdentity());
AZ::EntityBus::Broadcast(&AZ::EntityBus::Events::OnEntityActivated, m_root->m_entity->GetId());
MultiplayerTick();
}
void TearDown() override
{
m_child.reset();
m_root.reset();
HierarchyTests::TearDown();
}
void PopulateNetworkEntity(const EntityInfo& entityInfo)
{
entityInfo.m_entity->CreateComponent<AzFramework::TransformComponent>();
entityInfo.m_entity->CreateComponent<NetBindComponent>();
entityInfo.m_entity->CreateComponent<NetworkTransformComponent>();
}
void CreateNetworkParentChild(EntityInfo& root, EntityInfo& child)
{
PopulateNetworkEntity(root);
SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Authority);
PopulateNetworkEntity(child);
SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Authority);
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get());
child.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, childHandle);
child.m_replicator->Initialize(childHandle);
// Create an entity replicator for the root entity
const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get());
root.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Client, rootHandle);
root.m_replicator->Initialize(rootHandle);
root.m_entity->Activate();
child.m_entity->Activate();
}
AZStd::unique_ptr<EntityInfo> m_root;
AZStd::unique_ptr<EntityInfo> m_child;
void MultiplayerTick()
{
m_root->m_entity->FindComponent<NetBindComponent>()->NotifyPreRender(0.1f);
m_child->m_entity->FindComponent<NetBindComponent>()->NotifyPreRender(0.1f);
}
};
TEST_F(ServerNetTransformTests, SanityCheck)
{
EXPECT_EQ(
m_root->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateOne()
);
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateOne()
);
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetLocalTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
}
TEST_F(ServerNetTransformTests, NetTransformSavesLocalTransformWhenParentSet)
{
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkTransformComponent>()->GetTranslation(),
AZ::Vector3::CreateZero()
);
}
TEST_F(ServerNetTransformTests, NetTransformSavesWorldTransformWhenParentIsNotSet)
{
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
MultiplayerTick();
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkTransformComponent>()->GetTranslation(),
AZ::Vector3::CreateOne() // back at the parent translation
);
}
TEST_F(ServerNetTransformTests, ParentMovesChildNetTransformDoesntChange)
{
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkTransformComponent>()->GetTranslation(),
AZ::Vector3::CreateZero()
);
// move the parent
AZ::Transform rootTransform = AZ::Transform::CreateIdentity();
rootTransform.SetTranslation(AZ::Vector3::CreateOne() * 10.f);
m_root->m_entity->FindComponent<AzFramework::TransformComponent>()->SetWorldTM(rootTransform);
MultiplayerTick();
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateOne() * 10.f
);
// child local tm doesn't change
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetLocalTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkTransformComponent>()->GetTranslation(),
AZ::Vector3::CreateZero()
);
}
/*
* (Networked) Parent -> (Networked) Child
*/
class ClientNetTransformTests : public HierarchyTests
{
public:
void SetUp() override
{
HierarchyTests::SetUp();
m_root = AZStd::make_unique<EntityInfo>(1, "root", NetEntityId{ 1 }, EntityInfo::Role::Root);
m_child = AZStd::make_unique<EntityInfo>(2, "child", NetEntityId{ 2 }, EntityInfo::Role::Child);
CreateNetworkParentChild(*m_root, *m_child);
}
void TearDown() override
{
m_child.reset();
m_root.reset();
HierarchyTests::TearDown();
}
void PopulateNetworkEntity(const EntityInfo& entityInfo)
{
entityInfo.m_entity->CreateComponent<AzFramework::TransformComponent>();
entityInfo.m_entity->CreateComponent<NetBindComponent>();
entityInfo.m_entity->CreateComponent<NetworkTransformComponent>();
}
void CreateNetworkParentChild(EntityInfo& root, EntityInfo& child)
{
PopulateNetworkEntity(root);
SetupEntity(root.m_entity, root.m_netId, NetEntityRole::Client);
PopulateNetworkEntity(child);
SetupEntity(child.m_entity, child.m_netId, NetEntityRole::Client);
// Create an entity replicator for the child entity
const NetworkEntityHandle childHandle(child.m_entity.get(), m_networkEntityTracker.get());
child.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, childHandle);
child.m_replicator->Initialize(childHandle);
// Create an entity replicator for the root entity
const NetworkEntityHandle rootHandle(root.m_entity.get(), m_networkEntityTracker.get());
root.m_replicator = AZStd::make_unique<EntityReplicator>(*m_entityReplicationManager, m_mockConnection.get(), NetEntityRole::Authority, rootHandle);
root.m_replicator->Initialize(rootHandle);
root.m_entity->Activate();
child.m_entity->Activate();
}
AZStd::unique_ptr<EntityInfo> m_root;
AZStd::unique_ptr<EntityInfo> m_child;
void MultiplayerTick()
{
m_root->m_entity->FindComponent<NetBindComponent>()->NotifyPreRender(0.1f);
m_child->m_entity->FindComponent<NetBindComponent>()->NotifyPreRender(0.1f);
}
};
TEST_F(ClientNetTransformTests, ClientSetsLocalTmWhenParentIsSet)
{
SetTranslationOnNetworkTransform(m_root->m_entity, AZ::Vector3::CreateOne());
SetParentIdOnNetworkTransform(m_child->m_entity, NetEntityId{ 1 });
SetTranslationOnNetworkTransform(m_child->m_entity, AZ::Vector3::CreateZero());
AZ::EntityBus::Broadcast(&AZ::EntityBus::Events::OnEntityActivated, m_root->m_entity->GetId());
MultiplayerTick();
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateOne()
);
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetLocalTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
}
TEST_F(ClientNetTransformTests, ClientSetsWorldTmWhenParentIsNotSet)
{
SetTranslationOnNetworkTransform(m_root->m_entity, AZ::Vector3::CreateOne());
SetTranslationOnNetworkTransform(m_child->m_entity, AZ::Vector3::CreateZero());
AZ::EntityBus::Broadcast(&AZ::EntityBus::Events::OnEntityActivated, m_root->m_entity->GetId());
MultiplayerTick();
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetLocalTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
}
TEST_F(ClientNetTransformTests, ChildFollowsWhenParentMovesOnServer)
{
SetTranslationOnNetworkTransform(m_root->m_entity, AZ::Vector3::CreateOne());
SetParentIdOnNetworkTransform(m_child->m_entity, NetEntityId{ 1 });
SetTranslationOnNetworkTransform(m_child->m_entity, AZ::Vector3::CreateZero());
AZ::EntityBus::Broadcast(&AZ::EntityBus::Events::OnEntityActivated, m_root->m_entity->GetId());
MultiplayerTick();
// now parent moves
SetTranslationOnNetworkTransform(m_root->m_entity, AZ::Vector3::CreateOne() * 2.f);
MultiplayerTick();
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetWorldTM().GetTranslation(),
AZ::Vector3::CreateOne() * 2.f
);
EXPECT_EQ(
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->GetLocalTM().GetTranslation(),
AZ::Vector3::CreateZero()
);
}
}
@@ -7,13 +7,14 @@
#
set(FILES
Tests/Main.cpp
Tests/MockInterfaces.h
Tests/ClientHierarchyTests.cpp
Tests/ServerHierarchyTests.cpp
Tests/CommonHierarchySetup.h
Tests/IMultiplayerConnectionMock.h
Tests/Main.cpp
Tests/MockInterfaces.h
Tests/MultiplayerSystemTests.cpp
Tests/NetworkTransformTests.cpp
Tests/RewindableContainerTests.cpp
Tests/RewindableObjectTests.cpp
Tests/ServerHierarchyTests.cpp
)