using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode
{
///
/// Interface for customizing, overriding, spawning, and destroying Network Prefabs
/// Used by
///
public interface INetworkPrefabInstanceHandler
{
///
/// Client Side Only
/// Once an implementation is registered with the , this method will be called every time
/// a Network Prefab associated is spawned on clients
///
/// Note On Hosts: Use the
/// method to register all targeted NetworkPrefab overrides manually since the host will be acting as both a server and client.
///
/// Note on Pooling: If you are using a NetworkObject pool, don't forget to make the NetworkObject active
/// via the method.
///
/// the owner for the to be instantiated
/// the initial/default position for the to be instantiated
/// the initial/default rotation for the to be instantiated
///
NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
///
/// Invoked on Client and Server
/// Once an implementation is registered with the , this method will be called when
/// a Network Prefab associated is:
///
/// Server Side: destroyed or despawned with the destroy parameter equal to true
/// If is invoked with the default destroy parameter (i.e. false) then this method will NOT be invoked!
///
/// Client Side: destroyed when the client receives a destroy object message from the server or host.
///
/// Note on Pooling: When this method is invoked, you do not need to destroy the NetworkObject as long as you want your pool to persist.
/// The most common approach is to make the inactive by calling .
///
/// The being destroyed
void Destroy(NetworkObject networkObject);
}
///
/// Primary handler to add or remove customized spawn and destroy handlers for a network prefab (i.e. a prefab with a NetworkObject component)
/// Register custom prefab handlers by implementing the interface.
///
public class NetworkPrefabHandler
{
private NetworkManager m_NetworkManager;
///
/// Links a network prefab asset to a class with the INetworkPrefabInstanceHandler interface
///
private readonly Dictionary m_PrefabAssetToPrefabHandler = new Dictionary();
///
/// Links the custom prefab instance's GlobalNetworkObjectId to the original prefab asset's GlobalNetworkObjectId. (Needed for HandleNetworkPrefabDestroy)
/// [PrefabInstance][PrefabAsset]
///
private readonly Dictionary m_PrefabInstanceToPrefabAsset = new Dictionary();
internal static string PrefabDebugHelper(NetworkPrefab networkPrefab) => $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.name}\"";
///
/// Use a to register a class that implements the interface with the
///
/// the of the network prefab asset to be overridden
/// class that implements the interface to be registered
/// true (registered) false (failed to register)
public bool AddHandler(GameObject networkPrefabAsset, INetworkPrefabInstanceHandler instanceHandler)
{
return AddHandler(networkPrefabAsset.GetComponent().GlobalObjectIdHash, instanceHandler);
}
///
/// Use a to register a class that implements the interface with the
///
/// the of the network prefab asset to be overridden
/// the class that implements the interface to be registered
///
public bool AddHandler(NetworkObject prefabAssetNetworkObject, INetworkPrefabInstanceHandler instanceHandler)
{
return AddHandler(prefabAssetNetworkObject.GlobalObjectIdHash, instanceHandler);
}
///
/// Use a to register a class that implements the interface with the
///
/// the value of the network prefab asset being overridden
/// a class that implements the interface
///
public bool AddHandler(uint globalObjectIdHash, INetworkPrefabInstanceHandler instanceHandler)
{
if (!m_PrefabAssetToPrefabHandler.ContainsKey(globalObjectIdHash))
{
m_PrefabAssetToPrefabHandler.Add(globalObjectIdHash, instanceHandler);
return true;
}
return false;
}
///
/// HOST ONLY!
/// Since a host is unique and is considered both a client and a server, for each source NetworkPrefab you must manually
/// register all potential target overrides that have the component.
///
/// source NetworkPrefab to be overridden
/// one or more NetworkPrefabs could be used to override the source NetworkPrefab
public void RegisterHostGlobalObjectIdHashValues(GameObject sourceNetworkPrefab, List networkPrefabOverrides)
{
if (NetworkManager.Singleton.IsListening)
{
if (NetworkManager.Singleton.IsHost)
{
var sourceNetworkObject = sourceNetworkPrefab.GetComponent();
if (sourceNetworkPrefab != null)
{
var sourceGlobalObjectIdHash = sourceNetworkObject.GlobalObjectIdHash;
// Now we register all
foreach (var gameObject in networkPrefabOverrides)
{
if (gameObject.TryGetComponent(out var targetNetworkObject))
{
if (!m_PrefabInstanceToPrefabAsset.ContainsKey(targetNetworkObject.GlobalObjectIdHash))
{
m_PrefabInstanceToPrefabAsset.Add(targetNetworkObject.GlobalObjectIdHash, sourceGlobalObjectIdHash);
}
else
{
Debug.LogWarning($"{targetNetworkObject.name} appears to be a duplicate entry!");
}
}
else
{
throw new Exception($"{targetNetworkObject.name} does not have a {nameof(NetworkObject)} component!");
}
}
}
else
{
throw new Exception($"{sourceNetworkPrefab.name} does not have a {nameof(NetworkObject)} component!");
}
}
else
{
throw new Exception($"You should only call {nameof(RegisterHostGlobalObjectIdHashValues)} as a Host!");
}
}
else
{
throw new Exception($"You can only call {nameof(RegisterHostGlobalObjectIdHashValues)} once NetworkManager is listening!");
}
}
///
/// Use the of the overridden network prefab asset to remove a registered class that implements the interface.
///
/// of the network prefab asset that was being overridden
/// true (success) or false (failure)
public bool RemoveHandler(GameObject networkPrefabAsset)
{
return RemoveHandler(networkPrefabAsset.GetComponent().GlobalObjectIdHash);
}
///
/// Use the of the overridden network prefab asset to remove a registered class that implements the interface.
///
/// of the source NetworkPrefab that was being overridden
/// true (success) or false (failure)
public bool RemoveHandler(NetworkObject networkObject)
{
return RemoveHandler(networkObject.GlobalObjectIdHash);
}
///
/// Use the of the overridden network prefab asset to remove a registered class that implements the interface.
///
/// of the source NetworkPrefab that was being overridden
/// true (success) or false (failure)
public bool RemoveHandler(uint globalObjectIdHash)
{
if (m_PrefabInstanceToPrefabAsset.ContainsValue(globalObjectIdHash))
{
uint networkPrefabHashKey = 0;
foreach (var kvp in m_PrefabInstanceToPrefabAsset)
{
if (kvp.Value == globalObjectIdHash)
{
networkPrefabHashKey = kvp.Key;
break;
}
}
m_PrefabInstanceToPrefabAsset.Remove(networkPrefabHashKey);
}
return m_PrefabAssetToPrefabHandler.Remove(globalObjectIdHash);
}
///
/// Check to see if a with a is registered to an implementation
///
///
/// true or false
internal bool ContainsHandler(GameObject networkPrefab) => ContainsHandler(networkPrefab.GetComponent().GlobalObjectIdHash);
///
/// Check to see if a is registered to an implementation
///
///
/// true or false
internal bool ContainsHandler(NetworkObject networkObject) => ContainsHandler(networkObject.GlobalObjectIdHash);
///
/// Check to see if a is registered to an implementation
///
///
/// true or false
internal bool ContainsHandler(uint networkPrefabHash) => m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash) || m_PrefabInstanceToPrefabAsset.ContainsKey(networkPrefabHash);
///
/// Returns the source NetworkPrefab's
///
///
///
internal uint GetSourceGlobalObjectIdHash(uint networkPrefabHash)
{
if (m_PrefabAssetToPrefabHandler.ContainsKey(networkPrefabHash))
{
return networkPrefabHash;
}
if (m_PrefabInstanceToPrefabAsset.TryGetValue(networkPrefabHash, out var hash))
{
return hash;
}
return 0;
}
///
/// Will return back a generated via an implementation
/// Note: Invoked only on the client side and called within NetworkSpawnManager.CreateLocalNetworkObject
///
/// typically the "server-side" asset's prefab hash
///
///
///
///
internal NetworkObject HandleNetworkPrefabSpawn(uint networkPrefabAssetHash, ulong ownerClientId, Vector3 position, Quaternion rotation)
{
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
{
var networkObjectInstance = prefabInstanceHandler.Instantiate(ownerClientId, position, rotation);
//Now we must make sure this alternate PrefabAsset spawned in place of the prefab asset with the networkPrefabAssetHash (GlobalObjectIdHash)
//is registered and linked to the networkPrefabAssetHash so during the HandleNetworkPrefabDestroy process we can identify the alternate prefab asset.
if (networkObjectInstance != null && !m_PrefabInstanceToPrefabAsset.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
{
m_PrefabInstanceToPrefabAsset.Add(networkObjectInstance.GlobalObjectIdHash, networkPrefabAssetHash);
}
return networkObjectInstance;
}
return null;
}
///
/// Will invoke the implementation's Destroy method
///
///
internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance)
{
var networkObjectInstanceHash = networkObjectInstance.GlobalObjectIdHash;
// Do we have custom overrides registered?
if (m_PrefabInstanceToPrefabAsset.TryGetValue(networkObjectInstanceHash, out var networkPrefabAssetHash))
{
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler))
{
prefabInstanceHandler.Destroy(networkObjectInstance);
}
}
else // Otherwise the NetworkObject is the source NetworkPrefab
if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler))
{
prefabInstanceHandler.Destroy(networkObjectInstance);
}
}
///
/// Returns the to use as the override as could be defined within the NetworkPrefab list
/// Note: This should be used to create pools (with components)
/// under the scenario where you are using the Host model as it spawns everything locally. As such, the override
/// will not be applied when spawning locally on a Host.
/// Related Classes and Interfaces:
///
///
/// the to be checked for a defined NetworkPrefab override
/// a that is either the override or if no overrides exist it returns the same as the one passed in as a parameter
public GameObject GetNetworkPrefabOverride(GameObject gameObject)
{
if (gameObject.TryGetComponent(out var networkObject))
{
if (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(networkObject.GlobalObjectIdHash))
{
switch (m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].Override)
{
case NetworkPrefabOverride.Hash:
case NetworkPrefabOverride.Prefab:
{
return m_NetworkManager.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks[networkObject.GlobalObjectIdHash].OverridingTargetPrefab;
}
}
}
}
return gameObject;
}
///
/// Adds a new prefab to the network prefab list.
/// This can be any GameObject with a NetworkObject component, from any source (addressables, asset
/// bundles, Resource.Load, dynamically created, etc)
///
/// There are three limitations to this method:
/// - If you have NetworkConfig.ForceSamePrefabs enabled, you can only do this before starting
/// networking, and the server and all connected clients must all have the same exact set of prefabs
/// added via this method before connecting
/// - Adding a prefab on the server does not automatically add it on the client - it's up to you
/// to make sure the client and server are synchronized via whatever method makes sense for your game
/// (RPCs, configurations, deterministic loading, etc)
/// - If the server sends a Spawn message to a client that has not yet added a prefab for, the spawn message
/// and any other relevant messages will be held for a configurable time (default 1 second, configured via
/// NetworkConfig.SpawnTimeout) before an error is logged. This is intended to enable the SDK to gracefully
/// handle unexpected conditions (slow disks, slow network, etc) that slow down asset loading. This timeout
/// should not be relied on and code shouldn't be written around it - your code should be written so that
/// the asset is expected to be loaded before it's needed.
///
///
///
public void AddNetworkPrefab(GameObject prefab)
{
if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs)
{
throw new Exception($"All prefabs must be registered before starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
}
var networkObject = prefab.GetComponent();
if (!networkObject)
{
throw new Exception($"All {nameof(NetworkPrefab)}s must contain a {nameof(NetworkObject)} component.");
}
var networkPrefab = new NetworkPrefab { Prefab = prefab };
bool added = m_NetworkManager.NetworkConfig.Prefabs.Add(networkPrefab);
if (m_NetworkManager.IsListening && added)
{
m_NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
}
}
///
/// Remove a prefab from the prefab list.
/// As with AddNetworkPrefab, this is specific to the client it's called on -
/// calling it on the server does not automatically remove anything on any of the
/// client processes.
///
/// Like AddNetworkPrefab, when NetworkConfig.ForceSamePrefabs is enabled,
/// this cannot be called after connecting.
///
///
public void RemoveNetworkPrefab(GameObject prefab)
{
if (m_NetworkManager.IsListening && m_NetworkManager.NetworkConfig.ForceSamePrefabs)
{
throw new Exception($"Prefabs cannot be removed after starting {nameof(NetworkManager)} when {nameof(NetworkConfig.ForceSamePrefabs)} is enabled.");
}
var globalObjectIdHash = prefab.GetComponent().GlobalObjectIdHash;
m_NetworkManager.NetworkConfig.Prefabs.Remove(prefab);
if (ContainsHandler(globalObjectIdHash))
{
RemoveHandler(globalObjectIdHash);
}
}
///
/// If one exists, registers the player prefab
///
internal void RegisterPlayerPrefab()
{
var networkConfig = m_NetworkManager.NetworkConfig;
// If we have a player prefab, then we need to verify it is in the list of NetworkPrefabOverrideLinks for client side spawning.
if (networkConfig.PlayerPrefab != null)
{
if (networkConfig.PlayerPrefab.TryGetComponent(out var playerPrefabNetworkObject))
{
//In the event there is no NetworkPrefab entry (i.e. no override for default player prefab)
if (!networkConfig.Prefabs.NetworkPrefabOverrideLinks.ContainsKey(playerPrefabNetworkObject.GlobalObjectIdHash))
{
//Then add a new entry for the player prefab
AddNetworkPrefab(networkConfig.PlayerPrefab);
}
}
else
{
// Provide the name of the prefab with issues so the user can more easily find the prefab and fix it
Debug.LogError($"{nameof(NetworkConfig.PlayerPrefab)} (\"{networkConfig.PlayerPrefab.name}\") has no NetworkObject assigned to it!.");
}
}
}
internal void Initialize(NetworkManager networkManager)
{
m_NetworkManager = networkManager;
}
}
}