using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; using Cinemachine.Utility; using System.Reflection; using System.Linq; namespace Cinemachine.Editor { [CustomEditor(typeof(CinemachineVirtualCamera))] [CanEditMultipleObjects] internal class CinemachineVirtualCameraEditor : CinemachineVirtualCameraBaseEditor { VcamStageEditorPipeline m_PipelineSet = new VcamStageEditorPipeline(); [MenuItem("CONTEXT/CinemachineVirtualCamera/Adopt Game View Camera Settings")] static void AdoptGameViewCameraSettings(MenuCommand command) { var vcam = command.context as CinemachineVirtualCamera; var brain = CinemachineCore.Instance.FindPotentialTargetBrain(vcam); if (brain != null) { vcam.m_Lens = brain.CurrentCameraState.Lens; vcam.transform.position = brain.transform.position; vcam.transform.rotation = brain.transform.rotation; } } [MenuItem("CONTEXT/CinemachineVirtualCamera/Adopt Scene View Camera Settings")] static void AdoptSceneViewCameraSettings(MenuCommand command) { var vcam = command.context as CinemachineVirtualCamera; vcam.m_Lens = CinemachineMenu.MatchSceneViewCamera(vcam.transform); } protected override void OnEnable() { base.OnEnable(); Undo.undoRedoPerformed += ResetTargetOnUndo; m_PipelineSet.Initialize( // GetComponent (stage, result) => { int numNullComponents = 0; foreach (var obj in targets) { var vcam = obj as CinemachineVirtualCamera; if (vcam != null) { var c = vcam.GetCinemachineComponent(stage); if (c != null) result.Add(c); else ++numNullComponents; } } return numNullComponents; }, // SetComponent (stage, type) => { Undo.SetCurrentGroupName("Cinemachine pipeline change"); foreach (var obj in targets) { var vcam = obj as CinemachineVirtualCamera; Transform owner = vcam == null ? null : vcam.GetComponentOwner(); if (owner == null) continue; // maybe it's a prefab var c = vcam.GetCinemachineComponent(stage); if (c != null && c.GetType() == type) continue; if (c != null) { Undo.DestroyObjectImmediate(c); vcam.InvalidateComponentPipeline(); } if (type != null) { Undo.AddComponent(owner.gameObject, type); vcam.InvalidateComponentPipeline(); } } }); // We only look at the first target here, on purpose if (Target != null && Target.m_LockStageInInspector != null) foreach (var s in Target.m_LockStageInInspector) m_PipelineSet.SetStageIsLocked(s); #if UNITY_2021_2_OR_NEWER CinemachineSceneToolUtility.RegisterTool(typeof(FoVTool)); CinemachineSceneToolUtility.RegisterTool(typeof(FarNearClipTool)); #endif } protected override void OnDisable() { Undo.undoRedoPerformed -= ResetTargetOnUndo; m_PipelineSet.Shutdown(); base.OnDisable(); #if UNITY_2021_2_OR_NEWER CinemachineSceneToolUtility.UnregisterTool(typeof(FoVTool)); CinemachineSceneToolUtility.UnregisterTool(typeof(FarNearClipTool)); #endif } void OnSceneGUI() { m_PipelineSet.OnSceneGUI(); // call hidden editors' OnSceneGUI #if UNITY_2021_2_OR_NEWER DrawSceneTools(); #endif } #if UNITY_2021_2_OR_NEWER void DrawSceneTools() { var vcam = Target; if (vcam == null || !vcam.IsValid || vcam.m_ExcludedPropertiesInInspector.Contains("m_Lens")) { return; } var originalColor = Handles.color; Handles.color = Handles.preselectionColor; if (CinemachineSceneToolUtility.IsToolActive(typeof(FoVTool))) { CinemachineSceneToolHelpers.FovToolHandle(vcam, new SerializedObject(vcam).FindProperty(() => vcam.m_Lens), vcam.m_Lens, IsHorizontalFOVUsed()); } else if (CinemachineSceneToolUtility.IsToolActive(typeof(FarNearClipTool))) { CinemachineSceneToolHelpers.NearFarClipHandle(vcam, new SerializedObject(vcam).FindProperty(() => vcam.m_Lens)); } Handles.color = originalColor; } #endif public override void OnInspectorGUI() { if (Target.GetComponentOwner() == null) { EditorGUILayout.HelpBox( "It's not possible to add this component to a prefab instance. " + "Instead, you can open the Prefab in Prefab Mode or unpack " + "the Prefab instance to remove the Prefab connection.", MessageType.Error); return; } BeginInspector(); DrawHeaderInInspector(); DrawPropertyInInspector(FindProperty(x => x.m_Priority)); DrawTargetsInInspector(FindProperty(x => x.m_Follow), FindProperty(x => x.m_LookAt)); DrawPropertyInInspector(FindProperty(x => x.m_StandbyUpdate)); DrawLensSettingsInInspector(FindProperty(x => x.m_Lens)); DrawRemainingPropertiesInInspector(); m_PipelineSet.OnInspectorGUI(!IsPropertyExcluded("Header")); DrawExtensionsWidgetInInspector(); } void ResetTargetOnUndo() { ResetTarget(); for (int i = 0; i < targets.Length; ++i) (targets[i] as CinemachineVirtualCamera).InvalidateComponentPipeline(); } /// /// Register with CinemachineVirtualCamera to create the pipeline in an undo-friendly manner /// [InitializeOnLoad] class CreatePipelineWithUndo { static CreatePipelineWithUndo() { CinemachineVirtualCamera.CreatePipelineOverride = (CinemachineVirtualCamera vcam, string name, CinemachineComponentBase[] copyFrom) => { // Recycle existing pipeline child (if any) GameObject go = null; foreach (Transform child in vcam.transform) { if (child.GetComponent() != null) { go = child.gameObject; break; } } if (go == null) { // Create a new pipeline - can't do it if prefab instance if (PrefabUtility.IsPartOfAnyPrefab(vcam.gameObject)) return null; go = ObjectFactory.CreateGameObject(name); Undo.RegisterCreatedObjectUndo(go, "created pipeline"); Undo.SetTransformParent(go.transform, vcam.transform, "parenting pipeline"); Undo.AddComponent(go); } var oldStuff = go.GetComponents(); foreach (var c in oldStuff) Undo.DestroyObjectImmediate(c); // If copying, transfer the components if (copyFrom != null) { foreach (Component c in copyFrom) { Component copy = Undo.AddComponent(go, c.GetType()); Undo.RecordObject(copy, "copying pipeline"); ReflectionHelpers.CopyFields(c, copy); } } return go.transform; }; CinemachineVirtualCamera.DestroyPipelineOverride = (GameObject pipeline) => { var oldStuff = pipeline.GetComponents(); foreach (var c in oldStuff) Undo.DestroyObjectImmediate(c); // Cannot create or destroy child objects if prefab. // Just leave it there in that case, it will get discovered and recycled. if (!PrefabUtility.IsPartOfAnyPrefab(pipeline)) Undo.DestroyObjectImmediate(pipeline); }; } } // Because the cinemachine components are attached to hidden objects, their // gizmos don't get drawn by default. We have to do it explicitly. [InitializeOnLoad] static class CollectGizmoDrawers { static CollectGizmoDrawers() { m_GizmoDrawers = new Dictionary(); string definedIn = typeof(CinemachineComponentBase).Assembly.GetName().Name; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { // Note that we have to call GetName().Name. Just GetName() will not work. if ((!assembly.GlobalAssemblyCache) && ((assembly.GetName().Name == definedIn) || assembly.GetReferencedAssemblies().Any(a => a.Name == definedIn))) { try { foreach (var type in assembly.GetTypes()) { try { bool added = false; foreach (var method in type.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { if (added) break; if (!method.IsStatic) continue; var attributes = method.GetCustomAttributes(typeof(DrawGizmo), true) as DrawGizmo[]; foreach (var a in attributes) { if (typeof(CinemachineComponentBase).IsAssignableFrom(a.drawnType) && !a.drawnType.IsAbstract) { m_GizmoDrawers.Add(a.drawnType, method); added = true; break; } } } } catch (Exception) {} // Just skip uncooperative types } } catch (Exception) {} // Just skip uncooperative assemblies } } } public static Dictionary m_GizmoDrawers; } [DrawGizmo(GizmoType.Active | GizmoType.InSelectionHierarchy, typeof(CinemachineVirtualCamera))] internal static void DrawVirtualCameraGizmos(CinemachineVirtualCamera vcam, GizmoType selectionType) { var pipeline = vcam.GetComponentPipeline(); if (pipeline != null) { foreach (var c in pipeline) { if (c == null) continue; MethodInfo method; if (CollectGizmoDrawers.m_GizmoDrawers.TryGetValue(c.GetType(), out method)) { if (method != null) method.Invoke(null, new object[] {c, selectionType}); } } } } } }