/* * 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 "AzCore/Component/TransformBus.h" #include #include #include #include #include namespace Multiplayer { void SimplePlayerSpawnerComponent::Activate() { AZ::Interface::Register(this); AZ::Interface::Register(this); } void SimplePlayerSpawnerComponent::Deactivate() { AZ::Interface::Unregister(this); AZ::Interface::Unregister(this); } void SimplePlayerSpawnerComponent::Reflect(AZ::ReflectContext* context) { if (const auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("PlayerSpawnable", &SimplePlayerSpawnerComponent::m_playerSpawnable) ->Field("SpawnPoints", &SimplePlayerSpawnerComponent::m_spawnPoints) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { editContext->Class( "Simple Network Player Spawner", "A simple player spawner that comes included with the Multiplayer gem. Attach this component to any level's root entity which needs to spawn a network player." "If no spawn points are provided the network players will be spawned at the world-space origin.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "Multiplayer") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/SimpleNetworkPlayerSpawner.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/SimpleNetworkPlayerSpawner.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Level")) ->DataElement( AZ::Edit::UIHandlers::Default, &SimplePlayerSpawnerComponent::m_playerSpawnable, "Player Spawnable Asset", "The network player spawnable asset which will be spawned for each player that joins.") ->DataElement( AZ::Edit::UIHandlers::Default, &SimplePlayerSpawnerComponent::m_spawnPoints, "Spawn Points", "Networked players will spawn at the spawn point locations in order. If there are more players than spawn points, the new players will round-robin back starting with the first spawn point.") ; } } } void SimplePlayerSpawnerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("MultiplayerSpawnerService")); } void SimplePlayerSpawnerComponent::GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC_CE("MultiplayerSpawnerService")); } AZ::Transform SimplePlayerSpawnerComponent::GetNextSpawnPoint() const { if (m_spawnPoints.empty()) { return AZ::Transform::Identity(); } if (m_spawnIndex >= m_spawnPoints.size()) { AZ_Assert(false, "SimplePlayerSpawnerComponent has an out-of-bounds m_spawnIndex %i. Please ensure spawn index is always valid.", m_spawnIndex); return AZ::Transform::Identity(); } const AZ::EntityId spawnPointEntityId = m_spawnPoints[m_spawnIndex]; if (!spawnPointEntityId.IsValid()) { AZ_Assert( false, "Empty spawner entry at m_spawnIndex %i. Please ensure spawn index is always valid.", m_spawnIndex); return AZ::Transform::Identity(); } AZ::Transform spawnPointTransform = AZ::Transform::Identity(); AZ::TransformBus::EventResult(spawnPointTransform, spawnPointEntityId, &AZ::TransformInterface::GetWorldTM); return spawnPointTransform; } const AZStd::vector& SimplePlayerSpawnerComponent::GetSpawnPoints() const { return m_spawnPoints; } uint32_t SimplePlayerSpawnerComponent::GetSpawnPointCount() const { return aznumeric_cast(m_spawnPoints.size()); } uint32_t SimplePlayerSpawnerComponent::GetNextSpawnPointIndex() const { if (m_spawnPoints.empty()) { return 0; } if (m_spawnIndex >= m_spawnPoints.size()) { AZ_Assert(false, "SimplePlayerSpawnerComponent has an out-of-bounds m_spawnIndex %i. Please ensure spawn index is always valid.", m_spawnIndex); return static_cast(-1); } return m_spawnIndex; } void SimplePlayerSpawnerComponent::SetNextSpawnPointIndex(uint32_t index) { if (index >= m_spawnPoints.size()) { AZLOG_WARN("SetNextSpawnPointIndex called with out-of-bounds spawn index %i; total spawn points: %i", index, aznumeric_cast(m_spawnPoints.size())); return; } m_spawnIndex = index; } NetworkEntityHandle SimplePlayerSpawnerComponent::OnPlayerJoin( [[maybe_unused]] uint64_t userId, [[maybe_unused]] const MultiplayerAgentDatum& agentDatum) { const PrefabEntityId prefabEntityId(AZ::Name(m_playerSpawnable.m_spawnableAsset.GetHint().c_str())); const AZ::Transform transform = GetNextSpawnPoint(); m_spawnIndex = ++m_spawnIndex % m_spawnPoints.size(); INetworkEntityManager::EntityList entityList = GetNetworkEntityManager()->CreateEntitiesImmediate(prefabEntityId, NetEntityRole::Authority, transform); NetworkEntityHandle controlledEntity; if (entityList.empty()) { // Failure: The player prefab has no networked entities in it. AZLOG_ERROR( "Attempt to spawn prefab '%s' failed, no entities were spawned. Ensure that the prefab contains a single entity " "that is network enabled with a Network Binding component.", prefabEntityId.m_prefabName.GetCStr()); } else if (entityList.size() == 1) { // Success: The player prefab has exactly one networked entity in it. controlledEntity = entityList[0]; } else { // Failure: The player prefab has too many networked entities in it. AZLOG_ERROR( "Attempt to spawn prefab '%s' failed, it contains too many networked entities. " "A player prefab must only have a single networked entity inside of it.", prefabEntityId.m_prefabName.GetCStr()); for (auto& entityHandle : entityList) { AZ::Interface::Get()->GetNetworkEntityManager()->MarkForRemoval(entityHandle); } } return controlledEntity; } void SimplePlayerSpawnerComponent::OnPlayerLeave(ConstNetworkEntityHandle entityHandle, [[maybe_unused]] const ReplicationSet& replicationSet, [[maybe_unused]] AzNetworking::DisconnectReason reason) { AZ::Interface::Get()->GetNetworkEntityManager()->MarkForRemoval(entityHandle); } } // namespace Multiplayer