using UnityEngine; using UnityEditor; using UnityEngine.Splines; namespace Unity.Cinemachine.Editor { static class CinemachineMenu { const string m_CinemachineAssetsRootMenu = "Assets/Create/Cinemachine/"; const string m_CinemachineGameObjectRootMenu = "GameObject/Cinemachine/"; const int m_GameObjectMenuPriority = 11; // Right after Camera. // Assets Menu [MenuItem(m_CinemachineAssetsRootMenu + "BlenderSettings")] static void CreateBlenderSettingAsset() { ScriptableObjectUtility.Create(); } [MenuItem(m_CinemachineAssetsRootMenu + "NoiseSettings")] static void CreateNoiseSettingAsset() { ScriptableObjectUtility.Create(); } [MenuItem(m_CinemachineAssetsRootMenu + "Fixed Signal Definition")] static void CreateFixedSignalDefinition() { ScriptableObjectUtility.Create(); } // GameObject Menu [MenuItem(m_CinemachineGameObjectRootMenu + "Cinemachine Camera", false, m_GameObjectMenuPriority)] static void CreateVirtualCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Virtual Camera"); CreatePassiveCmCamera(parentObject: command.context as GameObject, select: true); } [MenuItem(m_CinemachineGameObjectRootMenu + "Targeted Cameras/Follow Camera", false, m_GameObjectMenuPriority)] static void CreateFollowCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Virtual Camera"); var targetObject = command.context as GameObject; var parent = targetObject == null || targetObject.transform.parent == null ? null : targetObject.transform.parent.gameObject; var vcam = CreateCinemachineObject("CinemachineCamera", parent, true); if (targetObject != null) vcam.Follow = targetObject.transform; vcam.Lens = MatchSceneViewCamera(vcam.transform); Undo.AddComponent(vcam.gameObject); Undo.AddComponent(vcam.gameObject); } [MenuItem(m_CinemachineGameObjectRootMenu + "Targeted Cameras/2D Camera", false, m_GameObjectMenuPriority)] static void Create2DCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("2D Camera"); var targetObject = command.context as GameObject; var parent = targetObject == null || targetObject.transform.parent == null ? null : targetObject.transform.parent.gameObject; var vcam = CreateCinemachineObject("CinemachineCamera", parent, true); if (targetObject != null) vcam.Follow = targetObject.transform; vcam.Lens = MatchSceneViewCamera(vcam.transform); Undo.AddComponent(vcam.gameObject); } [MenuItem(m_CinemachineGameObjectRootMenu + "Targeted Cameras/FreeLook Camera", false, m_GameObjectMenuPriority)] static void CreateFreeLookCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("FreeLook Camera"); var targetObject = command.context as GameObject; var parent = targetObject == null || targetObject.transform.parent == null ? null : targetObject.transform.parent.gameObject; var vcam = CreatePassiveCmCamera("FreeLook Camera", parent, true); if (targetObject != null) vcam.Follow = targetObject.transform; Undo.AddComponent(vcam.gameObject).OrbitStyle = CinemachineOrbitalFollow.OrbitStyles.ThreeRing; Undo.AddComponent(vcam.gameObject); Undo.AddComponent(vcam.gameObject); Undo.AddComponent(vcam.gameObject).enabled = false; } #if CINEMACHINE_PHYSICS [MenuItem(m_CinemachineGameObjectRootMenu + "Targeted Cameras/Third Person Aim Camera", false, m_GameObjectMenuPriority)] static void CreateThirdPersonAimCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Third Person Aim Camera"); var targetObject = command.context as GameObject; var parent = targetObject == null || targetObject.transform.parent == null ? null : targetObject.transform.parent.gameObject; var vcam = CreatePassiveCmCamera("Third Person Aim Camera", parent, true); if (targetObject != null) vcam.Follow = targetObject.transform; var thirdPersonFollow = Undo.AddComponent(vcam.gameObject); thirdPersonFollow.ShoulderOffset = new Vector3(0.8f, -0.4f, 0f); thirdPersonFollow.CameraSide = 1; thirdPersonFollow.VerticalArmLength = 1.2f; thirdPersonFollow.CameraDistance = 4f; Undo.AddComponent(vcam.gameObject); } #endif [MenuItem(m_CinemachineGameObjectRootMenu + "Targeted Cameras/Target Group Camera", false, m_GameObjectMenuPriority)] static void CreateTargetGroupCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Target Group Camera"); var targetObject = command.context as GameObject; var parent = targetObject == null || targetObject.transform.parent == null ? null : targetObject.transform.parent.gameObject; var vcam = CreateCinemachineObject("CinemachineCamera", parent, false); vcam.Lens = MatchSceneViewCamera(vcam.transform); Undo.AddComponent(vcam.gameObject); Undo.AddComponent(vcam.gameObject); Undo.AddComponent(vcam.gameObject).enabled = false; var targetGroup = CreateCinemachineObject( "Target Group", parent, true); vcam.Follow = targetGroup.transform; targetGroup.AddMember(targetObject == null ? null : targetObject.transform, 1, 0.5f); } [MenuItem(m_CinemachineGameObjectRootMenu + "Sequencer Camera", false, m_GameObjectMenuPriority)] static void CreateBlendListCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Blend List Camera"); var sequencer = CreateCinemachineObject( "Sequencer Camera", command.context as GameObject, true); // We give the camera a couple of children as an example of setup var childVcam1 = CreatePassiveCmCamera(parentObject: sequencer.gameObject); var childVcam2 = CreatePassiveCmCamera(parentObject: sequencer.gameObject); childVcam2.Lens.FieldOfView = 10; // Set up initial instruction set sequencer.Instructions = new () { new () { Camera = childVcam1, Hold = 1 }, new () { Camera = childVcam2, Blend = new () { Style = CinemachineBlendDefinition.Styles.EaseInOut, Time = 2f }} }; } #if CINEMACHINE_UNITY_ANIMATION [MenuItem(m_CinemachineGameObjectRootMenu + "State-Driven Camera", false, m_GameObjectMenuPriority)] static void CreateStateDivenCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("State-Driven Camera"); var stateDrivenCamera = CreateCinemachineObject( "State-Driven Camera", command.context as GameObject, true); // We give the camera a child as an example setup CreatePassiveCmCamera(parentObject: stateDrivenCamera.gameObject); } #endif #if CINEMACHINE_PHYSICS [MenuItem(m_CinemachineGameObjectRootMenu + "ClearShot Camera", false, m_GameObjectMenuPriority)] static void CreateClearShotCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("ClearShot Camera"); var clearShotCamera = CreateCinemachineObject( "ClearShot Camera", command.context as GameObject, true); // We give the camera a child as an example setup var childVcam = CreatePassiveCmCamera(parentObject: clearShotCamera.gameObject); Undo.AddComponent(childVcam.gameObject).AvoidObstacles.Enabled = false; } #endif [MenuItem(m_CinemachineGameObjectRootMenu + "Mixing Camera", false, m_GameObjectMenuPriority)] static void CreateMixingCamera(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Mixing Camera"); var mixingCamera = CreateCinemachineObject( "Mixing Camera", command.context as GameObject, true); // We give the camera a couple of children as an example of setup CreatePassiveCmCamera(parentObject: mixingCamera.gameObject); CreatePassiveCmCamera(parentObject: mixingCamera.gameObject); } [MenuItem(m_CinemachineGameObjectRootMenu + "Dolly Camera with Spline", false, m_GameObjectMenuPriority)] static void CreateDollyCameraWithPath(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Dolly Camera with Track"); var vcam = CreateCinemachineObject( "CinemachineCamera", command.context as GameObject, true); vcam.Lens = MatchSceneViewCamera(vcam.transform); Undo.AddComponent(vcam.gameObject); var splineContainer = ObjectFactory.CreateGameObject( "Dolly Spline", typeof(SplineContainer)).GetComponent(); splineContainer.Spline.SetTangentMode(TangentMode.AutoSmooth); splineContainer.Spline.Add(new BezierKnot(Vector3.zero)); splineContainer.Spline.Add(new BezierKnot(Vector3.right)); var splineDolly = Undo.AddComponent(vcam.gameObject); splineDolly.Spline = splineContainer; } [MenuItem(m_CinemachineGameObjectRootMenu + "Dolly Cart with Spline", false, m_GameObjectMenuPriority)] static void CreateDollyTrackWithCart(MenuCommand command) { CinemachineEditorAnalytics.SendCreateEvent("Dolly Track with Cart"); var splineContainer = ObjectFactory.CreateGameObject( "Dolly Spline", typeof(SplineContainer)).GetComponent(); splineContainer.Spline.SetTangentMode(TangentMode.AutoSmooth); splineContainer.Spline.Add(new BezierKnot(Vector3.zero)); splineContainer.Spline.Add(new BezierKnot(Vector3.right)); CreateCinemachineObject( "Dolly Cart", command.context as GameObject, true).Spline = splineContainer; } /// /// Sets the specified to match the position and /// rotation of the camera, and returns the scene view /// camera's lens settings. /// /// The to match with the /// camera. /// A representing the scene view camera's lens public static LensSettings MatchSceneViewCamera(Transform sceneObject) { var lens = LensSettings.Default; // Take initial settings from the GameView camera, because we don't want to override // things like ortho vs perspective - we just want position and FOV var brain = GetOrCreateBrain(); if (brain != null && brain.OutputCamera != null) lens = LensSettings.FromCamera(brain.OutputCamera); if (SceneView.lastActiveSceneView != null) { var src = SceneView.lastActiveSceneView.camera; // Respect scene view preferences - Create Objects at Origin: // only set position and rotation, if sceneObject is not in the exact center if (!AtOrigin(sceneObject.transform.position)) sceneObject.SetPositionAndRotation(src.transform.position, src.transform.rotation); if (lens.Orthographic == src.orthographic) { if (src.orthographic) lens.OrthographicSize = src.orthographicSize; else lens.FieldOfView = src.fieldOfView; } } return lens; static bool AtOrigin(Vector3 a) => a.x == 0f && a.y == 0f && a.z == 0f; } /// /// Creates a with no procedural components. /// public static CinemachineCamera CreatePassiveCmCamera( string name = "CinemachineCamera", GameObject parentObject = null, bool select = false) { var vcam = CreateCinemachineObject(name, parentObject, select); vcam.Lens = MatchSceneViewCamera(vcam.transform); return vcam; } /// /// Creates a Cinemachine in the scene with a specified component. /// /// The type of to add to the new . /// The name of the new . /// The to parent the new to. /// Whether the new should be selected. /// The instance of the component that is added to the new . static T CreateCinemachineObject(string name, GameObject parentObject, bool select) where T : Component { var selected = Selection.activeGameObject; // We always enforce the existence of the CM brain GetOrCreateBrain(); // We use ObjectFactory to create a new GameObject as it automatically supports undo/redo var go = ObjectFactory.CreateGameObject(name); var component = Undo.AddComponent(go); // We ensure that the new object has a unique name, for example "Camera (1)". // This must be done after setting the parent in order to get an accurate unique name GameObjectUtility.EnsureUniqueNameForSibling(go); // Place vcam as set by the unity editor preferences ObjectFactory.PlaceGameObject(go, parentObject); Selection.activeGameObject = select ? go : selected; return component; } /// /// Gets the first loaded . Creates one on /// the if none were found. /// public static CinemachineBrain GetOrCreateBrain() { if (CinemachineBrain.ActiveBrainCount > 0) return CinemachineBrain.GetActiveBrain(0); // Create a CinemachineBrain on the main camera var cam = Camera.main; if (cam == null) #if UNITY_2023_1_OR_NEWER cam = Object.FindFirstObjectByType(FindObjectsInactive.Exclude); #else cam = Object.FindObjectOfType(); #endif if (cam != null) return Undo.AddComponent(cam.gameObject); // No camera, just create a brain on an empty object var obj = ObjectFactory.CreateGameObject("CinemachineBrain", typeof(CinemachineBrain)); Undo.RegisterCreatedObjectUndo(obj, "CinemachineBrain"); return obj.GetComponent(); } } }