#if ENABLE_UNET using System.Text; using System.Collections.Generic; using System.Linq; using UnityEditor.Callbacks; using UnityEngine; using UnityEngine.Networking; namespace UnityEditor { public class NetworkScenePostProcess : MonoBehaviour { // persistent sceneId assignment to fix readstring bug that occurs when restarting the editor and // connecting to a build again. sceneids were then different because FindObjectsOfType's order // is not guranteed to be the same. // -> we need something unique and persistent, aka always the same when pressing play/building the first time // -> Unity has no built in unique id for GameObjects in the scene // helper function to figure out a unique, persistent scene id for a GameObject in the hierarchy // -> Unity's instanceId is unique but not persistent // -> hashing the whole GameObject is not enough either since a duplicate would have the same hash // -> we definitely need the transform sibling index in the hierarchy // -> so we might as well use just that // -> transforms have children too so we need a list of sibling indices like 0->3->5 public static List SiblingPathFor(Transform t) { List result = new List(); while (t != null) { result.Add(t.GetSiblingIndex()); t = t.parent; } result.Reverse(); // parent to child instead of child to parent order return result; } // we need to compare by using the whole sibling list // comparing the string won't work work because: // "1->2" // "20->2" // would compare '1' vs '2', then '-' vs '0' // // tests: // CompareSiblingPaths(new List(){0}, new List(){0}) => 0 // CompareSiblingPaths(new List(){0}, new List(){1}) => -1 // CompareSiblingPaths(new List(){1}, new List(){0}) => 1 // CompareSiblingPaths(new List(){0,1}, new List(){0,2}) => -1 // CompareSiblingPaths(new List(){0,2}, new List(){0,1}) => 1 // CompareSiblingPaths(new List(){1}, new List(){0,1}) => 1 // CompareSiblingPaths(new List(){1}, new List(){2,1}) => -1 public static int CompareSiblingPaths(List left, List right) { // compare [0], remove it, compare next, etc. while (left.Count > 0 && right.Count > 0) { if (left[0] < right[0]) { return -1; } else if (left[0] > right[0]) { return 1; } else { // equal, so they are both children of the same transform // -> which also means that they both must have one more // entry, so we can remove both without checking size left.RemoveAt(0); right.RemoveAt(0); } } // equal if both were empty or both had the same entry without any // more children (should never happen in practice) return 0; } public static int CompareNetworkIdentitySiblingPaths(NetworkIdentity left, NetworkIdentity right) { return CompareSiblingPaths(SiblingPathFor(left.transform), SiblingPathFor(right.transform)); } [PostProcessScene] public static void OnPostProcessScene() { var prefabWarnings = new HashSet(); // vis2k: MISMATCHING SCENEID BUG FIX // problem: // * FindObjectsOfType order is not guaranteed. restarting the // editor results in a different order // * connecting to a build again would cause UNET to deserialize // the wrong objects, causing all kinds of weird errors like // 'ReadString out of range' // // solution: // sort by sibling-index path, e.g. [0,1,2] vs [1] // this is the only deterministic way to sort a list of objects in // the scene. // -> it's the same result every single time, even after restarts // // note: there is a reason why we 'sort by' sibling path instead of // using it as sceneId directly. networkmanager etc. use Dont- // DestroyOnLoad, which changes the hierarchy: // // World: // NetworkManager // Player // // ..becomes.. // // World: // Player // DontDestroyOnLoad: // NetworkManager // // so the player's siblingindex would be decreased by one. // -> this is a problem because when building, OnPostProcessScene // is called before any dontdestroyonload happens, but when // entering play mode, it's called after // -> hence sceneids would differ by one // // => but if we only SORT it, then it doesn't matter if one // inbetween disappeared. as long as no NetworkIdentity used // DontDestroyOnLoad. // // note: assigning a GUID in NetworkIdentity.OnValidate would be way // cooler, but OnValidate isn't called for other unopened scenes // when building or pressing play, so the bug would still happen // there. // // note: this can still fail if DontDestroyOnLoad is called for a // NetworkIdentity - but no one should ever do that anyway. var uvs = FindObjectsOfType().ToList(); uvs.Sort(CompareNetworkIdentitySiblingPaths); int nextSceneId = 1; foreach (NetworkIdentity uv in uvs) { // if we had a [ConflictComponent] attribute that would be better than this check. // also there is no context about which scene this is in. if (uv.GetComponent() != null) { Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended."); } if (uv.isClient || uv.isServer) continue; uv.gameObject.SetActive(false); uv.ForceSceneId(nextSceneId++); if (LogFilter.logDebug) { Debug.Log("PostProcess sceneid assigned: name=" + uv.name + " scene=" + uv.gameObject.scene.name + " sceneid=" + uv.sceneId); } // saftey check for prefabs with more than one NetworkIdentity var prefabGO = UnityEditor.PrefabUtility.GetPrefabParent(uv.gameObject) as GameObject; if (prefabGO) { var prefabRootGO = UnityEditor.PrefabUtility.FindPrefabRoot(prefabGO); if (prefabRootGO) { var identities = prefabRootGO.GetComponentsInChildren(); if (identities.Length > 1 && !prefabWarnings.Contains(prefabRootGO.name)) { // make sure we only print one error per prefab prefabWarnings.Add(prefabRootGO.name); Debug.LogWarningFormat("Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported.", prefabRootGO.name); } } } } } } } #endif //ENABLE_UNET