214 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using UnityEngine;
 | |
| using System.Collections.Generic;
 | |
| using System;
 | |
| using FishNet.Object.Helping;
 | |
| using System.Linq;
 | |
| #if UNITY_EDITOR
 | |
| using UnityEditor.Experimental.SceneManagement;
 | |
| using UnityEditor.SceneManagement;
 | |
| using UnityEditor;
 | |
| #endif
 | |
| 
 | |
| namespace FishNet.Object
 | |
| {
 | |
|     public sealed partial class NetworkObject : MonoBehaviour
 | |
|     {
 | |
|         #region Serialized.
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Networked PrefabId assigned to this Prefab.
 | |
|         /// </summary>
 | |
|         [field: SerializeField, HideInInspector]
 | |
|         public ushort PrefabId { get; internal set; } = 0;
 | |
|         /// <summary>
 | |
|         /// Spawn collection to use assigned to this Prefab.
 | |
|         /// </summary>
 | |
|         [field: SerializeField, HideInInspector]
 | |
|         public ushort SpawnableCollectionId { get; internal set; } = 0;
 | |
| #pragma warning disable 414 //Disabled because Unity thinks tihs is unused when building.
 | |
|         /// <summary>
 | |
|         /// Hash to the scene which this object resides.
 | |
|         /// </summary>
 | |
|         [SerializeField, HideInInspector]
 | |
|         private uint _scenePathHash;
 | |
| #pragma warning restore 414
 | |
|         /// <summary>
 | |
|         /// Network Id for this scene object.
 | |
|         /// </summary>
 | |
|         [field: SerializeField, HideInInspector]
 | |
|         internal ulong SceneId { get; private set; }
 | |
|         /// <summary>
 | |
|         /// Hash for the path which this asset resides. This value is set during edit time.
 | |
|         /// </summary> 
 | |
|         [field: SerializeField, HideInInspector]
 | |
|         public ulong AssetPathHash { get; private set; }
 | |
|         /// <summary>
 | |
|         /// Sets AssetPathhash value.
 | |
|         /// </summary>
 | |
|         /// <param name="value">Value to use.</param>
 | |
|         public void SetAssetPathHash(ulong value) => AssetPathHash = value;
 | |
|         #endregion
 | |
| 
 | |
| #if UNITY_EDITOR
 | |
|         /// <summary>
 | |
|         /// This is used to store NetworkObjects in the scene during edit time.
 | |
|         /// SceneIds are compared against this collection to ensure there are no duplicated.
 | |
|         /// </summary>
 | |
|         [SerializeField, HideInInspector]
 | |
|         private List<NetworkObject> _sceneNetworkObjects = new List<NetworkObject>();
 | |
| #endif
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Removes SceneObject state.
 | |
|         /// This may only be called at runtime.
 | |
|         /// </summary>
 | |
|         internal void ClearRuntimeSceneObject()
 | |
|         {
 | |
|             if (!Application.isPlaying)
 | |
|             {
 | |
|                 Debug.LogError($"ClearRuntimeSceneObject may only be called at runtime.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             SceneId = 0;
 | |
|         }
 | |
| 
 | |
| #if UNITY_EDITOR
 | |
|         /// <summary>
 | |
|         /// Tries to generate a SceneId.
 | |
|         /// </summary>
 | |
|         internal void TryCreateSceneID()
 | |
|         {
 | |
|             if (Application.isPlaying)
 | |
|                 return;
 | |
|             //Unity bug, sometimes this can be null depending on editor callback orders.
 | |
|             if (gameObject == null)
 | |
|                 return;
 | |
|             //Not a scene object.
 | |
|             if (string.IsNullOrEmpty(gameObject.scene.name))
 | |
|             {
 | |
|                 SceneId = 0;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             ulong startId = SceneId;
 | |
|             uint startPath = _scenePathHash;
 | |
| 
 | |
|             ulong sceneId = 0;
 | |
|             uint scenePathHash = 0;
 | |
|             //If prefab or part of a prefab, not a scene object.            
 | |
|             if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() ||
 | |
|              //Not in a scene, another prefab check.
 | |
|              !gameObject.scene.IsValid() ||
 | |
|              //Stored on disk, so is a prefab. Somehow prefabutility missed it.
 | |
|              EditorUtility.IsPersistent(this))
 | |
|             {
 | |
|                 //These are all failing conditions, don't do additional checks.
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 System.Random rnd = new System.Random();
 | |
|                 scenePathHash = gameObject.scene.path.ToLower().GetStableHash32();
 | |
|                 sceneId = SceneId;
 | |
|                 //Not a valid sceneId or is a duplicate. 
 | |
|                 if (scenePathHash != _scenePathHash || SceneId == 0 || IsDuplicateSceneId(SceneId))
 | |
|                 {
 | |
|                     /* If a scene has not been opened since an id has been
 | |
|                      * generated then it will not be serialized in editor. The id
 | |
|                      * would be correct in build but not if running in editor. 
 | |
|                      * Should conditions be true where scene is building without
 | |
|                      * being opened then cancel build and request user to open and save
 | |
|                      * scene. */
 | |
|                     if (BuildPipeline.isBuildingPlayer)
 | |
|                         throw new InvalidOperationException($"Networked GameObject {gameObject.name} in scene {gameObject.scene.path} is missing a SceneId. Open the scene, select the Fish-Networking menu, and choose Rebuild SceneIds. If the problem persist ensures {gameObject.name} does not have any missing script references on it's prefab or in the scene. Also ensure that you have any prefab changes for the object applied.");
 | |
| 
 | |
|                     ulong shiftedHash = (ulong)scenePathHash << 32;
 | |
|                     ulong randomId = 0;
 | |
|                     while (randomId == 0 || IsDuplicateSceneId(randomId))
 | |
|                     {
 | |
|                         uint next = (uint)(rnd.Next(int.MinValue, int.MaxValue) + int.MaxValue);
 | |
|                         /* Since the collection is lost when a scene loads the it's possible to
 | |
|                         * have a sceneid from another scene. Because of this the scene path is
 | |
|                         * inserted into the sceneid. */
 | |
|                         randomId = (next & 0xFFFFFFFF) | shiftedHash;
 | |
|                     }
 | |
| 
 | |
|                     sceneId = randomId;
 | |
|                 }
 | |
| 
 | |
|             }
 | |
| 
 | |
|             bool idChanged = (sceneId != startId);
 | |
|             bool pathChanged = (startPath != scenePathHash);
 | |
|             //If either changed then dirty and set.
 | |
|             if (idChanged || pathChanged)
 | |
|             {
 | |
|                 //Set dirty so changes will be saved.
 | |
|                 EditorUtility.SetDirty(this);
 | |
|                 /* Add to sceneIds collection. This must be done
 | |
|                  * even if a new sceneId was not generated because
 | |
|                  * the collection information is lost when the
 | |
|                  * scene is existed. Essentially, it gets repopulated
 | |
|                  * when the scene is re-opened. */
 | |
|                 SceneId = sceneId;
 | |
|                 _scenePathHash = scenePathHash;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private bool IsEditingInPrefabMode()
 | |
|         {
 | |
|             if (EditorUtility.IsPersistent(this))
 | |
|             {
 | |
|                 // if the game object is stored on disk, it is a prefab of some kind, despite not returning true for IsPartOfPrefabAsset =/
 | |
|                 return true;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // If the GameObject is not persistent let's determine which stage we are in first because getting Prefab info depends on it
 | |
|                 StageHandle mainStage = StageUtility.GetMainStageHandle();
 | |
|                 StageHandle currentStage = StageUtility.GetStageHandle(gameObject);
 | |
|                 if (currentStage != mainStage)
 | |
|                 {
 | |
|                     var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
 | |
|                     if (prefabStage != null)
 | |
|                     {
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             return false;
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if the Id used is a sceneId already belonging to another object.
 | |
|         /// </summary>
 | |
|         /// <param name="id"></param>
 | |
|         /// <returns></returns>
 | |
|         private bool IsDuplicateSceneId(ulong id)
 | |
|         {
 | |
|             //Find all nobs in scene.
 | |
|             _sceneNetworkObjects = GameObject.FindObjectsOfType<NetworkObject>().ToList();
 | |
|             foreach (NetworkObject nob in _sceneNetworkObjects)
 | |
|             {
 | |
|                 if (nob != null && nob != this && nob.SceneId == id)
 | |
|                     return true;
 | |
|             }
 | |
|             //If here all checks pass.
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         private void ReferenceIds_OnValidate()
 | |
|         {
 | |
|             TryCreateSceneID();
 | |
|         }
 | |
|         private void ReferenceIds_Reset()
 | |
|         {
 | |
|             TryCreateSceneID();
 | |
|         }
 | |
| #endif
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 |