Files
GameDevTVObstacleDodge/Library/PackageCache/com.unity.cinemachine@5342685532bb/Editor/Upgrader/CinemachineUpgradeManager.cs

1072 lines
48 KiB
C#

#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
{
/// <summary>
/// Upgrades cm2 to cm3
/// </summary>
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";
/// <summary>
/// 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.
/// </summary>
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();
}
}
/// <summary>
/// Upgrades all the gameObjects in the current scene.
/// Obsolete components are deleted. Timeline references are not patched.
/// Undo is supported.
/// </summary>
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<GameObject>();
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();
}
}
/// <summary>
/// Upgrades all objects in all scenes and prefabs
/// </summary>
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 corrupted. It would be wise to restore the backup.\n\n"
+ "Please see the console messages for details.",
"OK");
}
/// <summary>Returns true if any of the objects are prefab instances or prefabs.</summary>
/// <param name="objects"></param>
/// <returns></returns>
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.IsPartOfPrefabInstance(go) || PrefabUtility.IsPartOfPrefabAsset(go))
return true;
}
return false;
}
/// <summary>Returns true if any of the objects are prefab instances or prefabs.</summary>
/// <param name="objects"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="conversionLinksPerScene">Key: scene index, Value: List of conversion links</param>
/// <param name="renameMap">Timeline rename mapping</param>
void PrepareUpgrades(
out Dictionary<int, List<ConversionLink>> conversionLinksPerScene,
out Dictionary<string, string> renameMap)
{
conversionLinksPerScene = new ();
renameMap = new ();
for (var s = 0; s < m_SceneManager.SceneCount; ++s)
{
var scene = OpenScene(s);
if (!scene.isLoaded)
continue;
// 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<ConversionLink>();
var allPrefabInstances = new List<GameObject>();
for (var p = 0; p < m_PrefabManager.PrefabCount; ++p)
{
allPrefabInstances.AddRange(
PrefabManager.FindAllInstancesOfPrefabEvenInNestedPrefabs(scene,
m_PrefabManager.GetPrefabAssetPath(p)));
}
var upgradedObjects = new HashSet<GameObject>();
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<CinemachineVirtualCameraBase>();
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;
}
/// <summary>
/// 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
/// </summary>
/// <param name="upgradeReferencables">
/// True, then only referencable prefab instances are upgraded.
/// False, then only non-referencable prefab instances are upgraded.</param>
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
try
{
using (var editingScope = new PrefabUtility.EditPrefabContentsScope(m_CurrentSceneOrPrefab))
{
var prefabContents = editingScope.prefabContentsRoot;
// Destroy any lingering invisible CM pipeline objects with invalid scripts, or prefab won't save
var removed = RemoveInvalidComponents(prefabContents.transform);
if (removed > 0)
EditorUtility.SetDirty(prefabContents);
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<Component>();
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<CinemachineDoNotUpgrade>(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
} // Prefabs are automatically saved when exiting the scope
}
catch (Exception e)
{
Debug.LogAssertion("CinemachineUpgradeManager: Failed to open " + m_CurrentSceneOrPrefab + ": " + e.Message);
continue;
}
}
m_CurrentSceneOrPrefab = string.Empty;
static int RemoveInvalidComponents(Transform root)
{
int count = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(root.gameObject);
for (int i = 0; i < root.childCount; ++i)
count += RemoveInvalidComponents(root.GetChild(i));
return count;
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="conversionLinksPerScene">Key: scene index, Value: List of conversion links</param>
void UpgradeReferencablePrefabInstances(Dictionary<int, List<ConversionLink>> conversionLinksPerScene)
{
for (var s = 0; s < m_SceneManager.SceneCount; ++s)
{
var scene = OpenScene(s);
if (!scene.isLoaded)
continue;
var timelineManager = new TimelineManager(scene);
var upgradedObjects = new HashSet<GameObject>();
UpgradePrefabInstances(upgradedObjects, conversionLinksPerScene[s], timelineManager, true);
EditorSceneManager.SaveScene(scene);
}
m_CurrentSceneOrPrefab = string.Empty;
}
/// <summary>
/// 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
/// </summary>
/// <param name="conversionLinksPerScene">Key: scene index, Value: List of conversion links</param>
/// <param name="timelineRenames">Timeline rename mapping</param>
void UpgradeRemaining(
Dictionary<int, List<ConversionLink>> conversionLinksPerScene,
Dictionary<string, string> timelineRenames)
{
for (var s = 0; s < m_SceneManager.SceneCount; ++s)
{
var scene = OpenScene(s);
if (!scene.isLoaded)
continue;
var timelineManager = new TimelineManager(scene);
var upgradedObjects = new HashSet<GameObject>();
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;
}
/// <summary>
/// For each prefab asset
/// - Delete obsolete components
/// - Update manager camera caches
/// </summary>
void CleanupPrefabAssets()
{
for (var p = 0; p < m_PrefabManager.PrefabCount; ++p)
{
m_CurrentSceneOrPrefab = m_PrefabManager.GetPrefabAssetPath(p);
try
{
using var editingScope = new PrefabUtility.EditPrefabContentsScope(m_CurrentSceneOrPrefab);
var prefabContents = editingScope.prefabContentsRoot;
var components = new List<Component>();
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<CinemachineDoNotUpgrade>(true) != null)
continue; // is a backup copy
m_ObjectUpgrader.DeleteObsoleteComponents(c.gameObject);
}
var managers = prefabContents.GetComponentsInChildren<CinemachineCameraManagerBase>();
foreach (var manager in managers)
{
manager.InvalidateCameraCache();
var justToUpdateCache = manager.ChildCameras;
}
}
catch (Exception e)
{
Debug.LogAssertion("CinemachineUpgradeManager: Failed to open " + m_CurrentSceneOrPrefab + ": " + e.Message);
continue;
}
}
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;
});
}
}
/// <summary>
/// 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
/// </summary>
struct ConversionLink
{
public string originalName; // not guaranteed to be unique
public string originalGUIDName; // unique
public string convertedGUIDName; // unique
public List<UniqueExposedReference> timelineReferences;
}
struct UniqueExposedReference
{
public string directorName; // unique GUID based name
public ExposedReference<CinemachineVirtualCameraBase> exposedReference;
}
static List<GameObject> 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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="conversionLinks">Conversion links for the current scene</param>
/// <param name="timelineManager">Timeline manager for the current scene</param>
/// <param name="upgradeReferencables">
/// True, then only referencable prefab instances are upgraded.
/// False, then only non-referencable prefab instances are upgraded.</param>
/// <param name="upgradedObjects">Set of gameObject that have been converted</param>
void UpgradePrefabInstances(HashSet<GameObject> upgradedObjects, List<ConversionLink> 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<CinemachineCamera>(), conversionLink);
var playableDirectors = prefabInstance.GetComponentsInChildren<PlayableDirector>(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<GameObject> gos)
{
return gos.FirstOrDefault(go => go != null && go.name.Equals(name));
}
static void SynchronizeComponents(GameObject prefabInstance, GameObject convertedCopy, List<Type> noDeleteList)
{
// Transfer values from converted to the instance
var components = convertedCopy.GetComponents<MonoBehaviour>();
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<MonoBehaviour>();
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);
}
}
}
/// <summary>
/// Upgrades all instances that are not part of any prefab and restores timeline references.
/// </summary>
/// <param name="gos">GameObjects to upgrade in the current scene</param>
/// <param name="upgradedObjects">Object upgraded</param>
/// <param name="timelineManager">Timeline manager for the current scene</param>
void UpgradeNonPrefabs(
List<GameObject> gos, HashSet<GameObject> 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<AnimationPlayableAsset>() //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
try
{
return EditorSceneManager.OpenScene(m_CurrentSceneOrPrefab, OpenSceneMode.Single);
}
catch (Exception e)
{
Debug.LogAssertion("CinemachineUpgradeManager: Failed to open " + m_CurrentSceneOrPrefab + ": " + e.Message);
return default;
}
}
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;
}
/// <summary>
/// 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.
/// </summary>
GameObject UpgradeObjectComponents(GameObject go, TimelineManager timelineManager)
{
// Grab the old component to update the CmShot timeline references
var oldComponent = go.GetComponent<CinemachineVirtualCameraBase>();
var notUpgradable = m_ObjectUpgrader.UpgradeComponents(go);
#if CINEMACHINE_TIMELINE
// Patch the timeline shots
if (timelineManager != null && oldComponent != null)
{
var newComponent = go.GetComponent<CinemachineCamera>();
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;
}
/// <summary>
/// Finds all gameObjects with components <paramref name="componentTypes"/>.
/// </summary>
/// <param name="rootObjects">GameObjects to check.</param>
/// <param name="componentTypes">Find gameObjects that have these components on them or on their children.</param>
/// <param name="sort">Sort output. First, candidates that may be referenced and then others.</param>
/// <returns>Sorted list of candidates. First, candidates that may be referenced by others.</returns>
List<GameObject> GetUpgradables(GameObject[] rootObjects, List<Type> componentTypes, bool sort)
{
var components = new List<Component>();
if (rootObjects != null)
foreach (var type in componentTypes)
foreach (var go in rootObjects)
components.AddRange(go.GetComponentsInChildren(type, true).ToList());
var upgradables = new List<GameObject>();
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<CinemachineDoNotUpgrade>(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<CinemachineFreeLook>(true) != null;
}
class SceneManager
{
List<string> m_AllScenePaths = new ();
public List<string> 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<string>();
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
}
}
/// <summary>
/// Terminology:
/// - Prefab Asset: prefab source that lives with the assets.
/// - Prefab Instance: instance of a Prefab Asset.
/// </summary>
class PrefabManager
{
List<GameObject> 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<Type> upgradeComponentTypes)
{
// Get all Prefab Assets in the project
var prefabGuids = AssetDatabase.FindAssets($"t:prefab", new [] { "Assets" });
var allPrefabs = prefabGuids.Select(
g => AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g))).ToList();
// Select Prefab Assets containing any of the upgradeComponentTypes
foreach (var prefab in allPrefabs)
{
if (prefab.GetComponentInParent<CinemachineDoNotUpgrade>(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)
{
GameObject prefab;
try
{
prefab = PrefabUtility.LoadPrefabContents(AssetDatabase.GetAssetPath(y));
}
catch (Exception e)
{
Debug.LogAssertion("CinemachineUpgradeManager: Failed to load prefab contents for " + y.name + ": " + e.Message);
return false;
}
var components = new List<Component>();
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<GameObject> FindAllInstancesOfPrefabEvenInNestedPrefabs(
Scene scene, string prefabPath)
{
var allInstances = new List<GameObject>();
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<CinemachineShot> Shots; }
Dictionary<PlayableDirector, Item> m_ShotsToUpdate;
List<PlayableDirector> m_PlayableDirectors;
string m_PathToRootObject;
public List<PlayableDirector> PlayableDirectors => m_PlayableDirectors;
public TimelineManager(Scene scene)
{
m_PathToRootObject = scene.path;
m_PlayableDirectors = new List<PlayableDirector>();
foreach (var go in scene.GetRootGameObjects())
m_PlayableDirectors.AddRange(go.GetComponentsInChildren<PlayableDirector>(true).ToList());
PruneDuplicates();
CollectShotsToUpdate();
}
public TimelineManager(GameObject root, string pathToRoot)
{
m_PathToRootObject = pathToRoot;
m_PlayableDirectors = root.GetComponentsInChildren<PlayableDirector>(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);
}
}
}
}
}
}
}
/// <summary>
/// Updates timeline reference with the upgraded vcam. This is called after each
/// vcam is upgraded, but before the obsolete component is deleted.
/// </summary>
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<UniqueExposedReference> GetTimelineReferences(CinemachineVirtualCameraBase vcam)
{
var references = new List<UniqueExposedReference>();
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