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; } } }