From 3756b9ba72ae536a8d75aace814d9ee266703b67 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Thu, 4 Jul 2024 16:22:05 -0400 Subject: [PATCH 1/9] add CinemachineVirtualCameraBaseEditor --- com.unity.cinemachine/CHANGELOG.md | 6 +++ .../Editor/Editors/CinemachineCameraEditor.cs | 14 ++--- .../Editors/CinemachineClearShotEditor.cs | 17 ++---- .../CinemachineExternalCameraEditor.cs | 24 --------- .../Editors/CinemachineMixingCameraEditor.cs | 14 +---- .../CinemachineSequencerCameraEditor.cs | 15 +----- .../CinemachineStateDrivenCameraEditor.cs | 20 ++----- .../CinemachineVirtualCameraBaseEditor.cs | 54 +++++++++++++++++++ ...inemachineVirtualCameraBaseEditor.cs.meta} | 5 +- .../Utility/CmCameraInspectorUtility.cs | 13 ----- .../Behaviours/CinemachineExternalCamera.cs | 16 +++--- 11 files changed, 85 insertions(+), 113 deletions(-) delete mode 100644 com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs create mode 100644 com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs rename com.unity.cinemachine/Editor/Editors/{CinemachineExternalCameraEditor.cs.meta => CinemachineVirtualCameraBaseEditor.cs.meta} (69%) diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index 60b94f4c5..be445ee28 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.1.2] - 2025-01-01 + +### Added +- Added CinemachineVirtualCameraBaseEditor, to make it easier to make conformant inspectors for custom virtual cameras and virtual camera managers. + + ## [3.1.1] - 2024-06-15 ### Added diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineCameraEditor.cs index eadfb8e50..f7bb7d892 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineCameraEditor.cs @@ -8,7 +8,7 @@ namespace Unity.Cinemachine.Editor { [CustomEditor(typeof(CinemachineCamera))] [CanEditMultipleObjects] - class CinemachineCameraEditor : UnityEditor.Editor + class CinemachineCameraEditor : CinemachineVirtualCameraBaseEditor { CinemachineCamera Target => target as CinemachineCamera; @@ -34,12 +34,9 @@ static void AdoptSceneViewCameraSettings(MenuCommand command) void OnEnable() => Undo.undoRedoPerformed += ResetTarget; void OnDisable() => Undo.undoRedoPerformed -= ResetTarget; - public override VisualElement CreateInspectorGUI() + protected override void AddInspectorProperties(VisualElement ux) { - var ux = new VisualElement(); - - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux, new () { serializedObject.FindProperty(() => Target.BlendHint) }); + ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.BlendHint))); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.Lens))); var defaultTargetLabel = new ObjectField(""); @@ -58,9 +55,6 @@ public override VisualElement CreateInspectorGUI() ux.AddHeader("Procedural Components"); this.AddPipelineDropdowns(ux); - ux.AddSpace(); - this.AddExtensionsDropdown(ux); - ux.TrackAnyUserActivity(() => { if (Target == null) @@ -74,8 +68,6 @@ public override VisualElement CreateInspectorGUI() defaultTargetLabel.value = Target.Follow; CmCameraInspectorUtility.SortComponents(target as CinemachineVirtualCameraBase); }); - - return ux; } [EditorTool("Field of View Tool", typeof(CinemachineCamera))] diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineClearShotEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineClearShotEditor.cs index d4c7b0a4e..6cd51e4a9 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineClearShotEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineClearShotEditor.cs @@ -7,7 +7,7 @@ namespace Unity.Cinemachine.Editor { [CustomEditor(typeof(CinemachineClearShot))] [CanEditMultipleObjects] - class CinemachineClearShotEditor : UnityEditor.Editor + class CinemachineClearShotEditor : CinemachineVirtualCameraBaseEditor { CinemachineClearShot Target => target as CinemachineClearShot; EvaluatorState m_EvaluatorState; @@ -22,17 +22,13 @@ static string GetAvailableQualityEvaluatorNames() return "Available Shot Quality Evaluators are: " + names; } - public override VisualElement CreateInspectorGUI() + protected override void AddInspectorProperties(VisualElement ux) { - var ux = new VisualElement(); - - var helpBox = ux.AddChild(new HelpBox()); - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux); - ux.AddHeader("Global Settings"); this.AddGlobalControls(ux); + var helpBox = ux.AddChild(new HelpBox()); + ux.AddHeader("Clear Shot"); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.DefaultTarget))); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.ActivateAfter))); @@ -41,10 +37,6 @@ public override VisualElement CreateInspectorGUI() ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.DefaultBlend))); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.CustomBlends))); - ux.AddSpace(); - this.AddChildCameras(ux, GetChildWarningMessage); - this.AddExtensionsDropdown(ux); - ux.TrackAnyUserActivity(() => { if (Target == null) @@ -80,7 +72,6 @@ public override VisualElement CreateInspectorGUI() break; } }); - return ux; } enum EvaluatorState diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs deleted file mode 100644 index 2e0179908..000000000 --- a/com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using UnityEditor; -using UnityEngine.UIElements; -using UnityEditor.UIElements; - -namespace Unity.Cinemachine.Editor -{ - [CustomEditor(typeof(CinemachineExternalCamera))] - [CanEditMultipleObjects] - class CinemachineExternalCameraEditor : UnityEditor.Editor - { - CinemachineExternalCamera Target => target as CinemachineExternalCamera; - - public override VisualElement CreateInspectorGUI() - { - var ux = new VisualElement(); - - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux, new () { serializedObject.FindProperty(() => Target.BlendHint) }); - ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.LookAtTarget))); - - return ux; - } - } -} diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineMixingCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineMixingCameraEditor.cs index 02a366cbe..a5847bf1a 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineMixingCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineMixingCameraEditor.cs @@ -7,19 +7,14 @@ namespace Unity.Cinemachine.Editor { [CustomEditor(typeof(CinemachineMixingCamera))] - class CinemachineMixingCameraEditor : UnityEditor.Editor + class CinemachineMixingCameraEditor : CinemachineVirtualCameraBaseEditor { CinemachineMixingCamera Target => target as CinemachineMixingCamera; static string WeightPropertyName(int i) => "Weight" + i; - public override VisualElement CreateInspectorGUI() + protected override void AddInspectorProperties(VisualElement ux) { - var ux = new VisualElement(); - - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux); - ux.AddHeader("Global Settings"); this.AddGlobalControls(ux); @@ -47,9 +42,6 @@ public override VisualElement CreateInspectorGUI() DrawProportionIndicator(children, numCameras, totalWeight); })); - ux.AddSpace(); - this.AddExtensionsDropdown(ux); - ux.TrackAnyUserActivity(() => { if (Target == null) @@ -67,8 +59,6 @@ public override VisualElement CreateInspectorGUI() for (int i = 0; i < weights.Count; ++i) weights[i].SetVisible(i < numCameras); }); - - return ux; } void DrawProportionIndicator( diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs index 5a0366e77..a6301849c 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs @@ -8,17 +8,12 @@ namespace Unity.Cinemachine.Editor { [CustomEditor(typeof(CinemachineSequencerCamera))] [CanEditMultipleObjects] - class CinemachineSequencerCameraEditor : UnityEditor.Editor + class CinemachineSequencerCameraEditor : CinemachineVirtualCameraBaseEditor { CinemachineSequencerCamera Target => target as CinemachineSequencerCamera; - public override VisualElement CreateInspectorGUI() + protected override void AddInspectorProperties(VisualElement ux) { - var ux = new VisualElement(); - - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux); - ux.AddHeader("Global Settings"); this.AddGlobalControls(ux); @@ -77,9 +72,6 @@ public override VisualElement CreateInspectorGUI() return row; }; - container.AddSpace(); - this.AddChildCameras(container, null); - container.TrackAnyUserActivity(() => { if (Target == null || list.itemsSource == null) @@ -98,9 +90,6 @@ public override VisualElement CreateInspectorGUI() availableCameras.Clear(); availableCameras.AddRange(Target.ChildCameras); }); - this.AddExtensionsDropdown(ux); - - return ux; // Local function static void FormatInstructionElement(bool isHeader, VisualElement e1, VisualElement e2, VisualElement e3) diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs index 53fab70bb..09bc708f1 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs @@ -11,7 +11,7 @@ namespace Unity.Cinemachine.Editor { [CanEditMultipleObjects] [CustomEditor(typeof(CinemachineStateDrivenCamera))] - class CinemachineStateDrivenCameraEditor : UnityEditor.Editor + class CinemachineStateDrivenCameraEditor : CinemachineVirtualCameraBaseEditor { CinemachineStateDrivenCamera Target => target as CinemachineStateDrivenCamera; @@ -20,15 +20,8 @@ class CinemachineStateDrivenCameraEditor : UnityEditor.Editor List m_TargetStateNames = new(); Dictionary m_StateIndexLookup; - public override VisualElement CreateInspectorGUI() + protected override void AddInspectorProperties(VisualElement ux) { - var ux = new VisualElement(); - - var noTargetHelp = ux.AddChild(new HelpBox("An Animated Target is required.", HelpBoxMessageType.Warning)); - - this.AddCameraStatus(ux); - this.AddTransitionsSection(ux); - ux.AddHeader("Global Settings"); this.AddGlobalControls(ux); @@ -36,8 +29,8 @@ public override VisualElement CreateInspectorGUI() ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.DefaultTarget))); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.DefaultBlend))); ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.CustomBlends))); - ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.AnimatedTarget))); + ux.Add(new PropertyField(serializedObject.FindProperty(() => Target.AnimatedTarget))); var layerProp = serializedObject.FindProperty(() => Target.LayerIndex); var layerSel = ux.AddChild(new PopupField(layerProp.displayName) { tooltip = layerProp.tooltip }); layerSel.AddToClassList(InspectorUtility.AlignFieldClassName); @@ -46,6 +39,7 @@ public override VisualElement CreateInspectorGUI() layerProp.intValue = Mathf.Max(0, m_LayerNames.FindIndex(v => v == evt.newValue)); serializedObject.ApplyModifiedProperties(); }); + var noTargetHelp = ux.AddChild(new HelpBox("An Animated Target is required.", HelpBoxMessageType.Warning)); ux.TrackAnyUserActivity(() => { @@ -166,12 +160,6 @@ public override VisualElement CreateInspectorGUI() availableCameras.Clear(); availableCameras.AddRange(Target.ChildCameras); }); - container.AddSpace(); - this.AddChildCameras(container, null); - container.AddSpace(); - this.AddExtensionsDropdown(ux); - - return ux; // Local function static void FormatInstructionElement( diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs new file mode 100644 index 000000000..08ac554d3 --- /dev/null +++ b/com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs @@ -0,0 +1,54 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace Unity.Cinemachine.Editor +{ + /// + /// Inspector for the CinemachineVirtualCameraBase class. It will be chosen by default + /// by Unity. You can inherit from it and customize it. + /// + [CustomEditor(typeof(CinemachineVirtualCameraBase), true, isFallback = true)] + [CanEditMultipleObjects] + public class CinemachineVirtualCameraBaseEditor : UnityEditor.Editor + { + //protected virtual void OnEnable() => Undo.undoRedoPerformed += ResetTarget; + //protected virtual void OnDisable() => Undo.undoRedoPerformed -= ResetTarget; + + /// + public override VisualElement CreateInspectorGUI() + { + var vcam = target as CinemachineVirtualCameraBase; + var ux = new VisualElement(); + + this.AddCameraStatus(ux); + ux.Add(new PropertyField(serializedObject.FindProperty(() => vcam.Priority))); + ux.Add(new PropertyField(serializedObject.FindProperty(() => vcam.OutputChannel))); + ux.Add(new PropertyField(serializedObject.FindProperty(() => vcam.StandbyUpdate))); + + AddInspectorProperties(ux); + + if (vcam is CinemachineCameraManagerBase manager && targets.Length == 1) + { + ux.AddSpace(); + this.AddChildCameras(ux, null); + } + ux.AddSpace(); + this.AddExtensionsDropdown(ux); + return ux; + } + + /// + /// Called by the default implementation of CreateInspectorGUI() to populate the inspector with the items that + /// are not specific to the base class. Default implementation iterates over the serialized properties and + /// adds an item for each. + /// + /// The VisualElement container to which to add items + protected virtual void AddInspectorProperties(VisualElement ux) + { + var p = serializedObject.FindProperty(() => (target as CinemachineVirtualCameraBase).StandbyUpdate); + if (p.NextVisible(false)) + InspectorUtility.AddRemainingProperties(ux, p); + } + } +} diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs.meta b/com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs.meta similarity index 69% rename from com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs.meta rename to com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs.meta index 97a0d8169..fccf1f9fc 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineExternalCameraEditor.cs.meta +++ b/com.unity.cinemachine/Editor/Editors/CinemachineVirtualCameraBaseEditor.cs.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 -guid: 3226d9f88577be74197693bd17cff8c3 -timeCreated: 1506455627 -licenseType: Pro +guid: e7d72dd62f9cbd947958c4a7482ab605 MonoImporter: + externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 diff --git a/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs b/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs index ae2d9ffc5..e65bd0729 100644 --- a/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs +++ b/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs @@ -151,19 +151,6 @@ public static void AddCameraStatus(this UnityEditor.Editor editor, VisualElement }); } - public static void AddTransitionsSection( - this UnityEditor.Editor editor, VisualElement ux, - List otherProperties = null) - { - var serializedObject = editor.serializedObject; - var target = editor.target as CinemachineVirtualCameraBase; - ux.Add(new PropertyField(serializedObject.FindProperty(() => target.Priority))); - ux.Add(new PropertyField(serializedObject.FindProperty(() => target.OutputChannel))); - ux.Add(new PropertyField(serializedObject.FindProperty(() => target.StandbyUpdate))); - for (int i = 0; otherProperties != null && i < otherProperties.Count; ++i) - ux.Add(new PropertyField(otherProperties[i])); - } - /// Add the pipeline control dropdowns in the inspector public static void AddPipelineDropdowns(this UnityEditor.Editor editor, VisualElement ux) { diff --git a/com.unity.cinemachine/Runtime/Behaviours/CinemachineExternalCamera.cs b/com.unity.cinemachine/Runtime/Behaviours/CinemachineExternalCamera.cs index 93976d47d..f48096a92 100644 --- a/com.unity.cinemachine/Runtime/Behaviours/CinemachineExternalCamera.cs +++ b/com.unity.cinemachine/Runtime/Behaviours/CinemachineExternalCamera.cs @@ -14,14 +14,6 @@ namespace Unity.Cinemachine [HelpURL(Documentation.BaseURL + "manual/CinemachineExternalCamera.html")] public class CinemachineExternalCamera : CinemachineVirtualCameraBase { - /// The object that the camera is looking at. Setting this may improve the - /// quality of the blends to and from this camera - [Tooltip("The object that the camera is looking at. Setting this may improve the " - + "quality of the blends to and from this camera")] - [NoSaveDuringPlay] - [FormerlySerializedAs("m_LookAt")] - public Transform LookAtTarget = null; - /// Hint for transitioning to and from this CinemachineCamera. Hints can be combined, although /// not all combinations make sense. In the case of conflicting hints, Cinemachine will /// make an arbitrary choice. @@ -32,6 +24,14 @@ public class CinemachineExternalCamera : CinemachineVirtualCameraBase [FormerlySerializedAs("m_BlendHint")] public CinemachineCore.BlendHints BlendHint = 0; + /// The object that the camera is looking at. Setting this may improve the + /// quality of the blends to and from this camera + [Tooltip("The object that the camera is looking at. Setting this may improve the " + + "quality of the blends to and from this camera")] + [NoSaveDuringPlay] + [FormerlySerializedAs("m_LookAt")] + public Transform LookAtTarget = null; + Camera m_Camera; CameraState m_State = CameraState.Default; From 9d959b787417a358b0a99bca9e758a2e9a5d5d9a Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 10:19:14 -0400 Subject: [PATCH 2/9] use [ChildCameraPropertyAttribute], layout tweaks --- .../CinemachineSequencerCameraEditor.cs | 19 ++++------------- .../CinemachineStateDrivenCameraEditor.cs | 21 +++++-------------- .../ChildCameraPropertyDrawer.cs | 19 +++++++++++++---- .../Utility/CmCameraInspectorUtility.cs | 3 ++- .../Editor/Utility/InspectorUtility.cs | 2 +- .../CinemachineStateDrivenCamera.cs | 1 + .../Core/CinemachineCameraManagerBase.cs | 5 ++++- .../Core/CinemachinePropertyAttribute.cs | 2 +- 8 files changed, 33 insertions(+), 39 deletions(-) diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs index a6301849c..487d20dae 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSequencerCameraEditor.cs @@ -27,6 +27,7 @@ protected override void AddInspectorProperties(VisualElement ux) HelpBoxMessageType.Info)); var container = ux.AddChild(new VisualElement()); + container.AddHeader("Instructions"); var vcam = Target; var header = container.AddChild(new VisualElement { style = { flexDirection = FlexDirection.Row, marginBottom = -2 } }); FormatInstructionElement(true, @@ -48,21 +49,12 @@ protected override void AddInspectorProperties(VisualElement ux) var instructions = serializedObject.FindProperty(() => Target.Instructions); list.BindProperty(instructions); - // Available camera candidates - var availableCameras = new List(); - list.makeItem = () => { var row = new BindableElement { style = { flexDirection = FlexDirection.Row }}; var def = new CinemachineSequencerCamera.Instruction(); - var vcamSel = row.AddChild(new PopupField - { - bindingPath = SerializedPropertyHelper.PropertyName(() => def.Camera), - choices = availableCameras, - formatListItemCallback = (obj) => obj == null ? "(null)" : obj.name, - formatSelectedValueCallback = (obj) => obj == null ? "(null)" : obj.name - }); + var vcamSel = row.AddChild(new PropertyField(null, "") { bindingPath = SerializedPropertyHelper.PropertyName(() => def.Camera) }); var blend = row.AddChild(new PropertyField(null, "") { bindingPath = SerializedPropertyHelper.PropertyName(() => def.Blend), name = "blendSelector" }); var hold = row.AddChild(InspectorUtility.CreateDraggableField(() => def.Hold, row.AddChild(new Label(" ")), out _)); @@ -85,10 +77,6 @@ protected override void AddInspectorProperties(VisualElement ux) var index = 0; list.Query().Where((e) => e.name == "blendSelector").ForEach((e) => e.style.visibility = (index++ == 0 && !Target.Loop) ? Visibility.Hidden : Visibility.Visible); - - // Gather the camera candidates - availableCameras.Clear(); - availableCameras.AddRange(Target.ChildCameras); }); // Local function @@ -99,10 +87,11 @@ static void FormatInstructionElement(bool isHeader, VisualElement e1, VisualElem e1.style.marginLeft = isHeader ? 2 * InspectorUtility.SingleLineHeight - 3 : 0; e1.style.flexBasis = floatFieldWidth + InspectorUtility.SingleLineHeight; e1.style.flexGrow = 1; + e1.style.flexShrink = 0; - e2.style.marginLeft = isHeader ? 4 * InspectorUtility.SingleLineHeight - 3 : 0; e2.style.flexBasis = floatFieldWidth + InspectorUtility.SingleLineHeight; e2.style.flexGrow = 1; + e2.style.flexShrink = 0; floatFieldWidth += isHeader ? InspectorUtility.SingleLineHeight/2 - 1 : 0; e3.style.flexBasis = floatFieldWidth; diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs index 09bc708f1..5af0a2da8 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineStateDrivenCameraEditor.cs @@ -56,6 +56,7 @@ protected override void AddInspectorProperties(VisualElement ux) HelpBoxMessageType.Info)); var container = ux.AddChild(new VisualElement() { style = { marginTop = 6 }}); + container.AddHeader("Instructions"); var vcam = Target; var header = container.AddChild(new VisualElement { style = { flexDirection = FlexDirection.Row, marginBottom = -2 } }); FormatInstructionElement(true, @@ -78,16 +79,13 @@ protected override void AddInspectorProperties(VisualElement ux) var instructions = serializedObject.FindProperty(() => Target.Instructions); list.BindProperty(instructions); - // Available camera candidates - var availableCameras = new List(); - list.makeItem = () => { var row = new BindableElement { style = { flexDirection = FlexDirection.Row }}; var def = new CinemachineStateDrivenCamera.Instruction(); - // This is the real state field, but it's hiddes + // This is the real state field, but it's hidden var hashField = row.AddChild(new IntegerField() { bindingPath = SerializedPropertyHelper.PropertyName(() => def.FullHash) }); hashField.SetVisible(false); @@ -129,14 +127,7 @@ protected override void AddInspectorProperties(VisualElement ux) evt.StopPropagation(); }); - var vcamSel = row.AddChild(new PopupField - { - bindingPath = SerializedPropertyHelper.PropertyName(() => def.Camera), - choices = availableCameras, - formatListItemCallback = (obj) => obj == null ? "(null)" : obj.name, - formatSelectedValueCallback = (obj) => obj == null ? "(null)" : obj.name - }); - + var vcamSel = row.AddChild(new PropertyField(null, "") { bindingPath = SerializedPropertyHelper.PropertyName(() => def.Camera) }); var wait = row.AddChild(InspectorUtility.CreateDraggableField(() => def.ActivateAfter, row.AddChild(new Label(" ")), out _)); wait.SafeSetIsDelayed(); var hold = row.AddChild(InspectorUtility.CreateDraggableField(() => def.MinDuration, row.AddChild(new Label(" ")), out _)); @@ -155,10 +146,6 @@ protected override void AddInspectorProperties(VisualElement ux) var isMultiSelect = targets.Length > 1; multiSelectMsg.SetVisible(isMultiSelect); container.SetVisible(!isMultiSelect); - - // Gather the camera candidates - availableCameras.Clear(); - availableCameras.AddRange(Target.ChildCameras); }); // Local function @@ -170,9 +157,11 @@ static void FormatInstructionElement( e1.style.marginLeft = isHeader ? 2 * InspectorUtility.SingleLineHeight - 3 : 0; e1.style.flexBasis = floatFieldWidth + InspectorUtility.SingleLineHeight; e1.style.flexGrow = 1; + e1.style.flexShrink = 0; e2.style.flexBasis = floatFieldWidth + InspectorUtility.SingleLineHeight; e2.style.flexGrow = 1; + e2.style.flexShrink = 0; floatFieldWidth += isHeader ? InspectorUtility.SingleLineHeight/2 - 1 : 0; e3.style.flexBasis = floatFieldWidth; diff --git a/com.unity.cinemachine/Editor/PropertyDrawers/ChildCameraPropertyDrawer.cs b/com.unity.cinemachine/Editor/PropertyDrawers/ChildCameraPropertyDrawer.cs index 9d702c426..625caae4a 100644 --- a/com.unity.cinemachine/Editor/PropertyDrawers/ChildCameraPropertyDrawer.cs +++ b/com.unity.cinemachine/Editor/PropertyDrawers/ChildCameraPropertyDrawer.cs @@ -10,14 +10,25 @@ class ChildCameraPropertyDrawer : PropertyDrawer { public override VisualElement CreatePropertyGUI(SerializedProperty property) { - if (property.serializedObject.targetObject is CinemachineCameraManagerBase m) + if (property.serializedObject.targetObject is CinemachineCameraManagerBase manager) { - var row = new InspectorUtility.LabeledRow(preferredLabel, property.tooltip); - var vcamSel = row.Contents.AddChild(new PopupField() { style = {flexBasis = 10, flexGrow = 1}}); + var vcamSel = new PopupField() { style = {flexBasis = 10, flexGrow = 1}}; vcamSel.BindProperty(property); vcamSel.formatListItemCallback = (obj) => obj == null ? "(null)" : obj.name; vcamSel.formatSelectedValueCallback = (obj) => obj == null ? "(null)" : obj.name; - vcamSel.TrackAnyUserActivity(() => vcamSel.choices = m == null ? null : m.ChildCameras.Cast().ToList()); + vcamSel.TrackAnyUserActivity(() => + { + if (manager != null && manager.ChildCameras != null) + { + vcamSel.choices = manager.ChildCameras.Cast().ToList(); + vcamSel.SetValueWithoutNotify(property.objectReferenceValue); + } + }); + if (preferredLabel.Length == 0) + return vcamSel; + + var row = new InspectorUtility.LabeledRow(preferredLabel, property.tooltip); + row.Contents.AddChild(vcamSel); return row; } return new PropertyField(property, preferredLabel); diff --git a/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs b/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs index e65bd0729..1d6d0b122 100644 --- a/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs +++ b/com.unity.cinemachine/Editor/Utility/CmCameraInspectorUtility.cs @@ -519,7 +519,7 @@ public static void AddChildCameras( { name = "vcamSelector", objectType = typeof(CinemachineVirtualCameraBase), - style = { flexBasis = 20, flexGrow = 1 } + style = { flexBasis = floatFieldWidth * 2, flexGrow = 1, flexShrink = 0 } }).SetEnabled(false); var priorityField = row.AddChild(InspectorUtility.CreateDraggableField( @@ -527,6 +527,7 @@ public static void AddChildCameras( priorityField.name = "priorityField"; priorityField.style.flexBasis = floatFieldWidth; priorityField.style.flexGrow = 0; + priorityField.style.flexShrink = 0; priorityField.style.marginRight = 4; priorityField.SafeSetIsDelayed(); diff --git a/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs b/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs index ca2d7271e..ecb4a69c2 100644 --- a/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs +++ b/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs @@ -253,7 +253,7 @@ public static void AddHeader(this VisualElement ux, string text, string tooltip row.focusable = false; row.Label.style.flexGrow = 1; row.Label.style.paddingTop = verticalPad; - row.Label.style.paddingBottom = EditorGUIUtility.standardVerticalSpacing - 2; + row.Label.style.paddingBottom = EditorGUIUtility.standardVerticalSpacing; } /// diff --git a/com.unity.cinemachine/Runtime/Behaviours/CinemachineStateDrivenCamera.cs b/com.unity.cinemachine/Runtime/Behaviours/CinemachineStateDrivenCamera.cs index 182338fa4..579d8e0a0 100644 --- a/com.unity.cinemachine/Runtime/Behaviours/CinemachineStateDrivenCamera.cs +++ b/com.unity.cinemachine/Runtime/Behaviours/CinemachineStateDrivenCamera.cs @@ -52,6 +52,7 @@ public struct Instruction /// The virtual camera to activate when the animation state becomes active [Tooltip("The virtual camera to activate when the animation state becomes active")] [FormerlySerializedAs("m_VirtualCamera")] + [ChildCameraProperty] public CinemachineVirtualCameraBase Camera; /// How long to wait (in seconds) before activating the camera. /// This filters out very short state durations diff --git a/com.unity.cinemachine/Runtime/Core/CinemachineCameraManagerBase.cs b/com.unity.cinemachine/Runtime/Core/CinemachineCameraManagerBase.cs index 70b376f69..ecc470531 100644 --- a/com.unity.cinemachine/Runtime/Core/CinemachineCameraManagerBase.cs +++ b/com.unity.cinemachine/Runtime/Core/CinemachineCameraManagerBase.cs @@ -51,6 +51,7 @@ public struct DefaultTargetSettings public CinemachineBlenderSettings CustomBlends = null; List m_ChildCameras; + int m_ChildCountCache; // used for invalidating child cache readonly BlendManager m_BlendManager = new (); CameraState m_State = CameraState.Default; ICinemachineCamera m_TransitioningFrom; @@ -282,10 +283,12 @@ public void InvalidateCameraCache() /// True if a cache rebuild was performed, false if cache is up to date. protected virtual bool UpdateCameraCache() { - if (m_ChildCameras != null) + var childCount = transform.childCount; + if (m_ChildCameras != null && m_ChildCountCache == childCount) return false; PreviousStateIsValid = false; m_ChildCameras = new(); + m_ChildCountCache = childCount; GetComponentsInChildren(true, m_ChildCameras); for (int i = m_ChildCameras.Count-1; i >= 0; --i) if (m_ChildCameras[i].transform.parent != transform) diff --git a/com.unity.cinemachine/Runtime/Core/CinemachinePropertyAttribute.cs b/com.unity.cinemachine/Runtime/Core/CinemachinePropertyAttribute.cs index 5efe34d12..f5ef0e31b 100644 --- a/com.unity.cinemachine/Runtime/Core/CinemachinePropertyAttribute.cs +++ b/com.unity.cinemachine/Runtime/Core/CinemachinePropertyAttribute.cs @@ -163,7 +163,7 @@ public enum RequiredTargets } /// - /// Attribute applied to a CinemachineVirtualCameraBase property to produce + /// Attribute applied to a CinemachineCameraManagerBase property to produce /// a child camera selector in the inspector. /// public sealed class ChildCameraPropertyAttribute : PropertyAttribute {} From 84bd6c4002e2ff2d739e8135245224d7791c6ea9 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 12:37:33 -0400 Subject: [PATCH 3/9] layout tweaking for inspectors --- ...nemachineSplineDollyLookAtTargetsEditor.cs | 13 ++++++-- .../Editors/CinemachineSplineRollEditor.cs | 12 +++++-- .../Utility/CinemachineSceneToolHelpers.cs | 33 +++++++++++++++++++ .../Editor/Utility/InspectorUtility.cs | 8 ++--- .../Utility/SplineDataInspectorUtility.cs | 1 + 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs index d2a097255..76abc0fcb 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs @@ -82,8 +82,13 @@ public override VisualElement CreateInspectorGUI() "This component requires a CinemachineSplineDolly component referencing a nonempty Spline", HelpBoxMessageType.Warning); ux.Add(invalidHelp); - var toolButton = ux.AddChild(new Button(() => ToolManager.SetActiveTool(typeof(LookAtDataOnSplineTool))) - { text = "Edit Targets in Scene View" }); + + var tooltip = "Use the Scene View tool to Edit the LookAt targets on the spline"; + var buttonRow = ux.AddChild(new InspectorUtility.LabeledRow("Edit in Scene View", tooltip)); + var toolButton = buttonRow.Contents.AddChild( + CinemachineSceneToolHelpers.CreateSceneToolActivationButtonForInspector( + typeof(LookAtDataOnSplineTool), LookAtDataOnSplineTool.IconPath, tooltip)); + ux.TrackAnyUserActivity(() => { var haveSpline = splineData != null && splineData.GetGetSplineAndDolly(out _, out _); @@ -309,11 +314,13 @@ class LookAtDataOnSplineTool : EditorTool public static Action s_OnDataIndexDragged; public static Action s_OnDataLookAtDragged; + public static string IconPath => $"{CinemachineSceneToolHelpers.IconPath}/CmSplineLookAtTargetsTool@256.png"; + void OnEnable() { m_IconContent = new () { - image = AssetDatabase.LoadAssetAtPath($"{CinemachineSceneToolHelpers.IconPath}/CmSplineLookAtTargetsTool@256.png"), + image = AssetDatabase.LoadAssetAtPath(IconPath), tooltip = "Assign LookAt targets to positions on the spline." }; } diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs index 523f580d1..e31cdb089 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs @@ -21,9 +21,13 @@ public override VisualElement CreateInspectorGUI() "This component should be associated with a non-empty spline", HelpBoxMessageType.Warning); ux.Add(invalidHelp); - var toolButton = ux.AddChild(new Button(() => ToolManager.SetActiveTool(typeof(SplineRollTool))) - { text = "Edit Data Points in Scene View" }); + var tooltip = "Use the Scene View tool to adjust the roll data points"; + var buttonRow = ux.AddChild(new InspectorUtility.LabeledRow("Edit in Scene View", tooltip)); + var toolButton = buttonRow.Contents.AddChild( + CinemachineSceneToolHelpers.CreateSceneToolActivationButtonForInspector( + typeof(SplineRollTool), SplineRollTool.IconPath, tooltip)); + ux.TrackAnyUserActivity(() => { var haveSpline = splineData != null && splineData.SplineContainer != null; @@ -244,11 +248,13 @@ sealed class SplineRollTool : EditorTool public static Action s_OnDataIndexDragged; public static Action s_OnDataLookAtDragged; + public static string IconPath => $"{CinemachineSceneToolHelpers.IconPath}/CmSplineRollTool@256.png"; + void OnEnable() { m_IconContent = new GUIContent { - image = AssetDatabase.LoadAssetAtPath($"{CinemachineSceneToolHelpers.IconPath}/CmSplineRollTool@256.png"), + image = AssetDatabase.LoadAssetAtPath(IconPath), tooltip = "Adjust the roll data points along the spline" }; } diff --git a/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs b/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs index 7d99ad2fe..8f913674f 100644 --- a/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs +++ b/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs @@ -1,6 +1,8 @@ using System; using UnityEditor; +using UnityEditor.EditorTools; using UnityEngine; +using UnityEngine.UIElements; namespace Unity.Cinemachine.Editor { @@ -25,6 +27,37 @@ static class CinemachineSceneToolHelpers static string ResourcePath => $"{CinemachineCore.kPackageRoot}/Editor/EditorResources"; public static string IconPath => $"{ResourcePath}/Icons/{SkinSuffix}"; + static Color s_NormalBkgColor = Color.black; + + /// Create a button for the inspector that activates a scene tool + public static VisualElement CreateSceneToolActivationButtonForInspector(Type toolType, string iconPath, string tooltip) + { + var toolButton = new Button(() => ToolManager.SetActiveTool(toolType)) + { + tooltip = tooltip, + style = + { + flexGrow = 0, + flexBasis = 2 * InspectorUtility.SingleLineHeight, + height = InspectorUtility.SingleLineHeight + 3, + }, + }; + toolButton.Add(new Image { image = AssetDatabase.LoadAssetAtPath(iconPath) }); + + // Capture "normal" colors + if (s_NormalBkgColor == Color.black) + toolButton.OnInitialGeometry(() => s_NormalBkgColor = toolButton.resolvedStyle.backgroundColor); + + toolButton.ContinuousUpdate(() => + { + if (ToolManager.activeToolType == toolType) + toolButton.style.backgroundColor = Color.Lerp(s_NormalBkgColor, new Color(0.1f, 0.3f, 0.6f, 1), 0.5f); + else + toolButton.style.backgroundColor = s_NormalBkgColor; + }); + return toolButton; + } + public static float SliderHandleDelta(Vector3 newPos, Vector3 oldPos, Vector3 forward) { var delta = newPos - oldPos; diff --git a/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs b/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs index ecb4a69c2..5a89d3bc7 100644 --- a/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs +++ b/com.unity.cinemachine/Editor/Utility/InspectorUtility.cs @@ -360,9 +360,9 @@ public static Button MiniPopupButton(string tooltip = null, ContextualMenuManipu var button = new Button { tooltip = tooltip, style = { flexGrow = 0, - flexBasis = SingleLineHeight, + flexBasis = SingleLineHeight + 3, backgroundImage = (StyleBackground)EditorGUIUtility.IconContent("_Popup").image, - width = SingleLineHeight, height = SingleLineHeight, + width = SingleLineHeight, height = SingleLineHeight + 3, alignSelf = Align.Center, paddingRight = 0, marginRight = 0 }}; @@ -383,9 +383,9 @@ public static Button MiniDropdownButton(string tooltip = null, ContextualMenuMan var button = new Button { tooltip = tooltip, style = { flexGrow = 0, - flexBasis = SingleLineHeight, + flexBasis = SingleLineHeight + 3, backgroundImage = (StyleBackground)EditorGUIUtility.IconContent("dropdown").image, - width = SingleLineHeight, height = SingleLineHeight, + width = SingleLineHeight, height = SingleLineHeight + 3, alignSelf = Align.Center, paddingRight = 0, marginRight = 0 }}; diff --git a/com.unity.cinemachine/Editor/Utility/SplineDataInspectorUtility.cs b/com.unity.cinemachine/Editor/Utility/SplineDataInspectorUtility.cs index bb9719add..66ab45301 100644 --- a/com.unity.cinemachine/Editor/Utility/SplineDataInspectorUtility.cs +++ b/com.unity.cinemachine/Editor/Utility/SplineDataInspectorUtility.cs @@ -36,6 +36,7 @@ public static VisualElement CreatePathUnitField(SerializedProperty splineDataPro }); enumField.TrackPropertyValue(indexUnitProp, (p) => enumField.value = (PathIndexUnit)indexUnitProp.enumValueIndex); enumField.TrackAnyUserActivity(() => enumField.SetEnabled(getSpline?.Invoke() != null)); + enumField.AddToClassList(InspectorUtility.AlignFieldClassName); return enumField; } From 91fbeeba1106bc36082bab508e59da436041f9b0 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 12:54:00 -0400 Subject: [PATCH 4/9] update doc --- .../CinemachineSplineDollyLookAtTargets.md | 4 +++- .../Documentation~/CinemachineSplineRoll.md | 12 +++++++++++- .../images/CinemachineSplineRollInspector.png | Bin 0 -> 22636 bytes .../SplineDollyLookAtTargetsInspector.png | Bin 0 -> 43353 bytes 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 com.unity.cinemachine/Documentation~/images/CinemachineSplineRollInspector.png create mode 100644 com.unity.cinemachine/Documentation~/images/SplineDollyLookAtTargetsInspector.png diff --git a/com.unity.cinemachine/Documentation~/CinemachineSplineDollyLookAtTargets.md b/com.unity.cinemachine/Documentation~/CinemachineSplineDollyLookAtTargets.md index de91b0eae..6f1990222 100644 --- a/com.unity.cinemachine/Documentation~/CinemachineSplineDollyLookAtTargets.md +++ b/com.unity.cinemachine/Documentation~/CinemachineSplineDollyLookAtTargets.md @@ -1,5 +1,7 @@ # Cinemachine Spline Dolly LookAt Targets +![Spline Dolly LookAt Targets Inspector](images/SplineDollyLookAtTargetsInspector.png) + This CinemachineCamera __Rotation Control__ behaviour lets you assign LookAt targets to points on a spline, so that as the camera arrives at the position on the spline, it looks at the specified place. It's useful for creating curated dolly shots with specified aim targets along the way. This behaviour eliminates the need to provide rotation animations for the camera that are synchronized with the spline position animation. LookAt points are anchored to specific spline positions, and because they specify a LookAt target point, the appropriate rotation angles get computed dynamically. As a result, the rotation animation is more robust and less likely to break if the spline is modified. @@ -18,7 +20,7 @@ When the LookAtDataOnSpline is selected in the inspector, a Scene View tool is p | Property | Field | Description | | --- | --- | --- | | __Index Unit__ | | Defines how to interpret the _Index_ field for each data point. _Knot_ is the recommended value because it remains robust if the spline points change. | -| __Targets__ | | The list of LookAt target on the spline. As the camera approaches these positions on the spline, the camera will look at the corresponding targets. | +| __Data Points__ | | The list of LookAt target on the spline. As the camera approaches these positions on the spline, the camera will look at the corresponding targets. | | | _Index_ | The position on the Spline where the camera should look at the supplied point. The value is interpreted according to the _Index Unit_ setting. | | | _Look At_ | The target object to look at. It may be None, in which case the Offset will specify a point in world space. | | | _Offset_ | The offset (in local coords) from the LookAt target's origin. If LookAt target is None, this will specify a world-space point. | diff --git a/com.unity.cinemachine/Documentation~/CinemachineSplineRoll.md b/com.unity.cinemachine/Documentation~/CinemachineSplineRoll.md index cd3543c03..7b599f12d 100644 --- a/com.unity.cinemachine/Documentation~/CinemachineSplineRoll.md +++ b/com.unity.cinemachine/Documentation~/CinemachineSplineRoll.md @@ -1,6 +1,16 @@ # Cinemachine Spline Roll +![Spline Roll Inspector](images/CinemachineSplineRollInspector.png) + This behavior adds Roll to a Spline. Roll is the rotation about the spline's tangent. Add data points to set the roll at specific points along the spline. Roll will be interpolated between those points. This behavior will also draw a railroad-track Gizmo in the Scene view, to help visualize the roll. -If you add this behavior to the Spline itself, then any [Cm Camera](CinemachineCamera.md) or [Cinemachine Spline Cart](CinemachineSplineCart.md) that follows the path will respect the roll. If instead you add this behavior to the CinemachineCamera itself, then the roll will be visible only to that CinemachineCamera. +If you add this behavior to the Spline itself, then any [Cinemachine Camera](CinemachineCamera.md) or [Cinemachine Spline Cart](CinemachineSplineCart.md) that follows the path will respect the roll. If instead you add this behavior to the CinemachineCamera itself, then the roll will be visible only to that CinemachineCamera. + +### Properties +| Property | Field | Description | +| --- | --- | --- | +| __Index Unit__ | | Defines how to interpret the _Index_ field for each data point. _Knot_ is the recommended value because it remains robust if the spline points change. | +| __Data Points__ | | The list of Roll points on the spline. At these postions on the spline, it will take on the specified roll value. | +| | _Index_ | The position on the Spline where it should assume the specified roll. The value is interpreted according to the _Index Unit_ setting. | +| | _Roll_ | The roll value for the spline. This is specified in degrees, and the axis of rotation is the spline tangent at that point. | diff --git a/com.unity.cinemachine/Documentation~/images/CinemachineSplineRollInspector.png b/com.unity.cinemachine/Documentation~/images/CinemachineSplineRollInspector.png new file mode 100644 index 0000000000000000000000000000000000000000..4288510f8cc1ea0742a405c341a1ac92e17dc069 GIT binary patch literal 22636 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 literal 0 HcmV?d00001 diff --git a/com.unity.cinemachine/Documentation~/images/SplineDollyLookAtTargetsInspector.png b/com.unity.cinemachine/Documentation~/images/SplineDollyLookAtTargetsInspector.png new file mode 100644 index 0000000000000000000000000000000000000000..dd6dbad8f691f1b7c962f33b09b7162063648ee7 GIT binary patch literal 43353 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�uIz#~+lL7g}9lj=NZ#3|qhn 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 Date: Fri, 5 Jul 2024 13:13:59 -0400 Subject: [PATCH 5/9] update samples --- .../Model/CameronSimpleController.controller | 14 ++-- .../Scripts/SimplePlayerAnimator.cs | 75 +++++++++++++------ 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/com.unity.cinemachine/Samples~/Shared Assets/Cameron/Model/CameronSimpleController.controller b/com.unity.cinemachine/Samples~/Shared Assets/Cameron/Model/CameronSimpleController.controller index de449873a..e74f1a65d 100644 --- a/com.unity.cinemachine/Samples~/Shared Assets/Cameron/Model/CameronSimpleController.controller +++ b/com.unity.cinemachine/Samples~/Shared Assets/Cameron/Model/CameronSimpleController.controller @@ -227,9 +227,9 @@ AnimatorStateTransition: m_Mute: 0 m_IsExit: 0 serializedVersion: 3 - m_TransitionDuration: 0.06615287 + m_TransitionDuration: 0.09497708 m_TransitionOffset: 0 - m_ExitTime: 0.58234453 + m_ExitTime: 0.5823446 m_HasExitTime: 0 m_HasFixedDuration: 1 m_InterruptionSource: 0 @@ -615,9 +615,9 @@ AnimatorStateTransition: m_Mute: 0 m_IsExit: 0 serializedVersion: 3 - m_TransitionDuration: 0.25 - m_TransitionOffset: 0 - m_ExitTime: 0.9 + m_TransitionDuration: 0.13899994 + m_TransitionOffset: 0.17746145 + m_ExitTime: 0.9101155 m_HasExitTime: 0 m_HasFixedDuration: 1 m_InterruptionSource: 0 @@ -646,9 +646,9 @@ AnimatorStateTransition: m_Mute: 0 m_IsExit: 0 serializedVersion: 3 - m_TransitionDuration: 0.25 + m_TransitionDuration: 0.12810051 m_TransitionOffset: 0 - m_ExitTime: 0.5614035 + m_ExitTime: 0.56140345 m_HasExitTime: 0 m_HasFixedDuration: 1 m_InterruptionSource: 0 diff --git a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs index 25789e0cc..3faabcc88 100644 --- a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs +++ b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs @@ -19,7 +19,6 @@ namespace Unity.Cinemachine.Samples /// and expects to be driven by the SimplePlayerControllerBase using the STartJump, EndJump, /// and PostUpdate callbacks. /// - [RequireComponent(typeof(Animator))] public class SimplePlayerAnimator : MonoBehaviour { [Tooltip("Tune this to the animation in the model: feet should not slide when walking at this speed")] @@ -34,23 +33,39 @@ public class SimplePlayerAnimator : MonoBehaviour [Tooltip("Scale factor for the overall speed of the jump animation")] public float JumpAnimationScale = 0.65f; - Animator m_Animator; SimplePlayerControllerBase m_Controller; Vector3 m_PreviousPosition; // used if m_Controller == null or disabled - bool m_WasWalking; - bool m_WasRunning; + bool m_IsWalking; + bool m_IsRunning; + bool m_IsJumping; + bool m_LandTriggered; + bool m_JumpTriggered; const float k_IdleThreshold = 0.2f; + public enum States { Idle, Walk, Run, Jump, RunJump } + + /// Current state of the player + public States State + { + get + { + if (m_IsJumping) + return m_IsRunning ? States.RunJump : States.Jump; + if (m_IsRunning) + return States.Run; + return m_IsWalking ? States.Walk : States.Idle; + } + } + void Start() { m_PreviousPosition = transform.position; - TryGetComponent(out m_Animator); m_Controller = GetComponentInParent(); if (m_Controller != null) { // Install our callbacks to handle jump and animation based on velocity - m_Controller.StartJump += () => OnJump(true); - m_Controller.EndJump += () => OnJump(false); + m_Controller.StartJump += () => m_JumpTriggered = true; + m_Controller.EndJump += () => m_LandTriggered = true; m_Controller.PostUpdate += (vel, jumpAnimationScale) => UpdateAnimation(vel, jumpAnimationScale); } } @@ -73,13 +88,6 @@ virtual protected void LateUpdate() } } - /// - /// Called by the SimplePlayerControllerBase when the player starts or ends a jump. - /// Override this to interact appropriately with your animation controller. - /// - /// True when jump starts, false when it ends. - virtual protected void OnJump(bool jumping) => m_Animator.SetTrigger(jumping ? "Jump" : "Land"); - /// /// Update the animation based on the player's velocity. /// Override this to interact appropriately with your animation controller. @@ -89,14 +97,20 @@ virtual protected void LateUpdate() /// It can be used to slow down the jump animation for longer jumps. virtual protected void UpdateAnimation(Vector3 vel, float jumpAnimationScale) { + if (!TryGetComponent(out Animator animator)) + { + Debug.LogError("SimplePlayerAnimator: An Animator component is required"); + return; + } + vel.y = 0; // we don't consider vertical movement var speed = vel.magnitude; // Hysteresis reduction - bool isRunning = speed > NormalWalkSpeed * 2 + (m_WasRunning ? -0.15f : 0.15f); - bool isWalking = !isRunning && speed > k_IdleThreshold + (m_WasWalking ? -0.05f : 0.05f); - m_WasWalking = isWalking; - m_WasRunning = isRunning; + bool isRunning = speed > NormalWalkSpeed * 2 + (m_IsRunning ? -0.15f : 0.15f); + bool isWalking = !isRunning && speed > k_IdleThreshold + (m_IsWalking ? -0.05f : 0.05f); + m_IsWalking = isWalking; + m_IsRunning = isRunning; // Set the normalized direction of motion and scale the animation speed to match motion speed var dir = speed > k_IdleThreshold ? vel / speed : Vector3.zero; @@ -109,12 +123,25 @@ virtual protected void UpdateAnimation(Vector3 vel, float jumpAnimationScale) ? speed / NormalSprintSpeed : Mathf.Min(MaxSprintScale, 1 + (speed - NormalSprintSpeed) / (3 * NormalSprintSpeed)); - m_Animator.SetFloat("DirX", dir.x); - m_Animator.SetFloat("DirZ", dir.z); - m_Animator.SetFloat("MotionScale", motionScale); - m_Animator.SetBool("Walking", isWalking); - m_Animator.SetBool("Running", isRunning); - m_Animator.SetFloat("JumpScale", JumpAnimationScale * jumpAnimationScale); + animator.SetFloat("DirX", dir.x); + animator.SetFloat("DirZ", dir.z); + animator.SetFloat("MotionScale", motionScale); + animator.SetBool("Walking", isWalking); + animator.SetBool("Running", isRunning); + animator.SetFloat("JumpScale", JumpAnimationScale * jumpAnimationScale); + + if (m_JumpTriggered) + { + animator.SetTrigger("Jump"); + m_JumpTriggered = false; + m_IsJumping = true; + } + if (m_LandTriggered) + { + animator.SetTrigger("Land"); + m_LandTriggered = false; + m_IsJumping = false; + } } } } From 208bc0364e4ca40319ce6bdb9808e62c0fe122d5 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 15:12:34 -0400 Subject: [PATCH 6/9] Update package.json --- com.unity.cinemachine/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.cinemachine/package.json b/com.unity.cinemachine/package.json index e05deae4a..3e65a92dd 100644 --- a/com.unity.cinemachine/package.json +++ b/com.unity.cinemachine/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.cinemachine", "displayName": "Cinemachine", - "version": "3.1.1", + "version": "3.1.2", "unity": "2022.3", "description": "Smart camera tools for passionate creators. \n\nCinemachine 3 is a newer and better version of Cinemachine, but upgrading an existing project from 2.X will likely require some effort. If you're considering upgrading an older project, please see our upgrade guide in the user manual.", "keywords": [ From c1f66c4ff256ca61f688c981a08814009be7b983 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 16:00:58 -0400 Subject: [PATCH 7/9] Update SimplePlayerAnimator.cs --- .../Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs index 3faabcc88..b6d6f97b8 100644 --- a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs +++ b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs @@ -57,7 +57,7 @@ public States State } } - void Start() + protected virtual void Start() { m_PreviousPosition = transform.position; m_Controller = GetComponentInParent(); @@ -74,7 +74,7 @@ void Start() /// LateUpdate is used to avoid having to worry about script execution order: /// it can be assumed that the player has already been moved. /// - virtual protected void LateUpdate() + protected virtual void LateUpdate() { // In no-controller mode, we monitor the player's motion and deduce the appropriate animation. // We don't support jumping in this mode. @@ -95,7 +95,7 @@ virtual protected void LateUpdate() /// Player's velocity, in player-local coordinates. /// Scale factor to apply to the jump animation. /// It can be used to slow down the jump animation for longer jumps. - virtual protected void UpdateAnimation(Vector3 vel, float jumpAnimationScale) + protected virtual void UpdateAnimation(Vector3 vel, float jumpAnimationScale) { if (!TryGetComponent(out Animator animator)) { From e2d5de01b09222b94e934f81be508735de93ceb7 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Fri, 5 Jul 2024 16:43:36 -0400 Subject: [PATCH 8/9] Update SimplePlayerAnimator.cs --- .../Scripts/SimplePlayerAnimator.cs | 104 ++++++++++-------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs index b6d6f97b8..491b9a611 100644 --- a/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs +++ b/com.unity.cinemachine/Samples~/Shared Assets/Scripts/SimplePlayerAnimator.cs @@ -35,11 +35,20 @@ public class SimplePlayerAnimator : MonoBehaviour SimplePlayerControllerBase m_Controller; Vector3 m_PreviousPosition; // used if m_Controller == null or disabled - bool m_IsWalking; - bool m_IsRunning; - bool m_IsJumping; - bool m_LandTriggered; - bool m_JumpTriggered; + + protected struct AnimationParams + { + public bool IsWalking; + public bool IsRunning; + public bool IsJumping; + public bool LandTriggered; + public bool JumpTriggered; + public Vector3 Direction; // normalized direction of motion + public float MotionScale; // scale factor for the animation speed + public float JumpScale; // scale factor for the jump animation + } + AnimationParams m_AnimationParams; + const float k_IdleThreshold = 0.2f; public enum States { Idle, Walk, Run, Jump, RunJump } @@ -49,11 +58,11 @@ public States State { get { - if (m_IsJumping) - return m_IsRunning ? States.RunJump : States.Jump; - if (m_IsRunning) + if (m_AnimationParams.IsJumping) + return m_AnimationParams.IsRunning ? States.RunJump : States.Jump; + if (m_AnimationParams.IsRunning) return States.Run; - return m_IsWalking ? States.Walk : States.Idle; + return m_AnimationParams.IsWalking ? States.Walk : States.Idle; } } @@ -64,9 +73,9 @@ protected virtual void Start() if (m_Controller != null) { // Install our callbacks to handle jump and animation based on velocity - m_Controller.StartJump += () => m_JumpTriggered = true; - m_Controller.EndJump += () => m_LandTriggered = true; - m_Controller.PostUpdate += (vel, jumpAnimationScale) => UpdateAnimation(vel, jumpAnimationScale); + m_Controller.StartJump += () => m_AnimationParams.JumpTriggered = true; + m_Controller.EndJump += () => m_AnimationParams.LandTriggered = true; + m_Controller.PostUpdate += (vel, jumpAnimationScale) => UpdateAnimationState(vel, jumpAnimationScale); } } @@ -84,7 +93,7 @@ protected virtual void LateUpdate() var pos = transform.position; var vel = Quaternion.Inverse(transform.rotation) * (pos - m_PreviousPosition) / Time.deltaTime; m_PreviousPosition = pos; - UpdateAnimation(vel, 1); + UpdateAnimationState(vel, 1); } } @@ -95,53 +104,62 @@ protected virtual void LateUpdate() /// Player's velocity, in player-local coordinates. /// Scale factor to apply to the jump animation. /// It can be used to slow down the jump animation for longer jumps. - protected virtual void UpdateAnimation(Vector3 vel, float jumpAnimationScale) + void UpdateAnimationState(Vector3 vel, float jumpAnimationScale) { - if (!TryGetComponent(out Animator animator)) - { - Debug.LogError("SimplePlayerAnimator: An Animator component is required"); - return; - } - vel.y = 0; // we don't consider vertical movement var speed = vel.magnitude; // Hysteresis reduction - bool isRunning = speed > NormalWalkSpeed * 2 + (m_IsRunning ? -0.15f : 0.15f); - bool isWalking = !isRunning && speed > k_IdleThreshold + (m_IsWalking ? -0.05f : 0.05f); - m_IsWalking = isWalking; - m_IsRunning = isRunning; + bool isRunning = speed > NormalWalkSpeed * 2 + (m_AnimationParams.IsRunning ? -0.15f : 0.15f); + bool isWalking = !isRunning && speed > k_IdleThreshold + (m_AnimationParams.IsWalking ? -0.05f : 0.05f); + m_AnimationParams.IsWalking = isWalking; + m_AnimationParams.IsRunning = isRunning; // Set the normalized direction of motion and scale the animation speed to match motion speed - var dir = speed > k_IdleThreshold ? vel / speed : Vector3.zero; - var motionScale = isWalking ? speed / NormalWalkSpeed : 1; + m_AnimationParams.Direction = speed > k_IdleThreshold ? vel / speed : Vector3.zero; + m_AnimationParams.MotionScale = isWalking ? speed / NormalWalkSpeed : 1; + m_AnimationParams.JumpScale = JumpAnimationScale * jumpAnimationScale; // We scale the sprint animation speed to loosely match the actual speed, but we cheat // at the high end to avoid making the animation look ridiculous if (isRunning) - motionScale = (speed < NormalSprintSpeed) + m_AnimationParams.MotionScale = (speed < NormalSprintSpeed) ? speed / NormalSprintSpeed : Mathf.Min(MaxSprintScale, 1 + (speed - NormalSprintSpeed) / (3 * NormalSprintSpeed)); - animator.SetFloat("DirX", dir.x); - animator.SetFloat("DirZ", dir.z); - animator.SetFloat("MotionScale", motionScale); - animator.SetBool("Walking", isWalking); - animator.SetBool("Running", isRunning); - animator.SetFloat("JumpScale", JumpAnimationScale * jumpAnimationScale); - - if (m_JumpTriggered) + UpdateAnimation(m_AnimationParams); + + if (m_AnimationParams.JumpTriggered) + m_AnimationParams.IsJumping = true; + if (m_AnimationParams.LandTriggered) + m_AnimationParams.IsJumping = false; + + m_AnimationParams.JumpTriggered = false; + m_AnimationParams.LandTriggered = false; + } + + /// + /// Update the animation based on the player's state. + /// Override this to interact appropriately with your animation controller. + /// + protected virtual void UpdateAnimation(AnimationParams animationParams) + { + if (!TryGetComponent(out Animator animator)) { - animator.SetTrigger("Jump"); - m_JumpTriggered = false; - m_IsJumping = true; + Debug.LogError("SimplePlayerAnimator: An Animator component is required"); + return; } - if (m_LandTriggered) - { + animator.SetFloat("DirX", animationParams.Direction.x); + animator.SetFloat("DirZ", animationParams.Direction.z); + animator.SetFloat("MotionScale", animationParams.MotionScale); + animator.SetBool("Walking", animationParams.IsWalking); + animator.SetBool("Running", animationParams.IsRunning); + animator.SetFloat("JumpScale", animationParams.JumpScale); + + if (m_AnimationParams.JumpTriggered) + animator.SetTrigger("Jump"); + if (m_AnimationParams.LandTriggered) animator.SetTrigger("Land"); - m_LandTriggered = false; - m_IsJumping = false; - } } } } From e5a39a964b501a44f78e2bc93ee27409f36f5590 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Wed, 10 Jul 2024 07:24:28 -0400 Subject: [PATCH 9/9] Make sure object is selected when activating tool --- .../CinemachineSplineDollyLookAtTargetsEditor.cs | 2 +- .../Editor/Editors/CinemachineSplineRollEditor.cs | 2 +- .../Editor/Utility/CinemachineSceneToolHelpers.cs | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs index 76abc0fcb..451e78532 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSplineDollyLookAtTargetsEditor.cs @@ -87,7 +87,7 @@ public override VisualElement CreateInspectorGUI() var buttonRow = ux.AddChild(new InspectorUtility.LabeledRow("Edit in Scene View", tooltip)); var toolButton = buttonRow.Contents.AddChild( CinemachineSceneToolHelpers.CreateSceneToolActivationButtonForInspector( - typeof(LookAtDataOnSplineTool), LookAtDataOnSplineTool.IconPath, tooltip)); + typeof(LookAtDataOnSplineTool), target, LookAtDataOnSplineTool.IconPath, tooltip)); ux.TrackAnyUserActivity(() => { diff --git a/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs b/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs index e31cdb089..96ce088ff 100644 --- a/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs +++ b/com.unity.cinemachine/Editor/Editors/CinemachineSplineRollEditor.cs @@ -26,7 +26,7 @@ public override VisualElement CreateInspectorGUI() var buttonRow = ux.AddChild(new InspectorUtility.LabeledRow("Edit in Scene View", tooltip)); var toolButton = buttonRow.Contents.AddChild( CinemachineSceneToolHelpers.CreateSceneToolActivationButtonForInspector( - typeof(SplineRollTool), SplineRollTool.IconPath, tooltip)); + typeof(SplineRollTool), target, SplineRollTool.IconPath, tooltip)); ux.TrackAnyUserActivity(() => { diff --git a/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs b/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs index 8f913674f..d3d98bf42 100644 --- a/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs +++ b/com.unity.cinemachine/Editor/Utility/CinemachineSceneToolHelpers.cs @@ -30,9 +30,15 @@ static class CinemachineSceneToolHelpers static Color s_NormalBkgColor = Color.black; /// Create a button for the inspector that activates a scene tool - public static VisualElement CreateSceneToolActivationButtonForInspector(Type toolType, string iconPath, string tooltip) + public static VisualElement CreateSceneToolActivationButtonForInspector( + Type toolType, UnityEngine.Object target, string iconPath, string tooltip) { - var toolButton = new Button(() => ToolManager.SetActiveTool(toolType)) + var toolButton = new Button(() => + { + if (Selection.activeObject != target) + Selection.activeObject = target; + EditorApplication.delayCall += () => ToolManager.SetActiveTool(toolType); + }) { tooltip = tooltip, style =