#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