Merge pull request #4023 from aws-lumberyard-dev/MPSpawnableRework
Reworked net entities instantiation in order to fix entity references…
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/EBus/Event.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -75,6 +76,15 @@ namespace Multiplayer
|
||||
const AZ::Transform& transform
|
||||
) = 0;
|
||||
|
||||
//! Requests a network spawnable to instantiate at a given transform
|
||||
//! This is an async function. The instantiated entities are not available immediately but will be constructed by the spawnable system
|
||||
//! The spawnable ticket has to be kept for the whole lifetime of the entities
|
||||
//! @param netSpawnable the network spawnable to spawn
|
||||
//! @param transform the transform where the spawnable should be spawned
|
||||
//! @return the ticket for managing the spawned entities
|
||||
[[nodiscard]] virtual AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform) = 0;
|
||||
|
||||
//! Configures new networked entity
|
||||
//! @param netEntity the entity to setup
|
||||
//! @param prefabEntryId the name of the spawnable the entity originated from
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <Source/MultiplayerGem.h>
|
||||
#include <Source/MultiplayerSystemComponent.h>
|
||||
#include <Source/AutoGen/AutoComponentTypes.h>
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
|
||||
@@ -23,7 +22,6 @@ namespace Multiplayer
|
||||
AzNetworking::NetworkingSystemComponent::CreateDescriptor(),
|
||||
MultiplayerSystemComponent::CreateDescriptor(),
|
||||
NetBindComponent::CreateDescriptor(),
|
||||
NetBindMarkerComponent::CreateDescriptor(),
|
||||
NetworkSpawnableHolderComponent::CreateDescriptor(),
|
||||
});
|
||||
|
||||
|
||||
@@ -465,8 +465,60 @@ namespace Multiplayer
|
||||
return netEntityId;
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnRootSpawnableAssigned(
|
||||
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> NetworkEntityManager::RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform)
|
||||
{
|
||||
// Prepare the parameters for the spawning process
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_priority = AzFramework::SpawnablePriority_High;
|
||||
|
||||
const AZ::Name netSpawnableName =
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(netSpawnable.GetId());
|
||||
|
||||
if (netSpawnableName.IsEmpty())
|
||||
{
|
||||
AZ_Error("NetworkEntityManager", false,
|
||||
"RequestNetSpawnableInstantiation: Requested spawnable %s doesn't exist in the NetworkSpawnableLibrary. Please make sure it is a network spawnable",
|
||||
netSpawnable.GetHint().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene
|
||||
optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = transform]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)
|
||||
{
|
||||
bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false);
|
||||
|
||||
for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast<uint32_t>(entities.size());
|
||||
netEntityIndex < entitiesSize; ++netEntityIndex)
|
||||
{
|
||||
AZ::Entity* netEntity = *(entities.begin() + netEntityIndex);
|
||||
|
||||
if (shouldUpdateTransform)
|
||||
{
|
||||
AzFramework::TransformComponent* netEntityTransform =
|
||||
netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
|
||||
AZ::Transform worldTm = netEntityTransform->GetWorldTM();
|
||||
worldTm = rootTransform * worldTm;
|
||||
netEntityTransform->SetWorldTM(worldTm);
|
||||
}
|
||||
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = netSpawnableName;
|
||||
prefabEntityId.m_entityOffset = netEntityIndex;
|
||||
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities
|
||||
auto ticket = AZStd::make_unique<AzFramework::EntitySpawnTicket>(netSpawnable);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
|
||||
return ticket;
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable,
|
||||
[[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
auto* multiplayer = GetMultiplayer();
|
||||
const auto agentType = multiplayer->GetAgentType();
|
||||
@@ -479,7 +531,6 @@ namespace Multiplayer
|
||||
|
||||
void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
// TODO: Do we need to clear all entities here?
|
||||
auto* multiplayer = GetMultiplayer();
|
||||
const auto agentType = multiplayer->GetAgentType();
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ namespace Multiplayer
|
||||
const AZ::Transform& transform
|
||||
) override;
|
||||
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
|
||||
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform) override;
|
||||
|
||||
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
|
||||
|
||||
uint32_t GetEntityCount() const override;
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Multiplayer
|
||||
NetworkEntityHandle Get(NetEntityId netEntityId);
|
||||
ConstNetworkEntityHandle Get(NetEntityId netEntityId) const;
|
||||
|
||||
//! Returns Net Entity ID for a given AZ Entity ID.
|
||||
NetEntityId Get(const AZ::EntityId& entityId) const;
|
||||
|
||||
//! Returns true if the netEntityId exists.
|
||||
|
||||
@@ -1,115 +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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
#include <Multiplayer/INetworkSpawnableLibrary.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
|
||||
->Version(1)
|
||||
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
|
||||
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
|
||||
}
|
||||
}
|
||||
|
||||
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
|
||||
{
|
||||
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
if (!spawnable)
|
||||
{
|
||||
asset =
|
||||
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
|
||||
|
||||
spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
}
|
||||
|
||||
return spawnable;
|
||||
}
|
||||
|
||||
|
||||
void NetBindMarkerComponent::Activate()
|
||||
{
|
||||
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
|
||||
const bool spawnImmediately =
|
||||
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
|
||||
|
||||
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
|
||||
{
|
||||
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->GetWorldTM();
|
||||
auto preInsertionCallback =
|
||||
[worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()]
|
||||
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)
|
||||
{
|
||||
if (entities.size() == 1)
|
||||
{
|
||||
AZ::Entity* netEntity = *entities.begin();
|
||||
|
||||
auto* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
transformComponent->SetWorldTM(worldTm);
|
||||
|
||||
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = spawnableName;
|
||||
prefabEntityId.m_entityOffset = static_cast<uint32_t>(netEntityIndex);
|
||||
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size());
|
||||
}
|
||||
};
|
||||
|
||||
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs));
|
||||
}
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::Deactivate()
|
||||
{
|
||||
if(m_netSpawnTicket.IsValid())
|
||||
{
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
|
||||
}
|
||||
}
|
||||
|
||||
size_t NetBindMarkerComponent::GetNetEntityIndex() const
|
||||
{
|
||||
return m_netEntityIndex;
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex)
|
||||
{
|
||||
m_netEntityIndex = netEntityIndex;
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
|
||||
{
|
||||
m_networkSpawnableAsset = networkSpawnableAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
|
||||
{
|
||||
return m_networkSpawnableAsset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
//! @class NetBindMarkerComponent
|
||||
//! @brief Component for tracking net entities in the original non-networked spawnable.
|
||||
class NetBindMarkerComponent final : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(NetBindMarkerComponent, "{40612C1B-427D-45C6-A2F0-04E16DF5B718}");
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
NetBindMarkerComponent() = default;
|
||||
~NetBindMarkerComponent() override = default;
|
||||
|
||||
//! AZ::Component overrides.
|
||||
//! @{
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
//! @}
|
||||
|
||||
size_t GetNetEntityIndex() const;
|
||||
void SetNetEntityIndex(size_t val);
|
||||
|
||||
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
|
||||
|
||||
private:
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad};
|
||||
size_t m_netEntityIndex = 0;
|
||||
AzFramework::EntitySpawnTicket m_netSpawnTicket;
|
||||
};
|
||||
} // namespace Multiplayer
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <Multiplayer/IMultiplayerTools.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Pipeline/NetworkPrefabProcessor.h>
|
||||
#include <Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
|
||||
@@ -46,7 +45,7 @@ namespace Multiplayer
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(1);
|
||||
serializeContext->Class<NetworkPrefabProcessor, PrefabProcessor>()->Version(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +136,6 @@ namespace Multiplayer
|
||||
networkSpawnableAsset.Create(networkSpawnable->GetId());
|
||||
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
|
||||
size_t netEntitiesIndexCounter = 0;
|
||||
|
||||
for (auto* prefabEntity : prefabNetEntities)
|
||||
{
|
||||
Instance* instance = netEntityToInstanceMap[prefabEntity];
|
||||
@@ -148,30 +145,11 @@ namespace Multiplayer
|
||||
AZ_Assert(netEntity, "Unable to detach entity %s [%s] from the source prefab instance",
|
||||
prefabEntity->GetName().c_str(), entityId.ToString().c_str());
|
||||
|
||||
// Net entity will need a new ID to avoid IDs collision
|
||||
netEntity->SetId(AZ::Entity::MakeId());
|
||||
netEntity->InvalidateDependencies();
|
||||
netEntity->EvaluateDependencies();
|
||||
|
||||
// Insert the entity into the target net spawnable
|
||||
netSpawnableEntities.emplace_back(netEntity);
|
||||
|
||||
// Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable
|
||||
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName());
|
||||
breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault());
|
||||
|
||||
// Marker component is responsible to spawning entities based on the index.
|
||||
NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
|
||||
netBindMarkerComponent->SetNetEntityIndex(netEntitiesIndexCounter);
|
||||
netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
|
||||
|
||||
// Copy the transform component from the original entity to have the correct transform and parent-child relationship
|
||||
AzFramework::TransformComponent* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
|
||||
|
||||
instance->AddEntity(*breadcrumbEntity);
|
||||
|
||||
netEntitiesIndexCounter++;
|
||||
}
|
||||
|
||||
// Add net spawnable asset holder to the prefab root
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -28,10 +30,32 @@ namespace Multiplayer
|
||||
|
||||
void NetworkSpawnableHolderComponent::Activate()
|
||||
{
|
||||
const auto agentType = GetMultiplayer()->GetAgentType();
|
||||
const bool shouldSpawnNetEntities =
|
||||
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
|
||||
|
||||
if(shouldSpawnNetEntities)
|
||||
{
|
||||
AZ::Transform rootEntityTransform = AZ::Transform::CreateIdentity();
|
||||
|
||||
AzFramework::TransformComponent* rootEntityTransformComponent =
|
||||
GetEntity()->FindComponent<AzFramework::TransformComponent>();
|
||||
if (rootEntityTransformComponent)
|
||||
{
|
||||
rootEntityTransform = rootEntityTransformComponent->GetWorldTM();
|
||||
}
|
||||
|
||||
INetworkEntityManager* networkEntityManager = GetNetworkEntityManager();
|
||||
AZ_Assert(networkEntityManager != nullptr,
|
||||
"Network Entity Manager must be initialized before NetworkSpawnableHolderComponent is activated");
|
||||
|
||||
m_netSpawnableTicket = networkEntityManager->RequestNetSpawnableInstantiation(m_networkSpawnableAsset, rootEntityTransform);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkSpawnableHolderComponent::Deactivate()
|
||||
{
|
||||
m_netSpawnableTicket.reset();
|
||||
}
|
||||
|
||||
void NetworkSpawnableHolderComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
|
||||
@@ -39,7 +63,7 @@ namespace Multiplayer
|
||||
m_networkSpawnableAsset = networkSpawnableAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset()
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetworkSpawnableHolderComponent::GetNetworkSpawnableAsset() const
|
||||
{
|
||||
return m_networkSpawnableAsset;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -33,9 +34,10 @@ namespace Multiplayer
|
||||
//! @}
|
||||
|
||||
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset();
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
|
||||
|
||||
private:
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{ AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> m_netSpawnableTicket;
|
||||
};
|
||||
} // namespace Multiplayer
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <AzTest/GemTestEnvironment.h>
|
||||
#include <QApplication>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <UnitTest/ToolsTestApplication.h>
|
||||
|
||||
@@ -30,7 +29,6 @@ namespace Multiplayer
|
||||
{
|
||||
AZStd::vector<AZ::ComponentDescriptor*> descriptors({
|
||||
NetBindComponent::CreateDescriptor(),
|
||||
NetBindMarkerComponent::CreateDescriptor(),
|
||||
NetworkSpawnableHolderComponent::CreateDescriptor()
|
||||
});
|
||||
|
||||
|
||||
@@ -110,8 +110,6 @@ set(FILES
|
||||
Source/NetworkInput/NetworkInputMigrationVector.h
|
||||
Source/NetworkTime/NetworkTime.cpp
|
||||
Source/NetworkTime/NetworkTime.h
|
||||
Source/Pipeline/NetBindMarkerComponent.cpp
|
||||
Source/Pipeline/NetBindMarkerComponent.h
|
||||
Source/Pipeline/NetworkSpawnableHolderComponent.cpp
|
||||
Source/Pipeline/NetworkSpawnableHolderComponent.h
|
||||
Source/Physics/PhysicsUtils.cpp
|
||||
|
||||
Reference in New Issue
Block a user