#if UNITY_5 && !UNITY_5_0 && !UNITY_5_1 && !UNITY_5_2 #define UNITY_MIN_5_3 #endif using System.Collections.Generic; using System.Diagnostics; using System.IO; using UnityEditor; using UnityEngine; using System.Collections; using Debug = UnityEngine.Debug; using UnityEditor.SceneManagement; [InitializeOnLoad] public class PhotonViewHandler : EditorWindow { private static bool CheckSceneForStuckHandlers = true; static PhotonViewHandler() { // hierarchyWindowChanged is called on hierarchy changed and on save. It's even called when hierarchy-window is closed and if a prefab with instances is changed. // this is not called when you edit a instance's value but: on save EditorApplication.hierarchyWindowChanged += HierarchyChange; } // this method corrects the IDs for photonviews in the scene and in prefabs // make sure prefabs always use viewID 0 // make sure instances never use a owner // this is a editor class that should only run if not playing internal static void HierarchyChange() { if (Application.isPlaying) { //Debug.Log("HierarchyChange ignored, while running."); CheckSceneForStuckHandlers = true; // done once AFTER play mode. return; } if (CheckSceneForStuckHandlers) { CheckSceneForStuckHandlers = false; PhotonNetwork.InternalCleanPhotonMonoFromSceneIfStuck(); } HashSet pvInstances = new HashSet(); HashSet usedInstanceViewNumbers = new HashSet(); bool fixedSomeId = false; //// the following code would be an option if we only checked scene objects (but we can check all PVs) //PhotonView[] pvObjects = GameObject.FindSceneObjectsOfType(typeof(PhotonView)) as PhotonView[]; //Debug.Log("HierarchyChange. PV Count: " + pvObjects.Length); string levelName = SceneManagerHelper.ActiveSceneName; #if UNITY_EDITOR levelName = SceneManagerHelper.EditorActiveSceneName; #endif int minViewIdInThisScene = PunSceneSettings.MinViewIdForScene(levelName); //Debug.Log("Level '" + Application.loadedLevelName + "' has a minimum ViewId of: " + minViewIdInThisScene); PhotonView[] pvObjects = Resources.FindObjectsOfTypeAll(typeof(PhotonView)) as PhotonView[]; foreach (PhotonView view in pvObjects) { // first pass: fix prefabs to viewID 0 if they got a view number assigned (cause they should not have one!) if (EditorUtility.IsPersistent(view.gameObject)) { if (view.viewID != 0 || view.prefixBackup != -1 || view.instantiationId != -1) { Debug.LogWarning("PhotonView on persistent object being fixed (id and prefix must be 0). Was: " + view); view.viewID = 0; view.prefixBackup = -1; view.instantiationId = -1; EditorUtility.SetDirty(view); // even in Unity 5.3+ it's OK to SetDirty() for non-scene objects. fixedSomeId = true; } } else { // keep all scene-instanced PVs for later re-check pvInstances.Add(view); } } Dictionary idPerObject = new Dictionary(); // second pass: check all used-in-scene viewIDs for duplicate viewIDs (only checking anything non-prefab) // scene-PVs must have user == 0 (scene/room) and a subId != 0 foreach (PhotonView view in pvInstances) { if (view.ownerId > 0) { Debug.Log("Re-Setting Owner ID of: " + view); } view.ownerId = 0; // simply make sure no owner is set (cause room always uses 0) view.prefix = -1; // TODO: prefix could be settable via inspector per scene?! if (view.viewID != 0) { if (view.viewID < minViewIdInThisScene || usedInstanceViewNumbers.Contains(view.viewID)) { view.viewID = 0; // avoid duplicates and negative values by assigning 0 as (temporary) number to be fixed in next pass } else { usedInstanceViewNumbers.Add(view.viewID); // builds a list of currently used viewIDs int instId = 0; if (idPerObject.TryGetValue(view.gameObject, out instId)) { view.instantiationId = instId; } else { view.instantiationId = view.viewID; idPerObject[view.gameObject] = view.instantiationId; } } } } // third pass: anything that's now 0 must get a new (not yet used) ID (starting at 0) int lastUsedId = (minViewIdInThisScene > 0) ? minViewIdInThisScene - 1 : 0; foreach (PhotonView view in pvInstances) { if (view.viewID == 0) { Undo.RecordObject(view, "Automatic viewID change for: "+view.gameObject.name); // Debug.LogWarning("setting scene ID: " + view.gameObject.name + " ID: " + view.subId.ID + " scene ID: " + view.GetSceneID() + " IsPersistent: " + EditorUtility.IsPersistent(view.gameObject) + " IsSceneViewIDFree: " + IsSceneViewIDFree(view.subId.ID, view)); int nextViewId = PhotonViewHandler.GetID(lastUsedId, usedInstanceViewNumbers); view.viewID = nextViewId; int instId = 0; if (idPerObject.TryGetValue(view.gameObject, out instId)) { view.instantiationId = instId; } else { view.instantiationId = view.viewID; idPerObject[view.gameObject] = nextViewId; } lastUsedId = nextViewId; fixedSomeId = true; #if !UNITY_MIN_5_3 EditorUtility.SetDirty(view); #endif } } if (fixedSomeId) { //Debug.LogWarning("Some subId was adjusted."); // this log is only interesting for Exit Games } } // TODO fail if no ID was available anymore // TODO look up lower numbers if offset hits max?! public static int GetID(int idOffset, HashSet usedInstanceViewNumbers) { while (idOffset < PhotonNetwork.MAX_VIEW_IDS) { idOffset++; if (!usedInstanceViewNumbers.Contains(idOffset)) { break; } } return idOffset; } //TODO: check if this can be internal protected (as source in editor AND as dll) public static void LoadAllScenesToFix() { string[] scenes = System.IO.Directory.GetFiles(".", "*.unity", SearchOption.AllDirectories); foreach (string scene in scenes) { EditorSceneManager.OpenScene(scene); PhotonViewHandler.HierarchyChange();//NOTE: most likely on load also triggers a hierarchy change EditorSceneManager.SaveOpenScenes(); } Debug.Log("Corrected scene views where needed."); } }