#if !CINEMACHINE_NO_CM2_SUPPORT //#define DEBUG_HELPERS #pragma warning disable CS0618 // suppress obsolete warnings using System; using System.Collections.Generic; using System.Linq; using System.Threading; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Playables; using UnityEngine.SceneManagement; #if CINEMACHINE_TIMELINE using UnityEngine.Timeline; #endif namespace Unity.Cinemachine.Editor { /// /// Upgrades cm2 to cm3 /// class CinemachineUpgradeManager { const string k_UnupgradableTag = " BACKUP - not fully upgradable by CM"; const string k_RenamePrefix = "__CM__UPGRADER__RENAME__ "; const string k_CopyPrefix = "__CM__UPGRADER__COPY__ "; UpgradeObjectToCm3 m_ObjectUpgrader; SceneManager m_SceneManager; PrefabManager m_PrefabManager; // This gets set to help with more informative warning messages about objects string m_CurrentSceneOrPrefab; const string k_ProgressBarTitle = "Upgrade Progress"; /// /// Upgrades the input gameObject. Referenced objects (e.g. paths) may also get upgraded. /// Obsolete components are deleted. Timeline references are not patched. /// Undo is supported. /// public static void UpgradeSingleObject(GameObject go) { var objectUpgrader = new UpgradeObjectToCm3(); try { var notUpgradable = objectUpgrader.UpgradeComponents(go); objectUpgrader.DeleteObsoleteComponents(go); // Report difficult cases if (notUpgradable != null) { notUpgradable.name = go.name + k_UnupgradableTag; Debug.LogWarning("Upgrader: " + go.name + " may not have been fully upgraded " + "automatically. A reference copy of the original was saved to " + notUpgradable.name); } } catch (Exception e) { Debug.LogError(e.Message); OnUnsuccessfulUpgrade(); } } /// /// Upgrades all the gameObjects in the current scene. /// Obsolete components are deleted. Timeline references are not patched. /// Undo is supported. /// public static void UpgradeObjectsInCurrentScene() { if (EditorUtility.DisplayDialog( "Upgrade objects in the current scene to Cinemachine 3", "This operation will not upgrade prefab instances or touch any timeline assets, " + "which can result in an incomplete upgrade. To do a complete upgrade, " + "you must choose the \"Upgrade Project\" option.\n\n" + "Upgrade scene?", "Upgrade", "Cancel")) { try { Thread.Sleep(1); // this is needed so the Display Dialog closes, and lets the progress bar open EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Initializing...", 0); var manager = new CinemachineUpgradeManager(false); var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); var rootObjects = scene.GetRootGameObjects(); var upgradable = manager.GetUpgradables( rootObjects, manager.m_ObjectUpgrader.RootUpgradeComponentTypes, true); var upgradedObjects = new HashSet(); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Upgrading Scene...", 0.5f); manager.UpgradeNonPrefabs(upgradable, upgradedObjects, null); UpgradeObjectReferences(rootObjects); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Cleaning up...", 1f); foreach (var go in upgradedObjects) manager.m_ObjectUpgrader.DeleteObsoleteComponents(go); } catch (Exception e) { Debug.LogError(e.Message); OnUnsuccessfulUpgrade(); } EditorUtility.ClearProgressBar(); } } /// /// Upgrades all objects in all scenes and prefabs /// public static void UpgradeProject() { if (EditorUtility.DisplayDialog( "Upgrade Project to Cinemachine 3", "This project contains objects created with Cinemachine 2, " + "which can be upgraded to Cinemachine 3 equivalents. " + "This can mostly be done automatically, but it is possible that " + "some objects might not be fully converted.\n\n" + "Any custom scripts in your project that reference the Cinemachine API will not be " + "automatically upgraded, and you may have to alter them manually. " + "Please see the upgrade guide in the user manual.\n\n" + "NOTE: Undo is not supported for this operation. You are strongly " + "advised to make a full backup of the project before proceeding.\n\n" + "If you prefer, you can cancel this operation and use the package manager to revert " + "Cinemachine to a 2.x version, which will continue to work as before.\n\n" + "Upgrade project?", "I made a backup, go ahead", "Cancel")) { var originalScenePath = EditorSceneManager.GetActiveScene().path; try { Thread.Sleep(1); // this is needed so the Display Dialog closes, and lets the progress bar open EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Initializing...", 0.1f); var manager = new CinemachineUpgradeManager(true); manager.PrepareUpgrades(out var conversionLinksPerScene, out var timelineRenames); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Upgrading Prefabs...", 0.4f); manager.UpgradePrefabAssets(true); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Upgrading Prefabs...", 0.5f); manager.UpgradeReferencablePrefabInstances(conversionLinksPerScene); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Upgrading Prefabs...", 0.6f); manager.UpgradePrefabAssets(false); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Upgrading Scenes...", 0.8f); manager.UpgradeRemaining(conversionLinksPerScene, timelineRenames); EditorUtility.DisplayProgressBar(k_ProgressBarTitle, "Cleaning up...", 1); manager.CleanupPrefabAssets(); } catch (Exception e) { Debug.LogError(e.Message); EditorUtility.ClearProgressBar(); OnUnsuccessfulUpgrade(); return; } EditorUtility.ClearProgressBar(); EditorSceneManager.OpenScene(originalScenePath); // re-open scene where the user was before upgrading } } static void OnUnsuccessfulUpgrade() { EditorUtility.DisplayDialog( "Cinemachine Upgrader", "The upgrade was unsuccessful, and your project may be correupted. It would be wise to restore the backup.\n\n" + "Please see the console messages for details.", "ok"); } /// Returns true if any of the objects are prefab instances or prefabs. /// /// public static bool ObjectsUsePrefabs(UnityEngine.Object[] objects) { for (int i = 0; i < objects.Length; ++i) { var go = objects[i] as GameObject; if (go == null) { var b = objects[i] as MonoBehaviour; if (b != null) go = b.gameObject; } if (go != null && PrefabUtility.IsPartOfAnyPrefab(go)) return true; } return false; } /// Returns true if any of the objects are prefab instances or prefabs. /// /// public static bool CurrentSceneUsesPrefabs() { var manager = new CinemachineUpgradeManager(false); var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene(); var rootObjects = scene.GetRootGameObjects(); var upgradable = manager.GetUpgradables( rootObjects, manager.m_ObjectUpgrader.RootUpgradeComponentTypes, true).ToArray(); return ObjectsUsePrefabs(upgradable); } /// /// For each scene: /// - Make names of timeline object unique - for unique referencing later on /// - Copy prefab instances (and upgrade the copy), build conversion link and collect timeline references /// /// Key: scene index, Value: List of conversion links /// Timeline rename mapping void PrepareUpgrades( out Dictionary> conversionLinksPerScene, out Dictionary renameMap) { conversionLinksPerScene = new (); renameMap = new (); for (var s = 0; s < m_SceneManager.SceneCount; ++s) { var scene = OpenScene(s); // Make timeline names unique var timelineManager = new TimelineManager(scene); #if CINEMACHINE_TIMELINE foreach (var director in timelineManager.PlayableDirectors) { var originalName = director.name; if (!originalName.StartsWith(k_RenamePrefix)) { director.name = k_RenamePrefix + originalName + " = " + GUID.Generate().ToString(); renameMap.Add(director.name, originalName); // key = guid, value = originalName } } #endif // CopyPrefabInstances, give unique names, create conversion links, collect timeline references // Upgrade prefab instance copies of referencables only var conversionLinks = new List(); var allPrefabInstances = new List(); for (var p = 0; p < m_PrefabManager.PrefabCount; ++p) { allPrefabInstances.AddRange( PrefabManager.FindAllInstancesOfPrefabEvenInNestedPrefabs(scene, m_PrefabManager.GetPrefabAssetPath(p))); } var upgradedObjects = new HashSet(); var upgradables = GetUpgradables(allPrefabInstances.ToArray(), m_ObjectUpgrader.RootUpgradeComponentTypes, true); foreach (var go in upgradables) { if (upgradedObjects.Contains(go)) continue; // Ignore if already converted (this can happen in nested prefabs) upgradedObjects.Add(go); #if CINEMACHINE_TIMELINE var originalVcam = go.GetComponent(); var timelineReferences = timelineManager.GetTimelineReferences(originalVcam); #endif var convertedCopy = UnityEngine.Object.Instantiate(go); UpgradeObjectComponents(convertedCopy, null); // Change the object name to something unique so we can find it later var conversionLink = new ConversionLink { originalName = go.name, originalGUIDName = GUID.Generate().ToString(), convertedGUIDName = k_CopyPrefix + GUID.Generate().ToString(), #if CINEMACHINE_TIMELINE timelineReferences = timelineReferences, #endif }; go.name = conversionLink.originalGUIDName; PrefabUtility.RecordPrefabInstancePropertyModifications(go); // record name change convertedCopy.name = conversionLink.convertedGUIDName; conversionLinks.Add(conversionLink); } conversionLinksPerScene.Add(s, conversionLinks); EditorSceneManager.SaveScene(scene); } m_CurrentSceneOrPrefab = string.Empty; } /// /// For each prefab asset that has any component from filter /// - Upgrade the prefab asset, but do not delete obsolete components /// - Fix timeline references /// - Fix object references /// - Fix animation references /// /// /// True, then only referencable prefab instances are upgraded. /// False, then only non-referencable prefab instances are upgraded. void UpgradePrefabAssets(bool upgradeReferencables) { for (var p = 0; p < m_PrefabManager.PrefabCount; ++p) { m_CurrentSceneOrPrefab = m_PrefabManager.GetPrefabAssetPath(p); #if DEBUG_HELPERS Debug.Log("Upgrading prefab asset: " + m_CurrentSceneOrPrefab); #endif using var editingScope = new PrefabUtility.EditPrefabContentsScope(m_CurrentSceneOrPrefab); var prefabContents = editingScope.prefabContentsRoot; if (upgradeReferencables ^ UpgradeObjectToCm3.HasReferencableComponent(prefabContents)) continue; var timelineManager = new TimelineManager(prefabContents, m_CurrentSceneOrPrefab); // Note: this logic relies on the fact FreeLooks will be added first in the component list var components = new List(); foreach (var type in m_ObjectUpgrader.RootUpgradeComponentTypes) components.AddRange(prefabContents.GetComponentsInChildren(type, true).ToList()); // upgrade all foreach (var c in components) { if (c == null || c.gameObject == null) continue; // was a hidden rig if (c.GetComponentInParent(true) != null) continue; // is a backup copy // Upgrade prefab and fix timeline references UpgradeObjectComponents(c.gameObject, timelineManager); } // Fix object references UpgradeObjectReferences(new[] { editingScope.prefabContentsRoot }); #if CINEMACHINE_TIMELINE // Fix animation references foreach (var playableDirector in timelineManager.PlayableDirectors) UpdateAnimationBindings(playableDirector); #endif } m_CurrentSceneOrPrefab = string.Empty; } /// /// For each scene: /// - Upgrade referencable prefab instances without deleting obsolete components, /// - Sync referencable prefab instances with their linked copies (to regain lost prefab instance modifications) /// - Delete referencable prefab instance copies /// - Update timeline references /// /// Key: scene index, Value: List of conversion links void UpgradeReferencablePrefabInstances(Dictionary> conversionLinksPerScene) { for (var s = 0; s < m_SceneManager.SceneCount; ++s) { var scene = OpenScene(s); var timelineManager = new TimelineManager(scene); var upgradedObjects = new HashSet(); UpgradePrefabInstances(upgradedObjects, conversionLinksPerScene[s], timelineManager, true); EditorSceneManager.SaveScene(scene); } m_CurrentSceneOrPrefab = string.Empty; } /// /// For each scene: /// - Upgrade non-referencable prefab instances without deleting obsolete components, sync with their /// linked copies (to regain lost prefab instance modifications), delete copies, and update timeline references /// - Upgrade non-prefabs, and update timeline references /// - Fix animation references /// - Fix object references /// - Delete obsolete components /// - Restore timeline names to their original names /// /// Key: scene index, Value: List of conversion links /// Timeline rename mapping void UpgradeRemaining( Dictionary> conversionLinksPerScene, Dictionary timelineRenames) { for (var s = 0; s < m_SceneManager.SceneCount; ++s) { var scene = OpenScene(s); var timelineManager = new TimelineManager(scene); var upgradedObjects = new HashSet(); UpgradePrefabInstances(upgradedObjects, conversionLinksPerScene[s], timelineManager, false); var rootObjects = scene.GetRootGameObjects(); var upgradables = GetUpgradables(rootObjects, m_ObjectUpgrader.RootUpgradeComponentTypes, true); UpgradeNonPrefabs(upgradables, upgradedObjects, timelineManager); // restore dolly references in prefab instances foreach (var go in upgradables) UpgradeObjectComponents(go, null); #if CINEMACHINE_TIMELINE // Fix animation references foreach (var playableDirector in timelineManager.PlayableDirectors) UpdateAnimationBindings(playableDirector); #endif UpgradeObjectReferences(rootObjects); // Clean up all obsolete components foreach (var go in rootObjects) m_ObjectUpgrader.DeleteObsoleteComponents(go); #if CINEMACHINE_TIMELINE // Restore timeline names foreach (var director in timelineManager.PlayableDirectors) { if (timelineRenames.ContainsKey(director.name)) // search based on guid name { director.name = timelineRenames[director.name]; // restore director name if (PrefabUtility.IsPartOfAnyPrefab(director.gameObject)) PrefabUtility.RecordPrefabInstancePropertyModifications(director); } } #endif EditorSceneManager.SaveScene(scene); } m_CurrentSceneOrPrefab = string.Empty; } /// /// For each prefab asset /// - Delete obsolete components /// - Update manager camera caches /// void CleanupPrefabAssets() { for (var p = 0; p < m_PrefabManager.PrefabCount; ++p) { m_CurrentSceneOrPrefab = m_PrefabManager.GetPrefabAssetPath(p); using var editingScope = new PrefabUtility.EditPrefabContentsScope(m_CurrentSceneOrPrefab); var prefabContents = editingScope.prefabContentsRoot; var components = new List(); foreach (var type in m_ObjectUpgrader.RootUpgradeComponentTypes) components.AddRange(prefabContents.GetComponentsInChildren(type, true).ToList()); foreach (var c in components) { if (c == null) continue; // ignore if (c.GetComponentInParent(true) != null) continue; // is a backup copy m_ObjectUpgrader.DeleteObsoleteComponents(c.gameObject); } var managers = prefabContents.GetComponentsInChildren(); foreach (var manager in managers) { manager.InvalidateCameraCache(); var justToUpdateCache = manager.ChildCameras; } } m_CurrentSceneOrPrefab = string.Empty; } static void UpgradeObjectReferences(GameObject[] rootObjects) { foreach (var go in rootObjects) { if (go == null) continue; // ignore deleted objects (prefab instance copies) ReflectionHelpers.RecursiveUpdateBehaviourReferences(go, (expectedType, oldValue) => { var newType = UpgradeObjectToCm3.GetBehaviorReferenceUpgradeType(oldValue); if (expectedType.IsAssignableFrom(newType)) return oldValue.GetComponent(newType) as MonoBehaviour; return oldValue; }); } } /// /// Data to link original prefab data to upgraded prefab data for restoring prefab modifications. /// timelineReferences are used to restore timelines that referenced vcams that needed to be upgraded /// struct ConversionLink { public string originalName; // not guaranteed to be unique public string originalGUIDName; // unique public string convertedGUIDName; // unique public List timelineReferences; } struct UniqueExposedReference { public string directorName; // unique GUID based name public ExposedReference exposedReference; } static List GetAllGameObjects() { var all = (GameObject[])Resources.FindObjectsOfTypeAll(typeof(GameObject)); return all.Where(go => !EditorUtility.IsPersistent(go.transform.root.gameObject) && (go.hideFlags & (HideFlags.NotEditable | HideFlags.HideAndDontSave)) == 0).ToList(); } /// /// First, restores modifications in all prefab instances in the current scene by copying data from the linked /// converted copy of the prefab instance. /// Then, restores timeline references to any of these upgraded instances. /// /// Conversion links for the current scene /// Timeline manager for the current scene /// /// True, then only referencable prefab instances are upgraded. /// False, then only non-referencable prefab instances are upgraded. /// Set of gameObject that have been converted void UpgradePrefabInstances(HashSet upgradedObjects, List conversionLinks, TimelineManager timelineManager, bool upgradeReferencables) { var allGameObjectsInScene = GetAllGameObjects(); foreach (var conversionLink in conversionLinks) { var prefabInstance = Find(conversionLink.originalGUIDName, allGameObjectsInScene); if (prefabInstance == null) continue; // it has been upgraded already #if DEBUG_HELPERS Debug.Log("Upgrading prefab instance: " + conversionLink.originalName); #endif if (upgradeReferencables ^ UpgradeObjectToCm3.HasReferencableComponent(prefabInstance)) { #if DEBUG_HELPERS Debug.Log("SKIPPING because upgradeReferencables=" + upgradeReferencables); #endif continue; } var convertedCopy = Find(conversionLink.convertedGUIDName, allGameObjectsInScene); // Prefab instance modification that added an old vcam needs to be upgraded, // all other prefab instances were indirectly upgraded when the Prefab Asset was upgraded UpgradeObjectComponents(prefabInstance, null); SynchronizeComponents(prefabInstance, convertedCopy, m_ObjectUpgrader.ObsoleteComponentTypesToDelete); #if CINEMACHINE_TIMELINE timelineManager.UpdateTimelineReference(prefabInstance.GetComponent(), conversionLink); var playableDirectors = prefabInstance.GetComponentsInChildren(true); for (int i = 0; i < playableDirectors.Length; ++i) UpdateAnimationBindings(playableDirectors[i]); #endif // Restore original scene state (prefab instance name, delete converted copies) prefabInstance.name = conversionLink.originalName; UnityEngine.Object.DestroyImmediate(convertedCopy); PrefabUtility.RecordPrefabInstancePropertyModifications(prefabInstance); upgradedObjects.Add(prefabInstance); } // local functions static GameObject Find(string name, List gos) { return gos.FirstOrDefault(go => go != null && go.name.Equals(name)); } static void SynchronizeComponents(GameObject prefabInstance, GameObject convertedCopy, List noDeleteList) { // Transfer values from converted to the instance var components = convertedCopy.GetComponents(); foreach (var c in components) { UnityEditorInternal.ComponentUtility.CopyComponent(c); var c2 = prefabInstance.GetComponent(c.GetType()); if (c2 == null) UnityEditorInternal.ComponentUtility.PasteComponentAsNew(prefabInstance); else UnityEditorInternal.ComponentUtility.PasteComponentValues(c2); } // Delete instance components that are no longer applicable components = prefabInstance.GetComponents(); foreach (var c in components) { // We'll delete these later if (noDeleteList.Contains(c.GetType())) continue; if (convertedCopy.GetComponent(c.GetType()) == null) UnityEngine.Object.DestroyImmediate(c); } } } /// /// Upgrades all instances that are not part of any prefab and restores timeline references. /// /// GameObjects to upgrade in the current scene /// Object upgraded /// Timeline manager for the current scene void UpgradeNonPrefabs( List gos, HashSet upgradedObjects, TimelineManager timelineManager) { foreach (var go in gos) { // Skip prefab instances (they are done separately) if (PrefabUtility.GetPrefabInstanceStatus(go) != PrefabInstanceStatus.NotAPrefab) continue; // Don't upgrade twice if (upgradedObjects.Contains(go)) continue; upgradedObjects.Add(go); UpgradeObjectComponents(go, timelineManager); } } #if CINEMACHINE_TIMELINE void UpdateAnimationBindings(PlayableDirector playableDirector) { if (playableDirector == null) return; var playableAsset = playableDirector.playableAsset; if (playableAsset is TimelineAsset timelineAsset) { var tracks = timelineAsset.GetOutputTracks(); foreach (var track in tracks) { if (track is AnimationTrack animationTrack) { var binding = playableDirector.GetGenericBinding(track); #if DEBUG_HELPERS if (binding == null) { Debug.Log("Binding is null for " + GetFullName(playableDirector.gameObject, m_CurrentSceneOrPrefab) + ", track:" + track.name + ", PlayableAsset=" + playableAsset.name); } #endif if (binding is Animator trackAnimator) { if (!animationTrack.inClipMode) m_ObjectUpgrader.ProcessAnimationClip(animationTrack.infiniteClip, trackAnimator); //uses recorded clip else { var clips = animationTrack.GetClips(); var animationClips = clips .Select(c => c.asset) //animation clip is stored in the clip's asset .OfType() //need to cast to the correct asset type .Select(asset => asset.clip); //finally we get an animation clip! foreach (var animationClip in animationClips) m_ObjectUpgrader.ProcessAnimationClip(animationClip, trackAnimator); } } } } } } #endif CinemachineUpgradeManager(bool initPrefabManager) { m_ObjectUpgrader = new UpgradeObjectToCm3(); m_SceneManager = new SceneManager(); if (initPrefabManager) m_PrefabManager = new PrefabManager(m_ObjectUpgrader.RootUpgradeComponentTypes); } Scene OpenScene(int sceneIndex) { m_CurrentSceneOrPrefab = m_SceneManager.GetScenePath(sceneIndex); #if DEBUG_HELPERS Debug.Log("Opening scene: " + m_CurrentSceneOrPrefab); #endif return EditorSceneManager.OpenScene(m_CurrentSceneOrPrefab, OpenSceneMode.Single); } static string GetFullName(GameObject go, string pathToRoot) { string path = go.name; while (go.transform.parent != null) { go = go.transform.parent.gameObject; path = go.name + "/" + path; } return pathToRoot + "/" + path; } /// /// First upgrades the input gameObject's components without deleting the obsolete components. /// Then upgrades timeline references (if timelineManager is not null) to the upgraded gameObject. /// If the object could not be fully upgraded, a pre-upgrade /// copy is created and returned, else returns null. /// GameObject UpgradeObjectComponents(GameObject go, TimelineManager timelineManager) { // Grab the old component to update the CmShot timeline references var oldComponent = go.GetComponent(); var notUpgradable = m_ObjectUpgrader.UpgradeComponents(go); #if CINEMACHINE_TIMELINE // Patch the timeline shots if (timelineManager != null && oldComponent != null) { var newComponent = go.GetComponent(); if (oldComponent != newComponent) timelineManager.UpdateTimelineReference(oldComponent, newComponent); } #endif // Report difficult cases if (notUpgradable != null) { notUpgradable.name = go.name + k_UnupgradableTag; Debug.LogWarning("Upgrader: " + GetFullName(go, m_CurrentSceneOrPrefab) + " may not have been fully upgraded " + "automatically. A reference copy of the original was saved to " + notUpgradable.name); } return notUpgradable; } /// /// Finds all gameObjects with components . /// /// GameObjects to check. /// Find gameObjects that have these components on them or on their children. /// Sort output. First, candidates that may be referenced and then others. /// Sorted list of candidates. First, candidates that may be referenced by others. List GetUpgradables(GameObject[] rootObjects, List componentTypes, bool sort) { var components = new List(); if (rootObjects != null) foreach (var type in componentTypes) foreach (var go in rootObjects) components.AddRange(go.GetComponentsInChildren(type, true).ToList()); var upgradables = new List(); foreach (var c in components) { // Ignore hidden-object freeLook rigs if (c == null || IsHiddenFreeLookRig(c)) continue; // Ignore backup copies that we may have created in a previous upgrade attempt if (c.GetComponentInParent(true) != null) continue; upgradables.Add(c.gameObject); } return upgradables; } // Hack: ignore nested rigs of a freeLook (GML todo: how to remove this?) static bool IsHiddenFreeLookRig(Component c) { return c is not CinemachineFreeLook && c.GetComponentInParent(true) != null; } class SceneManager { List m_AllScenePaths = new (); public List s_IgnoreList = new() {}; // TODO: expose this to the user so they can ignore scenes they don't want to upgrade public int SceneCount => m_AllScenePaths.Count; public string GetScenePath(int index) => m_AllScenePaths[index]; public SceneManager() { var allSceneGuids = new List(); allSceneGuids.AddRange(AssetDatabase.FindAssets("t:scene", new[] { "Assets" })); for (var i = allSceneGuids.Count - 1; i >= 0; --i) { var sceneGuid = allSceneGuids[i]; var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid); var add = true; for (var j = 0; add && j < s_IgnoreList.Count; ++j) if (scenePath.Contains(s_IgnoreList[j])) add = false; if (add) m_AllScenePaths.Add(scenePath); } #if DEBUG_HELPERS Debug.Log("**** All scenes ****"); m_AllScenePaths.ForEach(path => Debug.Log(path)); Debug.Log("********************"); #endif } } /// /// Terminology: /// - Prefab Asset: prefab source that lives with the assets. /// - Prefab Instance: instance of a Prefab Asset. /// class PrefabManager { List m_PrefabAssets = new (); public int PrefabCount => m_PrefabAssets.Count; public GameObject GetPrefabAsset(int index) => m_PrefabAssets[index]; public string GetPrefabAssetPath(int index) => AssetDatabase.GetAssetPath(m_PrefabAssets[index]); public PrefabManager(List upgradeComponentTypes) { // Get all Prefab Assets in the project var prefabGuids = AssetDatabase.FindAssets($"t:prefab", new [] { "Assets" }); var allPrefabs = prefabGuids.Select( g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))).ToList(); // Select Prefab Assets containing any of the upgradeComponentTypes foreach (var prefab in allPrefabs) { if (prefab.GetComponentInParent(true) != null) continue; foreach (var type in upgradeComponentTypes) { var c = prefab.GetComponentsInChildren(type, true); if (c != null && c.Length > 0) { m_PrefabAssets.Add(prefab); break; } } } // Sort by dependency - non-nested prefabs are first, and then nested prefabs if (m_PrefabAssets.Count > 1) m_PrefabAssets.Sort((a, b) => { // if they are not part of each other then we use the name to compare just for consistency. return IsXPartOfY(a, b) ? -1 : IsXPartOfY(b, a) ? 1 : a.name.CompareTo(b.name); bool IsXPartOfY(GameObject x, GameObject y) { var prefab = PrefabUtility.LoadPrefabContents(AssetDatabase.GetAssetPath(y)); var components = new List(); foreach (var type in upgradeComponentTypes) components.AddRange(prefab.GetComponentsInChildren(type, true).ToList()); var result = false; foreach (var c in components) { if (c == null || IsHiddenFreeLookRig(c)) continue; var prefabInstance = c.gameObject; var r1 = PrefabUtility.GetCorrespondingObjectFromSource(prefabInstance); if (x.Equals(r1)) { result = true; break; } } PrefabUtility.UnloadPrefabContents(prefab); return result; } }); #if DEBUG_HELPERS Debug.Log("**** All prefabs ****"); m_PrefabAssets.ForEach(prefab => Debug.Log(prefab.name)); Debug.Log("*********************"); #endif } public static List FindAllInstancesOfPrefabEvenInNestedPrefabs( Scene scene, string prefabPath) { var allInstances = new List(); foreach (var go in scene.GetRootGameObjects()) { // Check if prefabInstance is an instance of the input prefab var nearestPrefabRoot = PrefabUtility.GetNearestPrefabInstanceRoot(go); if (nearestPrefabRoot != null) { var nearestPrefabRootPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(go); if (nearestPrefabRootPath.Equals(prefabPath) && !allInstances.Contains(nearestPrefabRoot)) allInstances.Add(nearestPrefabRoot); } } return allInstances; } } class TimelineManager { #if !CINEMACHINE_TIMELINE public TimelineManager(GameObject root, string pathToRoot) {} public TimelineManager(Scene scene) {} #else struct Item { public string Name; public List Shots; } Dictionary m_ShotsToUpdate; List m_PlayableDirectors; string m_PathToRootObject; public List PlayableDirectors => m_PlayableDirectors; public TimelineManager(Scene scene) { m_PathToRootObject = scene.path; m_PlayableDirectors = new List(); foreach (var go in scene.GetRootGameObjects()) m_PlayableDirectors.AddRange(go.GetComponentsInChildren(true).ToList()); PruneDuplicates(); CollectShotsToUpdate(); } public TimelineManager(GameObject root, string pathToRoot) { m_PathToRootObject = pathToRoot; m_PlayableDirectors = root.GetComponentsInChildren(true).ToList(); PruneDuplicates(); CollectShotsToUpdate(); } void PruneDuplicates() { for (int i = m_PlayableDirectors.Count - 1; i >= 0; --i) if (m_PlayableDirectors[i].name.StartsWith(k_CopyPrefix)) m_PlayableDirectors.RemoveAt(i); } void CollectShotsToUpdate() { m_ShotsToUpdate = new (); // collect all cmShots that may require a reference update foreach (var playableDirector in m_PlayableDirectors) { var playableAsset = playableDirector.playableAsset; if (playableAsset is TimelineAsset timelineAsset) { var tracks = timelineAsset.GetOutputTracks(); foreach (var track in tracks) { if (track is CinemachineTrack cmTrack) { var clips = cmTrack.GetClips(); foreach (var clip in clips) { if (clip.asset is CinemachineShot cmShot) { if (!m_ShotsToUpdate.ContainsKey(playableDirector)) m_ShotsToUpdate.Add(playableDirector, new () { Name = playableDirector.name, Shots = new () }); m_ShotsToUpdate[playableDirector].Shots.Add(cmShot); } } } } } } } /// /// Updates timeline reference with the upgraded vcam. This is called after each /// vcam is upgraded, but before the obsolete component is deleted. /// public void UpdateTimelineReference( CinemachineVirtualCameraBase oldComponent, CinemachineVirtualCameraBase upgraded) { foreach (var (director, items) in m_ShotsToUpdate) { foreach (var cmShot in items.Shots) { var exposedRef = cmShot.VirtualCamera; var vcam = exposedRef.Resolve(director); if (vcam == oldComponent) director.SetReferenceValue(exposedRef.exposedName, upgraded); } } } public void UpdateTimelineReference(CinemachineVirtualCameraBase upgraded, ConversionLink link) { foreach (var (director, items) in m_ShotsToUpdate) { if (director == null) continue; var references = link.timelineReferences; foreach (var reference in references) { if (reference.directorName != director.name) { #if DEBUG_HELPERS Debug.Log("Skipping reference for " + reference.directorName + " because it is not for " + director.name); #endif continue; // ignore references that are not for this director } foreach (var cmShot in items.Shots) { var exposedRef = cmShot.VirtualCamera; if (exposedRef.exposedName == reference.exposedReference.exposedName) director.SetReferenceValue(exposedRef.exposedName, upgraded); } } } } public List GetTimelineReferences(CinemachineVirtualCameraBase vcam) { var references = new List(); foreach (var (director, items) in m_ShotsToUpdate) { foreach (var cmShot in items.Shots) { var exposedRef = cmShot.VirtualCamera; if (vcam == exposedRef.Resolve(director)) references.Add(new UniqueExposedReference { directorName = director.name, exposedReference = exposedRef }); } } return references; } #endif } } } #pragma warning restore CS0618 #endif