diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs index 12a8178c4f..a295a8d200 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporter.bindings.cs @@ -15,7 +15,7 @@ namespace UnityEditor.U2D { // SpriteAtlas Importer lets you modify [[SpriteAtlas]] - [HelpURL("https://docs.unity3d.com/6000.2/Documentation/Manual/sprite/atlas/v2/sprite-atlas-v2.html")] + [HelpURL("sprite/atlas/sprite-atlas-reference")] [NativeHeader("Editor/Src/2D/SpriteAtlas/SpriteAtlasImporter.h")] public sealed partial class SpriteAtlasImporter : AssetImporter { diff --git a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs index 34bfc9c41c..878bc90c00 100644 --- a/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs +++ b/Editor/Mono/2D/SpriteAtlas/SpriteAtlasImporterInspector.cs @@ -317,6 +317,8 @@ private SerializedObject GetSerializedAssetObject() public override void OnEnable() { base.OnEnable(); + if (!AreImporterTargetsValid()) // asset gone: base already logged and bailed + return; m_FilterMode = serializedObject.FindProperty("m_TextureSettings.filterMode"); m_AnisoLevel = serializedObject.FindProperty("m_TextureSettings.anisoLevel"); diff --git a/Editor/Mono/AI/EditorAIAssistantAnalytics.cs b/Editor/Mono/AI/EditorAIAssistantAnalytics.cs new file mode 100644 index 0000000000..efe8c15dff --- /dev/null +++ b/Editor/Mono/AI/EditorAIAssistantAnalytics.cs @@ -0,0 +1,58 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine.Analytics; + +namespace UnityEditor.Toolbars; + +enum UITriggerLocalEventSubType +{ + AIDropdownOpened, + AIInstallAccepted, +} + +internal static class EditorAIAssistantAnalytics +{ + const string k_EventName = "AIAssistantUITriggerLocalEvent"; + const string k_VendorKey = "unity.ai.assistant"; + + [Serializable] + class UITriggerLocalEventData : IAnalytic.IData + { + public UITriggerLocalEventData(UITriggerLocalEventSubType subType) => SubType = subType.ToString(); + + public string SubType; + } + + [AnalyticInfo(eventName: k_EventName, vendorKey: k_VendorKey)] + class UITriggerLocalEvent : IAnalytic + { + readonly UITriggerLocalEventData m_Data; + + public UITriggerLocalEvent(UITriggerLocalEventData data) => m_Data = data; + + public bool TryGatherData(out IAnalytic.IData data, out Exception error) + { + error = null; + data = m_Data; + return true; + } + } + + static void ReportUITriggerLocalEvent(UITriggerLocalEventData data) + { + EditorAnalytics.SendAnalytic(new UITriggerLocalEvent(data)); + } + + internal static void ReportAIDropdownOpenedEvent() + { + ReportUITriggerLocalEvent(new UITriggerLocalEventData(UITriggerLocalEventSubType.AIDropdownOpened)); + } + + internal static void ReportAIInstallAcceptedEvent() + { + ReportUITriggerLocalEvent(new UITriggerLocalEventData(UITriggerLocalEventSubType.AIInstallAccepted)); + } +} diff --git a/Editor/Mono/Animation/AnimationMode.bindings.cs b/Editor/Mono/Animation/AnimationMode.bindings.cs index 5ef3cafb54..189732c450 100644 --- a/Editor/Mono/Animation/AnimationMode.bindings.cs +++ b/Editor/Mono/Animation/AnimationMode.bindings.cs @@ -41,6 +41,8 @@ public class AnimationMode static internal event Action onAnimationRecordingStop; static internal event Action onAnimationPlaybackStart; static internal event Action onAnimationPlaybackStop; + static internal event Action onAnimationSampleEnd; + static internal event Action onAnimationModeStop; static private PrefColor s_AnimatedPropertyColor = new PrefColor("Animation/Property Animated", 0.82f, 0.97f, 1.00f, 1.00f, 0.54f, 0.85f, 1.00f, 1.00f); static private PrefColor s_RecordedPropertyColor = new PrefColor("Animation/Property Recorded", 1.00f, 0.60f, 0.60f, 1.00f, 1.00f, 0.50f, 0.50f, 1.00f); @@ -52,7 +54,6 @@ public class AnimationMode static private AnimationModeDriver s_DummyDriver; - static internal AnimationModeDriver GetDriver() => Internal_GetDriver() as AnimationModeDriver; static private AnimationModeDriver DummyDriver() { if (s_DummyDriver == null) @@ -71,12 +72,16 @@ static private AnimationModeDriver DummyDriver() public static void StopAnimationMode() { Internal_StopAnimationMode(DummyDriver()); + + onAnimationModeStop?.Invoke(); } // Stops animation mode, as used by the animation editor. public static void StopAnimationMode(AnimationModeDriver driver) { Internal_StopAnimationMode(driver); + + onAnimationModeStop?.Invoke(); } // Returns true if the editor is currently in animation mode. @@ -148,6 +153,18 @@ internal static void StartCandidateRecording(AnimationModeDriver driver) Internal_StartCandidateRecording(driver); } + public static void BeginSampling() + { + Internal_BeginSampling(); + } + + public static void EndSampling() + { + Internal_EndSampling(); + + onAnimationSampleEnd?.Invoke(); + } + [NativeThrows] extern internal static void AddCandidate(EditorCurveBinding binding, PropertyModification modification, bool keepPrefabOverride); @@ -158,12 +175,6 @@ internal static void StartCandidateRecording(AnimationModeDriver driver) extern internal static bool IsRecordingCandidates(); - [NativeThrows] - extern public static void BeginSampling(); - - [NativeThrows] - extern public static void EndSampling(); - [NativeThrows] extern public static void SampleAnimationClip([NotNull] GameObject gameObject, [NotNull] AnimationClip clip, float time); @@ -206,6 +217,12 @@ internal static void StartCandidateRecording(AnimationModeDriver driver) // Return editor curve bindings for animator hierarhcy that need to be snapshot for animation mode. extern internal static EditorCurveBinding[] GetAnimatorBindings([NotNull] GameObject root); + [NativeThrows] + extern private static void Internal_BeginSampling(); + + [NativeThrows] + extern private static void Internal_EndSampling(); + extern private static void Internal_StartAnimationMode(Object driver); extern private static void Internal_StopAnimationMode(Object driver); @@ -216,7 +233,5 @@ internal static void StartCandidateRecording(AnimationModeDriver driver) [NativeThrows] extern private static void Internal_StartCandidateRecording(Object driver); - - extern internal static Object Internal_GetDriver(); } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs b/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs index b808d78f2b..35519065a0 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimEditor.cs @@ -167,8 +167,8 @@ public void OnAnimEditorGUI(EditorWindow parent, Rect position) GUILayout.BeginVertical(); // First row of controls - GUILayout.BeginHorizontal(AnimationWindowStyles.animPlayToolBar); - PlayControlsOnGUI(); + Rect playControlsRect = EditorGUILayout.BeginHorizontal(AnimationWindowStyles.animPlayToolBar); + PlayControlsOnGUI(playControlsRect); GUILayout.EndHorizontal(); // Second row of controls @@ -522,6 +522,9 @@ private void ApplyRevertOnGUI() internal void HierarchyOnGUI(Rect hierarchyLayoutRect) { + if (m_State.disabled) + return; + if (!m_State.showReadOnly && m_State.selection.isReadOnly) { Vector2 labelSize = GUI.skin.label.CalcSize(AnimationWindowStyles.readOnlyPropertiesLabel); @@ -546,8 +549,7 @@ internal void HierarchyOnGUI(Rect hierarchyLayoutRect) return; } - if (!m_State.disabled) - m_Hierarchy.OnGUI(hierarchyLayoutRect); + m_Hierarchy.OnGUI(hierarchyLayoutRect); } private void FrameRateInputFieldOnGUI() @@ -777,8 +779,14 @@ private void AddKeyframeButtonOnGUI() } } - private void PlayControlsOnGUI() + private void PlayControlsOnGUI(Rect playControlsRect) { + // Remove keyfocus when clicking within control to ensure play control shortcuts are received (UUM-113412) + if (Event.current.type == EventType.MouseDown && playControlsRect.Contains(Event.current.mousePosition)) + { + GUIUtility.keyboardControl = 0; + } + using (new EditorGUI.DisabledScope(!m_State.canPreview)) { PreviewButtonOnGUI(); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs index 06f534b2ff..f2a3c4f080 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindow.cs @@ -259,6 +259,7 @@ void OnEnable() Undo.undoRedoEvent += UndoRedoPerformed; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + AssemblyReloadEvents.beforeAssemblyReload += PurgeSelection; } void OnDisable() @@ -268,6 +269,7 @@ void OnDisable() Undo.undoRedoEvent -= UndoRedoPerformed; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + AssemblyReloadEvents.beforeAssemblyReload -= PurgeSelection; } void OnDestroy() @@ -326,18 +328,27 @@ void OnSelectionChangeInternal(bool fromCallback) { if (selection == newSelection) OnSelectionUpdated(); - else if (fromCallback && DisplayUnsavedChangesDialogIfNecessary()) - selection = newSelection; - else + else if (fromCallback) { - selection = newSelection; - var lastSelectedObject = EditorUtility.EntityIdToObject(m_LastSelectedObjectID); - if (lastSelectedObject != null) + // Handle unsaved changes. + if (DisplayUnsavedChangesDialogIfNecessary()) + selection = newSelection; + // Fallback to last selected object if changes were canceled + else { - activeObject = lastSelectedObject; - Selection.activeObject = activeObject; + selection = newSelection; + var lastSelectedObject = EditorUtility.EntityIdToObject(m_LastSelectedObjectID); + if (lastSelectedObject != null) + { + activeObject = lastSelectedObject; + Selection.activeObject = activeObject; + } } } + else + { + selection = newSelection; + } m_LastSelectedObjectID = activeObject != null ? activeObject.GetInstanceID() : 0; selectionChanged = true; @@ -351,18 +362,27 @@ void OnSelectionChangeInternal(bool fromCallback) var fallbackSelection = GetFallbackSelection(activeObject); if (selection == fallbackSelection) OnSelectionUpdated(); - else if (fromCallback && DisplayUnsavedChangesDialogIfNecessary()) - selection = fallbackSelection; - else + else if (fromCallback) { - selection = fallbackSelection; - var lastSelectedObject = EditorUtility.EntityIdToObject(m_LastSelectedObjectID); - if (lastSelectedObject != null) + // Handle unsaved changes. + if (DisplayUnsavedChangesDialogIfNecessary()) + selection = fallbackSelection; + // Fallback to last selected object if changes were canceled + else { - activeObject = lastSelectedObject; - Selection.activeObject = activeObject; + selection = fallbackSelection; + var lastSelectedObject = EditorUtility.EntityIdToObject(m_LastSelectedObjectID); + if (lastSelectedObject != null) + { + activeObject = lastSelectedObject; + Selection.activeObject = activeObject; + } } } + else + { + selection = fallbackSelection; + } m_LastSelectedObjectID = activeObject != null ? activeObject.GetInstanceID() : 0; } @@ -485,12 +505,17 @@ private void OnPlayModeStateChanged(PlayModeStateChange state) } else { - selection?.OnPlayModeStateChanged(state); + // Purge selection before domain reload to prevent stale GameObject references + PurgeSelection(); } } else { - selection?.OnPlayModeStateChanged(state); + // Purge selection before domain reload on ExitingPlayMode + if (state == PlayModeStateChange.ExitingPlayMode) + { + PurgeSelection(); + } if (state == PlayModeStateChange.EnteredEditMode) { @@ -500,6 +525,18 @@ private void OnPlayModeStateChanged(PlayModeStateChange state) } } + private void PurgeSelection() + { + // Clear selection to prevent stale GameObject references during domain reload. + // Note: Dispose() will also stop preview by disposing the controller. + // This matches the behavior during assembly reload (script compilation). + if (state != null) + { + state.linkedWithSequencer = false; + state.selection = new FallbackSelectionItem(); + } + } + public void AddItemsToMenu(GenericMenu menu) { m_LockTracker.AddItemsToMenu(menu, m_AnimEditor.stateDisabled); diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowClipPopup.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowClipPopup.cs index a9335a0615..82c5ddd10d 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowClipPopup.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowClipPopup.cs @@ -145,7 +145,6 @@ private IAnimationWindowClip DoClipPopup(IAnimationWindowClip clip, GUIStyle sty if (evt.button == 0 && position.Contains(evt.mousePosition)) { DisplayClipMenu(position, controlID, clip); - GUIUtility.keyboardControl = controlID; evt.Use(); } break; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs index 07e4076f2a..f1206973b6 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowCurve.cs @@ -32,11 +32,14 @@ class AnimationWindowCurve : private System.Type m_ValueType; + private bool m_IsPhantom; + public EditorCurveBinding binding { get { return m_Binding; } } - public bool isPPtrCurve { get { return m_Binding.isPPtrCurve; } } - public bool isDiscreteCurve { get { return m_Binding.isDiscreteCurve; } } + public bool isPPtrCurve => m_Binding.isPPtrCurve; + public bool isDiscreteCurve => m_Binding.isDiscreteCurve; + public bool isFloatCurve => !m_Binding.isDiscreteCurve && !m_Binding.isPPtrCurve; public bool isSerializeReferenceCurve { get {return m_Binding.isSerializeReferenceCurve;}} - public bool isPhantom { get { return m_Binding.isPhantom; } } + public bool isPhantom { get { return m_IsPhantom; } set { m_IsPhantom = value; } } public InheritanceState inheritanceState { get; set; } public string propertyName { get { return m_Binding.propertyName; } } public string path { get { return m_Binding.path; } } diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs index 2039feb4bb..2bc5123b72 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowHierarchyGUI.cs @@ -294,13 +294,13 @@ private void DoIconAndName(Rect rect, AnimationWindowHierarchyNode node, bool se nodePrefix = string.IsNullOrEmpty(gameObjectName) ? "" : gameObjectName + " : "; } - Styles.content = new GUIContent(nodePrefix + node.displayName + warningText, GetIconForItem(node), tooltipText); + Styles.content = new GUIContent(nodePrefix + node.displayName + warningText, GetEffectiveIcon(node, selected, focused), tooltipText); textColor = EditorStyles.label.normal.textColor; } else { - Styles.content = new GUIContent(node.displayName + warningText, GetIconForItem(node), tooltipText); + Styles.content = new GUIContent(node.displayName + warningText, GetEffectiveIcon(node, selected, focused), tooltipText); textColor = EditorStyles.label.normal.textColor; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs index 134bc4fac4..97295ebd4c 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowKeyframe.cs @@ -101,8 +101,9 @@ public AnimationWindowCurve curve } } - public bool isPPtrCurve { get { return curve.isPPtrCurve; } } - public bool isDiscreteCurve { get { return curve.isDiscreteCurve; } } + public bool isPPtrCurve => curve.isPPtrCurve; + public bool isDiscreteCurve => curve.isDiscreteCurve; + public bool isFloatCurve => curve.isFloatCurve; public AnimationWindowKeyframe() { diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs index 001cccf317..0b489e5581 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowState.cs @@ -332,16 +332,9 @@ public void RefreshCurve(EditorCurveBinding binding) } } - private void PurgeSelection() - { - linkedWithSequencer = false; - m_Selection = new FallbackSelectionItem(); - } - public void OnEnable() { Undo.undoRedoEvent += UndoRedoPerformed; - AssemblyReloadEvents.beforeAssemblyReload += PurgeSelection; AnimationUtility.onCurveWasModified += CurveWasModified; // NoOps... @@ -356,7 +349,6 @@ public void OnEnable() public void OnDisable() { Undo.undoRedoEvent -= UndoRedoPerformed; - AssemblyReloadEvents.beforeAssemblyReload -= PurgeSelection; AnimationUtility.onCurveWasModified -= CurveWasModified; previewing = false; diff --git a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs index a6ffa822f1..a69eaeeb39 100644 --- a/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs +++ b/Editor/Mono/Animation/AnimationWindow/AnimationWindowUtility.cs @@ -312,7 +312,7 @@ public static bool ContainsFloatKeyframes(List keyframe foreach (var key in keyframes) { - if (!key.isPPtrCurve) + if (key.isFloatCurve) return true; } @@ -861,6 +861,7 @@ public static CurveWrapper GetCurveWrapper(AnimationWindowState state, Animation curveWrapper.renderer.SetWrap(WrapMode.Clamp, clip.isLooping ? WrapMode.Loop : WrapMode.Clamp); curveWrapper.renderer.SetCustomRange(0f, clip.length); curveWrapper.binding = curve.binding; + curveWrapper.isPhantom = curve.isPhantom; curveWrapper.id = curve.GetHashCode(); curveWrapper.color = CurveUtility.GetPropertyColor(curve.propertyName); curveWrapper.hidden = false; diff --git a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowClip.cs b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowClip.cs index 1bc69f4cb3..670319efac 100644 --- a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowClip.cs +++ b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowClip.cs @@ -9,10 +9,11 @@ namespace UnityEditor.AnimationWindowBuiltin { + [Serializable] class AnimationWindowClip : IAnimationWindowClip, IEquatable { - AnimationClip m_Clip; - GameObject m_RootGameObject; + [SerializeField] AnimationClip m_Clip; + [SerializeField] GameObject m_RootGameObject; public string name => m_Clip.name; public int id => m_Clip.GetInstanceID(); @@ -87,7 +88,7 @@ public void LoadCurves(List curves) { AnimationWindowCurve curve = new AnimationWindowCurve( this, - RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(curveBinding, m_Clip), + curveBinding, GetValueType(curveBinding)); curves.Add(curve); @@ -144,11 +145,11 @@ private void FillInMissingTransformCurves(AnimationClip animationClip, List curvesCache) { var newBinding = lastBinding; - newBinding.isPhantom = true; if (!propertyGroup[0].HasValue) { newBinding.propertyName = propertyGroupName + ".x"; AnimationWindowCurve curve = new AnimationWindowCurve(this, newBinding, GetValueType(newBinding)); + curve.isPhantom = true; curvesCache.Add(curve); } @@ -156,6 +157,7 @@ private void FillPropertyGroup(AnimationClip animationClip, ref EditorCurveBindi { newBinding.propertyName = propertyGroupName + ".y"; AnimationWindowCurve curve = new AnimationWindowCurve(this, newBinding, GetValueType(newBinding)); + curve.isPhantom = true; curvesCache.Add(curve); } @@ -163,6 +165,7 @@ private void FillPropertyGroup(AnimationClip animationClip, ref EditorCurveBindi { newBinding.propertyName = propertyGroupName + ".z"; AnimationWindowCurve curve = new AnimationWindowCurve(this, newBinding, GetValueType(newBinding)); + curve.isPhantom = true; curvesCache.Add(curve); } } diff --git a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowControl.cs index 76f2b0dcd0..58a1d473a8 100644 --- a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowControl.cs @@ -19,14 +19,6 @@ namespace UnityEditor.AnimationWindowBuiltin [Serializable] class AnimationWindowControl : IAnimationWindowController, IAnimationContextualResponder { - public AnimationWindowControl() - { - if (AnimationMode.GetDriver() is MainDriver mainDriver) - m_Driver = mainDriver; - } - - class MainDriver: AnimationModeDriver {} - class CandidateDriver: AnimationModeDriver {} class CandidateRecordingState : IAnimationRecordingState { public GameObject activeGameObject { get; private set; } @@ -174,7 +166,9 @@ public void Dispose() { StopPreview(); if (m_Driver != null) + { ScriptableObject.DestroyImmediate(m_Driver); + } } public void OnSelectionChanged() @@ -186,15 +180,6 @@ public void OnSelectionChanged() StopPreview(); } - public void OnPlayModeStateChanged(PlayModeStateChange state) - { - if (state == PlayModeStateChange.ExitingPlayMode || - state == PlayModeStateChange.ExitingEditMode) - { - StopPreview(); - } - } - public float time { get => m_Time.time; @@ -361,8 +346,11 @@ public bool canPreview if (state.activeAnimationPlayer is Animator{ isOptimizable:true, hasTransformHierarchy:false}) return false; + var driver = GetAnimationModeDriverNoAlloc(); - return (driver != null && AnimationMode.InAnimationMode(driver)) || !AnimationMode.InAnimationMode(); + + return !AnimationMode.InAnimationMode() // no one is in mode + || (driver != null && AnimationMode.InAnimationMode(driver)); } } @@ -618,7 +606,7 @@ private void ResampleAnimation(ResampleFlags flags) if (animationClip != null) { var animationPlayer = state.activeAnimationPlayer; - bool usePlayableGraph = animationPlayer is Animator; + bool usePlayableGraph = animationPlayer is Animator && !animationClip.legacy; if (usePlayableGraph) { @@ -696,7 +684,7 @@ private AnimationModeDriver GetAnimationModeDriver() { if (m_Driver == null) { - m_Driver = ScriptableObject.CreateInstance(); + m_Driver = ScriptableObject.CreateInstance(); m_Driver.hideFlags = HideFlags.HideAndDontSave; m_Driver.name = "AnimationWindowDriver"; m_Driver.isKeyCallback += (Object target, string propertyPath) => @@ -726,7 +714,7 @@ private AnimationModeDriver GetCandidateDriver() { if (m_CandidateDriver == null) { - m_CandidateDriver = ScriptableObject.CreateInstance(); + m_CandidateDriver = ScriptableObject.CreateInstance(); m_CandidateDriver.name = "AnimationWindowCandidateDriver"; } diff --git a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowEvent.cs b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowEvent.cs index 3227c787f2..6c22223fce 100644 --- a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowEvent.cs +++ b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowEvent.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; namespace UnityEditor.AnimationWindowBuiltin @@ -48,11 +49,13 @@ public AnimationEventEditorState() } } - internal class AnimationWindowEvent : ScriptableObject + [HelpURL("script-AnimationWindowEvent")] + class AnimationWindowEvent : ScriptableObject { public GameObject root; public AnimationClip clip; - public AnimationClipInfoProperties clipInfo; + // Only used within AnimationClipEditor. + [NonSerialized] public AnimationClipInfoProperties clipInfo; public int eventIndex; static public AnimationWindowEvent CreateAndEdit(GameObject root, AnimationClip clip, float time) diff --git a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowSelectionItem.cs b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowSelectionItem.cs index 3a0f75ea68..c80c0a82d4 100644 --- a/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowSelectionItem.cs +++ b/Editor/Mono/Animation/AnimationWindow/Builtin/AnimationWindowSelectionItem.cs @@ -273,11 +273,6 @@ public void DiscardChanges() throw new NotImplementedException(); } - public virtual void OnPlayModeStateChanged(PlayModeStateChange state) - { - controller.OnPlayModeStateChanged(state); - } - // When curve is modified, we never trigger refresh right away. We order a refresh at later time by setting refresh to appropriate value. public virtual void CurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType type) { diff --git a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs index 5c045487c8..a4a607224b 100644 --- a/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/CurveEditor.cs @@ -68,6 +68,7 @@ internal enum SelectionMode // Input - should not be changed by curve editor public int id; public EditorCurveBinding binding; + public bool isPhantom; public int groupId; public int regionId; // Regions are defined by two curves added after each other with the same regionId. public Color color; diff --git a/Editor/Mono/Animation/AnimationWindow/DefaultAnimationWindowController.cs b/Editor/Mono/Animation/AnimationWindow/DefaultAnimationWindowController.cs index 88b1161a2d..7ced4114fd 100644 --- a/Editor/Mono/Animation/AnimationWindow/DefaultAnimationWindowController.cs +++ b/Editor/Mono/Animation/AnimationWindow/DefaultAnimationWindowController.cs @@ -16,7 +16,6 @@ class DefaultAnimationWindowController : IAnimationWindowController public void Dispose() {} public void OnSelectionChanged() {} - public void OnPlayModeStateChanged(PlayModeStateChange state) { } public float time { diff --git a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs index 4e3b948ff4..f7ef108b89 100644 --- a/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs +++ b/Editor/Mono/Animation/AnimationWindow/DopeSheetEditor.cs @@ -589,7 +589,7 @@ private GenericMenu GenerateMenu(DopeLine dopeline) Hashtable editorCurves = new Hashtable(); foreach (AnimationWindowKeyframe key in state.selectedKeys) { - if (key.isDiscreteCurve) + if (!key.isFloatCurve) continue; int index = key.curve.GetKeyframeIndex(AnimationKeyTime.Time(key.time, state.frameRate)); @@ -1478,7 +1478,12 @@ public void UpdateCurves(List changedCurves, string undoText) { AnimationWindowCurve curve = state.filteredCurves.Find(c => changedCurve.curveId == c.GetHashCode()); if (curve != null) + { + curve.Clear(); + curve.FromAnimationCurve(changedCurve.curve); + curves.Add(curve); + } else Debug.LogError("Could not match ChangedCurve data to destination curves."); } diff --git a/Editor/Mono/Animation/AnimationWindow/FallbackSelectionItem.cs b/Editor/Mono/Animation/AnimationWindow/FallbackSelectionItem.cs index ba630422f2..8cde0bb53c 100644 --- a/Editor/Mono/Animation/AnimationWindow/FallbackSelectionItem.cs +++ b/Editor/Mono/Animation/AnimationWindow/FallbackSelectionItem.cs @@ -7,6 +7,7 @@ namespace UnityEditor { + [Serializable] class FallbackSelectionItem : IAnimationWindowSelectionItem { GameObject m_GameObject; @@ -78,10 +79,6 @@ public Type GetValueType(EditorCurveBinding _) return null; } - public void OnPlayModeStateChanged(PlayModeStateChange state) - { - } - public bool isImported => false; public bool hasUnsavedChanges => false; public void SaveChanges() diff --git a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowControl.cs b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowControl.cs index a0b2b49002..b95ff6fde8 100644 --- a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowControl.cs +++ b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowControl.cs @@ -21,10 +21,6 @@ public virtual void OnEnable() public abstract void OnSelectionChanged(); - public void OnPlayModeStateChanged(PlayModeStateChange state) - { - } - public void Init(AnimationWindowState state) { m_State = state; diff --git a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowController.cs b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowController.cs index 730865cd50..5859e47c2e 100644 --- a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowController.cs +++ b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowController.cs @@ -13,8 +13,6 @@ interface IAnimationWindowController : IDisposable { void OnSelectionChanged(); - void OnPlayModeStateChanged(PlayModeStateChange state); - float time { get; set; } int frame { get; set; } diff --git a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowSelectionItem.cs b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowSelectionItem.cs index 78c39215f6..460a99a2a6 100644 --- a/Editor/Mono/Animation/AnimationWindow/IAnimationWindowSelectionItem.cs +++ b/Editor/Mono/Animation/AnimationWindow/IAnimationWindowSelectionItem.cs @@ -34,7 +34,6 @@ interface IAnimationWindowSelectionItem : ISelectionBinding, IDisposable IAnimationWindowClip[] GetClips(); IAnimationWindowClip CreateNewClip(); bool InitializeSelection(); - void OnPlayModeStateChanged(PlayModeStateChange state); bool IsCompatibleWith(UnityEngine.Object selectedObject); EditorCurveBinding[] GetAnimatableBindings(GameObject gameObject); diff --git a/Editor/Mono/Animation/AnimationWindow/RotationCurveInterpolation.cs b/Editor/Mono/Animation/AnimationWindow/RotationCurveInterpolation.cs index ece0fffa22..4c943b46c2 100644 --- a/Editor/Mono/Animation/AnimationWindow/RotationCurveInterpolation.cs +++ b/Editor/Mono/Animation/AnimationWindow/RotationCurveInterpolation.cs @@ -3,6 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; using UnityEditorInternal; namespace UnityEditor @@ -42,27 +44,37 @@ public static string GetPrefixForInterpolation(Mode newInterpolationMode) return null; } + static List s_BindingsCache = new (); + static readonly Regex s_PropertyWithSuffixRegex = new (@"(?\.[xyz])$"); internal static EditorCurveBinding[] ConvertRotationPropertiesToInterpolationType(ReadOnlySpan selection, Mode newInterpolationMode) { - if (selection.Length != 4) - return selection.ToArray(); + if (s_BindingsCache.Capacity < selection.Length) + s_BindingsCache.Capacity = selection.Length; - if (GetModeFromCurveData(selection[0]) == Mode.RawQuaternions) + s_BindingsCache.Clear(); + for (int i = 0; i < selection.Length; ++i) { - EditorCurveBinding[] newCurves = new EditorCurveBinding[3]; - newCurves[0] = selection[0]; - newCurves[1] = selection[1]; - newCurves[2] = selection[2]; + if (GetModeFromCurveData(selection[i]) == Mode.RawQuaternions) + { + // Process x, y, z rotation bindings. Drop w channel. + var match = s_PropertyWithSuffixRegex.Match(selection[i].propertyName); + if (match.Success) + { + string prefix = GetPrefixForInterpolation(newInterpolationMode); - string prefix = GetPrefixForInterpolation(newInterpolationMode); - newCurves[0].propertyName = prefix + ".x"; - newCurves[1].propertyName = prefix + ".y"; - newCurves[2].propertyName = prefix + ".z"; + var newBinding = selection[i]; + newBinding.propertyName = prefix + match.Groups["suffix"]; - return newCurves; + s_BindingsCache.Add(newBinding); + } + } + else + { + s_BindingsCache.Add(selection[i]); + } } - else - return selection.ToArray(); + + return s_BindingsCache.ToArray(); } } } diff --git a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs index 334acb0ff6..77ee63a627 100644 --- a/Editor/Mono/Animation/EditorCurveBinding.bindings.cs +++ b/Editor/Mono/Animation/EditorCurveBinding.bindings.cs @@ -3,12 +3,8 @@ // https://unity3d.com/legal/licenses/Unity_Reference_Only_License using System; -using UnityEngine.Bindings; -using UnityEngine.Scripting; -using UnityEngine.Playables; -using UnityEngine.Scripting.APIUpdating; -using UnityEngine.Internal; using UnityEngine; +using UnityEngine.Bindings; using static UnityEditor.AnimationUtility; namespace UnityEditor @@ -115,7 +111,7 @@ static public EditorCurveBinding PPtrCurve(string inPath, System.Type inType, st EditorCurveBinding binding; BaseCurve(inPath, inType, inPropertyName, out binding); binding.m_isPPtrCurve = 1; - binding.m_isDiscreteCurve = 1; + binding.m_isDiscreteCurve = 0; binding.m_isSerializeReferenceCurve = 0; binding.m_isUnknownCurve = 0; return binding; @@ -149,7 +145,7 @@ static public EditorCurveBinding SerializeReferenceCurve(string inPath, System.T EditorCurveBinding binding; BaseCurve(inPath, inType, $"managedReferences[{refID}].{inPropertyName}", out binding); binding.m_isPPtrCurve = isPPtr ? 1 : 0; - binding.m_isDiscreteCurve = isDiscrete || isPPtr ? 1 : 0; + binding.m_isDiscreteCurve = isDiscrete ? 1 : 0; binding.m_isSerializeReferenceCurve = 1; binding.m_isUnknownCurve = 0; diff --git a/Editor/Mono/Animation/MaterialAnimationUtility.cs b/Editor/Mono/Animation/MaterialAnimationUtility.cs index 27cfc5e01d..87dba66f93 100644 --- a/Editor/Mono/Animation/MaterialAnimationUtility.cs +++ b/Editor/Mono/Animation/MaterialAnimationUtility.cs @@ -8,6 +8,7 @@ using UnityEngine; using ShaderPropertyType = UnityEngine.Rendering.ShaderPropertyType; using Object = UnityEngine.Object; +using UnityEngine.Rendering; namespace UnityEditorInternal { @@ -41,11 +42,24 @@ static PropertyModification[] MaterialPropertyToPropertyModifications(MaterialPr static PropertyModification[] MaterialPropertyToPropertyModifications(MaterialProperty materialProp, Object target, Color color) { + PropertyModification[] modifications = CreatePropertyModifications(4, target); - SetupPropertyModification(materialProp.name + ".r", color.r, modifications[0]); - SetupPropertyModification(materialProp.name + ".g", color.g, modifications[1]); - SetupPropertyModification(materialProp.name + ".b", color.b, modifications[2]); - SetupPropertyModification(materialProp.name + ".a", color.a, modifications[3]); + + // HDR Colours animations need to be read as {x,y,z,w} properties to render correctly. + if ((materialProp.propertyFlags & ShaderPropertyFlags.HDR) != 0) + { + SetupPropertyModification(materialProp.name + ".x", color.r, modifications[0]); + SetupPropertyModification(materialProp.name + ".y", color.g, modifications[1]); + SetupPropertyModification(materialProp.name + ".z", color.b, modifications[2]); + SetupPropertyModification(materialProp.name + ".w", color.a, modifications[3]); + } + else + { + SetupPropertyModification(materialProp.name + ".r", color.r, modifications[0]); + SetupPropertyModification(materialProp.name + ".g", color.g, modifications[1]); + SetupPropertyModification(materialProp.name + ".b", color.b, modifications[2]); + SetupPropertyModification(materialProp.name + ".a", color.a, modifications[3]); + } return modifications; } @@ -127,7 +141,7 @@ static public void SetupMaterialPropertyBlock(MaterialProperty materialProp, int { MaterialPropertyBlock block = new MaterialPropertyBlock(); target.GetPropertyBlock(block); - materialProp.WriteToMaterialPropertyBlock(block, changedMask); + materialProp.WriteToMaterialPropertyBlockInEditor(block, changedMask); target.SetPropertyBlock(block); } diff --git a/Editor/Mono/AnimatorController.bindings.cs b/Editor/Mono/AnimatorController.bindings.cs index 89521ecc2e..4033521a30 100644 --- a/Editor/Mono/AnimatorController.bindings.cs +++ b/Editor/Mono/AnimatorController.bindings.cs @@ -82,8 +82,11 @@ static public StateMachineBehaviourContext[] FindStateMachineBehaviourContext(St [FreeFunction("FindStateMachineBehaviourContext")] extern internal static StateMachineBehaviourContext[] Internal_FindStateMachineBehaviourContext(ScriptableObject behaviour); - [FreeFunction("AnimatorControllerBindings::Internal_CreateStateMachineBehaviour")] - extern public static int CreateStateMachineBehaviour(MonoScript script); + [FreeFunction("AnimatorControllerBindings::Internal_CreateNewStateMachineBehaviour")] + extern public static EntityId CreateNewStateMachineBehaviour(MonoScript script); + + [Obsolete("CreateStateMachineBehaviour is deprecated. Use CreateNewStateMachineBehaviour instead.", false)] + public static int CreateStateMachineBehaviour(MonoScript script) => (int)CreateNewStateMachineBehaviour(script); [FreeFunction("AnimatorControllerBindings::CanAddStateMachineBehaviours")] extern internal static bool CanAddStateMachineBehaviours(); @@ -130,7 +133,7 @@ internal extern bool isAssetBundled get; } - extern internal void AddStateEffectiveBehaviour([NotNull] AnimatorState state, int layerIndex, int instanceID); + extern internal void AddStateEffectiveBehaviour([NotNull] AnimatorState state, int layerIndex, EntityId entityId); extern internal void RemoveStateEffectiveBehaviour([NotNull] AnimatorState state, int layerIndex, int behaviourIndex); [FreeFunction(Name = "AnimatorControllerBindings::Internal_GetEffectiveBehaviours", HasExplicitThis = true)] diff --git a/Editor/Mono/Annotation/GizmoInfo.cs b/Editor/Mono/Annotation/GizmoInfo.cs index 63c3abe3ef..a5fc46acd8 100644 --- a/Editor/Mono/Annotation/GizmoInfo.cs +++ b/Editor/Mono/Annotation/GizmoInfo.cs @@ -95,19 +95,45 @@ public Texture2D thumb { get { - if (m_Thumb == null) + EnsureThumbResolved(); + return m_Thumb; + } + } + + + [NonSerialized] bool m_ThumbResolved; + + void EnsureThumbResolved() + { + if (m_ThumbResolved) return; + m_ThumbResolved = true; + + // Try script icons + var scriptObj = script; + if (scriptObj != null) + { + // 1) [Icon] attribute path + if (scriptObj is MonoScript monoScript) { - // Icon for scripts - if (script != null) - m_Thumb = EditorGUIUtility.GetIconForObject(m_Script); - // Icon for builtin components - else if (hasIcon) - m_Thumb = AssetPreview.GetMiniTypeThumbnailFromClassID(m_ClassID); + var type = monoScript.GetClass(); + if (type != null) + { + var path = EditorGUIUtility.GetIconPathFromAttribute(type); + if (!string.IsNullOrEmpty(path)) + m_Thumb = EditorGUIUtility.LoadIcon(path); + } } + if (m_Thumb != null) return; - return m_Thumb; + // 2) Unity's normal script icon (meta icon etc.) + m_Thumb = EditorGUIUtility.GetIconForObject(scriptObj); + if (m_Thumb != null) return; } - } + + // Builtin component icons + if (m_Thumb == null && hasIcon) + m_Thumb = AssetPreview.GetMiniTypeThumbnailFromClassID(m_ClassID); + } public int CompareTo(object obj) { diff --git a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs index 280da3f857..f0d8327e9f 100644 --- a/Editor/Mono/AssemblyInfo/AssemblyInfo.cs +++ b/Editor/Mono/AssemblyInfo/AssemblyInfo.cs @@ -36,9 +36,11 @@ [assembly: InternalsVisibleTo("Unity.IntegrationTests.AssetImporting")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.BuildPipeline")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.Builds")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Insights")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.CrashReporting")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.DeploymentTargets")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.EditorApplication")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.EditorDeeplink")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.EditorUI")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.GameCore")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.GameView")] @@ -50,6 +52,8 @@ [assembly: InternalsVisibleTo("Unity.IntegrationTests.PS4")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.PS5")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.Switch")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.Switch2")] +[assembly: InternalsVisibleTo("Unity.IntegrationTests.NintendoCore")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.Rendering")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.SceneVisibility")] [assembly: InternalsVisibleTo("Unity.IntegrationTests.ScriptCompilation")] @@ -75,6 +79,7 @@ [assembly: InternalsVisibleTo("UnityEditor.PS4.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.PS5.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.Switch.Extensions")] +[assembly: InternalsVisibleTo("UnityEditor.Switch2.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.WebGL.Extensions")] [assembly: InternalsVisibleTo("Unity.Automation.Players.WebGL")] [assembly: InternalsVisibleTo("Unity.WebGL.Extensions")] @@ -95,7 +100,6 @@ [assembly: InternalsVisibleTo("UnityEditor.Analytics")] [assembly: InternalsVisibleTo("UnityEditor.Purchasing")] [assembly: InternalsVisibleTo("UnityEditor.Lumin")] -[assembly: InternalsVisibleTo("UnityEditor.Switch.Extensions")] [assembly: InternalsVisibleTo("UnityEditor.EditorTestsRunner")] [assembly: InternalsVisibleTo("UnityEditor.TestRunner")] [assembly: InternalsVisibleTo("UnityEditor.TestRunner.Tests")] @@ -103,6 +107,7 @@ [assembly: InternalsVisibleTo("ExternalCSharpCompiler")] [assembly: InternalsVisibleTo("UnityEngine.TestRunner")] [assembly: InternalsVisibleTo("Unity.Modules.AssetDatabase.ImportActivityWindow.Tests")] +[assembly: InternalsVisibleTo("Unity.Modules.AssetDatabase.UnityPackage.Tests.Editor")] [assembly: InternalsVisibleTo("UnityEditor.VR")] [assembly: InternalsVisibleTo("Unity.RuntimeTests")] [assembly: InternalsVisibleTo("Unity.RuntimeTests.Framework")] @@ -111,7 +116,12 @@ [assembly: InternalsVisibleTo("Unity.CrossModule.PlayMode.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Module.AssetDatabase.DanglingComponents.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.ComponentUtility.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.AssetType.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.DragAndDrop.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.SceneHierarchy.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.SceneHierarchy.Tests.Common")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.NestedPrefabsBackwardsCompatibility.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.CoreEditor.NestedPrefabsFrontEnd.Tests.Editor")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("UnityEditor.InteractiveTutorialsFramework")] [assembly: InternalsVisibleTo("UnityEditor.Networking")] @@ -194,6 +204,7 @@ [assembly: InternalsVisibleTo("UnityEditor.Switch.Tests")] +[assembly: InternalsVisibleTo("UnityEditor.Switch2.Tests")] [assembly: InternalsVisibleTo("UnityEditor.BuildProfileModule.Tests")] //For add Component tests @@ -218,13 +229,17 @@ [assembly: InternalsVisibleTo("Unity.Modules.Licensing.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.PlatformIcons.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.BuildProfileEditor.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.UI.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.Multiplayer.Server.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.AssetDatabase.AssetPostProcessor.Tests.Editor")] // This should move with the AnimationWindow to a module at some point [assembly: InternalsVisibleTo("Unity.Modules.Animation.AnimationWindow.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.Physics.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.GI.EditorBake.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.Physics2D.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Modules.U2D.NineSlice.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.GI.Prefabs.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.PackageManagerUI.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Tests.Shared")] @@ -237,8 +252,17 @@ [assembly: InternalsVisibleTo("Unity.CrossModule.ScriptableRenderPipeline.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Core.EditorWindowManagement.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.BootConfig.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.BootConfig.Tests.Playmode")] [assembly: InternalsVisibleTo("Unity.Core.InspectorFramework.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.EditorUtils.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Umpe.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.Undo.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.Core.UnityType.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.Core.VersionControl.Tests.Editor")] [assembly: InternalsVisibleTo("Unity.ProjectAuditor.EditorTests")] [assembly: InternalsVisibleTo("Unity.Modules.GI.Analytics.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.CrossModule.AssetLoading.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.SceneTemplateEditor.Tests.Editor")] +[assembly: InternalsVisibleTo("Unity.Modules.AI.Tests.Editor")] diff --git a/Editor/Mono/AssetModificationProcessor.cs b/Editor/Mono/AssetModificationProcessor.cs index 3c49382af1..1ff4bb026d 100644 --- a/Editor/Mono/AssetModificationProcessor.cs +++ b/Editor/Mono/AssetModificationProcessor.cs @@ -161,6 +161,14 @@ static void OnWillSaveAssets(string[] assets, out string[] assetsThatShouldBeSav MethodInfo method = assetModificationProcessorClass.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (method != null) { + if (ContainsNullOrWhiteSpaceString(assetsThatShouldBeSaved)) + { + int originalCount = assetsThatShouldBeSaved.Length; + assetsThatShouldBeSaved = Array.FindAll(assetsThatShouldBeSaved, s => !string.IsNullOrWhiteSpace(s)); + int skippedCount = originalCount - assetsThatShouldBeSaved.Length; + Debug.LogWarning($"OnWillSaveAssets: Skipped {skippedCount} null or empty path(s)."); + } + object[] args = { assetsThatShouldBeSaved }; if (!CheckArguments(args, method)) continue; @@ -610,5 +618,10 @@ internal static bool MakeEditable(string[] paths, string prompt, List ou return true; } + + internal static bool ContainsNullOrWhiteSpaceString(string[] stringArray) + { + return Array.Exists(stringArray, string.IsNullOrWhiteSpace); + } } } diff --git a/Editor/Mono/AssetPipeline/BumpMapSettings.bindings.cs b/Editor/Mono/AssetPipeline/BumpMapSettings.bindings.cs index dd9c4c80b7..d177927130 100644 --- a/Editor/Mono/AssetPipeline/BumpMapSettings.bindings.cs +++ b/Editor/Mono/AssetPipeline/BumpMapSettings.bindings.cs @@ -2,6 +2,7 @@ // Copyright (c) Unity Technologies. For terms of use, see // https://unity3d.com/legal/licenses/Unity_Reference_Only_License +using System; using UnityEngine; using UnityEngine.Bindings; @@ -11,13 +12,12 @@ namespace UnityEditor [NativeHeader("Editor/Src/AssetPipeline/TextureImporting/BumpMapSettings.h")] internal class BumpMapSettings { - public static extern bool silentMode { get; set; } - public static extern void PerformBumpMapCheck([NotNull] Material material); } public static class MaterialEditorExtensions { + [Obsolete("PerformBumpMapCheck is obsolete.", false)] public static void PerformBumpMapCheck(this Material material) { BumpMapSettings.PerformBumpMapCheck(material); diff --git a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs index 081cd3b494..2035b8cdf9 100644 --- a/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs +++ b/Editor/Mono/AssetPipeline/SpeedTree/SpeedTree9ImporterEditor.cs @@ -46,6 +46,12 @@ internal IEnumerable importers public override void OnEnable() { + if (!AreImporterTargetsValid()) + { + base.OnEnable(); // lets the base mark the editor enabled/inert (OnDisable symmetry) + return; + } + m_STImporter = target as SpeedTree9Importer; if (tabs == null) @@ -67,9 +73,14 @@ public override void OnEnable() public override void OnDisable() { - foreach (var tab in tabs) + // The tabs are only built by OnEnable when the importer targets are still valid. base.OnDisable + // must run either way: it is what unsubscribes this editor from the static header GUI events. + if (tabs != null) { - tab.OnDisable(); + foreach (var tab in tabs) + { + tab.OnDisable(); + } } base.OnDisable(); } diff --git a/Editor/Mono/Audio/AudioContainerWindow.cs b/Editor/Mono/Audio/AudioContainerWindow.cs index 03355b2a14..23b7474c63 100644 --- a/Editor/Mono/Audio/AudioContainerWindow.cs +++ b/Editor/Mono/Audio/AudioContainerWindow.cs @@ -97,6 +97,8 @@ enum Icons VisualElement m_CountRandomizationButtonImage; MinMaxSlider m_CountRandomizationRangeSlider; Vector2Field m_CountRandomizationRangeField; + readonly Vector2 m_CountRandomizationBounds = new(-10, 10); + Label m_AutomaticTriggerModeLabel; Label m_LoopLabel; @@ -1162,6 +1164,8 @@ void SubscribeToAutomaticTriggerCallbacksAndEvents() { m_TimeRandomizationButton.clicked += OnTimeRandomizationButtonClicked; m_CountRandomizationButton.clicked += OnCountRandomizationButtonClicked; + m_CountRandomizationRangeSlider.RegisterCallback>(OnCountRandomizationRangeChanged, TrickleDown.TrickleDown); + m_CountRandomizationRangeSlider.RegisterCallback(OnCountSliderMouseUp, TrickleDown.TrickleDown); m_TimeSlider.RegisterValueChangedCallback(OnTimeChanged); m_TimeRandomizationRangeField.RegisterValueChangedCallback(OnTimeRandomizationRangeChanged); m_TimeRandomizationRangeSlider.RegisterValueChangedCallback(OnTimeRandomizationRangeChanged); @@ -1175,6 +1179,9 @@ void UnsubscribeFromAutomaticTriggerCallbacksAndEvents() if (m_CountRandomizationButton != null) m_CountRandomizationButton.clicked -= OnCountRandomizationButtonClicked; + m_CountRandomizationRangeSlider?.UnregisterCallback>(OnCountRandomizationRangeChanged); + m_CountRandomizationRangeSlider?.UnregisterCallback(OnCountSliderMouseUp, TrickleDown.TrickleDown); + m_TimeSlider?.UnregisterValueChangedCallback(OnTimeChanged); m_TimeRandomizationRangeField?.UnregisterValueChangedCallback(OnTimeRandomizationRangeChanged); m_TimeRandomizationRangeSlider?.UnregisterValueChangedCallback(OnTimeRandomizationRangeChanged); @@ -1343,6 +1350,30 @@ void OnCountRandomizationButtonClicked() State.AudioContainer.loopCountRandomizationEnabled = !State.AudioContainer.loopCountRandomizationEnabled; } + void OnCountRandomizationRangeChanged(ChangeEvent evt) + { + + // always stop propagation to stop decimals coming through, we will apply the value in OnCountSliderMouseUp + // This stops jittering of the slider/field + evt.StopImmediatePropagation(); + + var roundedAndClampedValue = new Vector2( + Mathf.Clamp(Mathf.RoundToInt(evt.newValue.x), m_CountRandomizationBounds.x, 0.0f), + Mathf.Clamp(Mathf.RoundToInt(evt.newValue.y), 0.0f, m_CountRandomizationBounds.y) + ); + + m_CountRandomizationRangeSlider.SetValueWithoutNotify(roundedAndClampedValue); + m_CountRandomizationRangeField.SetValueWithoutNotify(roundedAndClampedValue); + + } + + void OnCountSliderMouseUp(MouseCaptureOutEvent evt) + { + var property = State.SerializedObject.FindProperty("m_LoopCountRandomizationRange"); + property.vector2Value = m_CountRandomizationRangeSlider.value; + State.SerializedObject.ApplyModifiedProperties(); + } + void OnAudioMasterMuteChanged(bool isMuted) { if (isMuted && State.IsPreviewPlayingOrPaused()) diff --git a/Editor/Mono/Audio/AudioGeneratorSerializableInterfaceDrawer.cs b/Editor/Mono/Audio/AudioGeneratorSerializableInterfaceDrawer.cs index 5e6cbb44ef..b2831c5890 100644 --- a/Editor/Mono/Audio/AudioGeneratorSerializableInterfaceDrawer.cs +++ b/Editor/Mono/Audio/AudioGeneratorSerializableInterfaceDrawer.cs @@ -7,15 +7,15 @@ namespace UnityEditor { - [CustomPropertyDrawer(typeof(IGeneratorDefinition.Serializable))] + [CustomPropertyDrawer(typeof(IAudioGenerator.Serializable))] internal class AudioGeneratorSerializableInterfaceDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty serializableStruct, GUIContent label) { - var innerField = serializableStruct.FindPropertyRelative(nameof(IGeneratorDefinition.Serializable.Reference)); + var innerField = serializableStruct.FindPropertyRelative(nameof(IAudioGenerator.Serializable.Reference)); // TODO: There's a bug where "allowSceneObjects" isn't recovered correctly from the relative property, // so while you can drag and drop scene references, you can't object pick them (yet). - EditorGUI.ObjectField(position, innerField, typeof(IGeneratorDefinition), label); + EditorGUI.ObjectField(position, innerField, typeof(IAudioGenerator), label); } } } diff --git a/Editor/Mono/Audio/AudioRandomContainerExtensions.cs b/Editor/Mono/Audio/AudioRandomContainerExtensions.cs new file mode 100644 index 0000000000..9e67f90353 --- /dev/null +++ b/Editor/Mono/Audio/AudioRandomContainerExtensions.cs @@ -0,0 +1,103 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine; +using UnityEngine.Audio; + +namespace UnityEditor; + +static class AudioRandomContainerExtensions +{ + const string k_BaseAddElementsUndoName = $"Add {nameof(AudioRandomContainer)} element"; + + /// + /// Adds a number of new, default-initialized objects to . + /// + /// The instance to add the elements to. + /// The number of elements to add. + internal static void AddElements(this AudioRandomContainer container, int count) + { + ValidateContainer(container); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count), "Must be greater than zero."); + + AddElementsInner(container, count, (i, element) => { }); + } + + /// + /// Adds a given number of new objects with clips assigned to . + /// + /// The instance to add the elements to. + /// An array of objects to be assigned to each new element. + internal static void AddElements(this AudioRandomContainer container, AudioClip[] clips) + { + ValidateContainer(container); + + if (clips == null) + throw new ArgumentNullException(nameof(clips)); + + if (clips.Length == 0) + throw new ArgumentException("Must not be empty.", nameof(clips)); + + AddElementsInner(container, clips.Length, (i, element) => + { + if (clips[i] != null) + element.audioClip = clips[i]; + }); + } + + static void ValidateContainer(AudioRandomContainer container) + { + if (container == null) + throw new ArgumentNullException(nameof(container)); + + if (!EditorUtility.IsPersistent(container)) + throw new ArgumentException("Must be a persistent asset.", nameof(container)); + } + + static void AddElementsInner(AudioRandomContainer container, int count, + Action configureElement) + { + var undoGroupName = count > 1 ? $"{k_BaseAddElementsUndoName}s" : k_BaseAddElementsUndoName; + + Undo.RegisterCompleteObjectUndo(container, undoGroupName); + Undo.SetCurrentGroupName(undoGroupName); + + if (container.elements == null) + container.elements = Array.Empty(); + + var newElements = new AudioContainerElement[count]; + var oldAndNewElements = new AudioContainerElement[container.elements.Length + count]; + Array.Copy(container.elements, oldAndNewElements, container.elements.Length); + + for (var i = 0; i < count; i++) + { + var element = new AudioContainerElement + { + name = $"{nameof(AudioContainerElement)}-{GUID.Generate()}", hideFlags = HideFlags.HideInHierarchy + }; + + configureElement(i, element); + newElements[i] = element; + oldAndNewElements[container.elements.Length + i] = element; + AssetDatabase.AddObjectToAsset(element, container); + EditorUtility.SetDirty(element); + } + + container.elements = oldAndNewElements; + EditorUtility.SetDirty(container); + + foreach (var element in newElements) + Undo.RegisterCreatedObjectUndo(element, k_BaseAddElementsUndoName); + + // Note: we deliberately don't save the root asset or the sub assets here + // as this by unspoken Unity convention generally is considered the user's initiative. + + var undoGroup = Undo.GetCurrentGroup(); + Undo.CollapseUndoOperations(undoGroup); + Undo.IncrementCurrentGroup(); + } +} diff --git a/Editor/Mono/BuildPipeline/AssemblyStripper.cs b/Editor/Mono/BuildPipeline/AssemblyStripper.cs index 3dd8fad816..63019a402b 100644 --- a/Editor/Mono/BuildPipeline/AssemblyStripper.cs +++ b/Editor/Mono/BuildPipeline/AssemblyStripper.cs @@ -20,6 +20,26 @@ namespace UnityEditorInternal { internal class AssemblyStripper { + /// + /// Escapes XML special characters to prevent XML parsing errors. + /// This is necessary for compiler-generated names (e.g., lambda methods like <OnEnable>b__1_1) + /// and generic type names (e.g., List<int>). + /// + /// The string value to escape. + /// The escaped string safe for use in XML attributes and content. + private static string EscapeXmlString(string value) + { + if (string.IsNullOrEmpty(value)) + return value; + + return value + .Replace("&", "&") // Must be first to avoid double-escaping + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'"); + } + static List ProcessBuildPipelineGenerateAdditionalLinkXmlFiles(BuildPostProcessArgs args) { var results = new List(); @@ -130,7 +150,7 @@ private static NPath WriteTypesInScenesBlacklist(RuntimeClassRegistry rcr, NPath sb.AppendLine($"\t"); foreach (var type in assemblyTypePair.Value.OrderBy(s => s)) { - sb.AppendLine($"\t\t"); + sb.AppendLine($"\t\t"); } sb.AppendLine("\t"); } @@ -157,7 +177,7 @@ private static NPath WriteSerializedTypesBlacklist(RuntimeClassRegistry rcr, NPa foreach (var type in assemblyTypePair.Value.OrderBy(s => s)) { oneOrMoreItemsWritten = true; - sb.AppendLine($"\t\t"); + sb.AppendLine($"\t\t"); } sb.AppendLine("\t"); } @@ -375,9 +395,9 @@ private static string GetMethodPreserveBlacklistContents(RuntimeClassRegistry rc var groupedByType = assembly.GroupBy(m => m.fullTypeName); foreach (var type in groupedByType.OrderBy(t => t.Key)) { - sb.AppendLine(string.Format("\t\t", type.Key)); + sb.AppendLine(string.Format("\t\t", EscapeXmlString(type.Key))); foreach (var method in type.OrderBy(m => m.methodName)) - sb.AppendLine(string.Format("\t\t\t", method.methodName)); + sb.AppendLine(string.Format("\t\t\t", EscapeXmlString(method.methodName))); sb.AppendLine("\t\t"); } sb.AppendLine("\t"); diff --git a/Editor/Mono/BuildPipeline/BuildCallbackContext.bindings.cs b/Editor/Mono/BuildPipeline/BuildCallbackContext.bindings.cs new file mode 100644 index 0000000000..8ad42862a9 --- /dev/null +++ b/Editor/Mono/BuildPipeline/BuildCallbackContext.bindings.cs @@ -0,0 +1,81 @@ +// Unity C# reference source +// Copyright (c) Unity Technologies. For terms of use, see +// https://unity3d.com/legal/licenses/Unity_Reference_Only_License + +using System; +using UnityEngine; +using UnityEngine.Bindings; +using UnityEditor.Build.Reporting; +using UnityEngine.Scripting; +using System.Runtime.InteropServices; + +namespace UnityEditor.Build +{ + [RequiredByNativeCode] + [NativeType(Header = "Modules/ContentBuild/Editor/Public/BuildCallbackContext.h")] + [NativeClass("BuildPipeline::BuildCallbackContext")] + public class BuildCallbackContext + { + // The bindings generator is setting the instance pointer in this field + internal IntPtr m_Self; + + internal static class BindingsMarshaller // IS THIS NEEDED ?? + { + public static IntPtr ConvertToNative(BuildCallbackContext ctx) => ctx?.m_Self ?? IntPtr.Zero; + + public static BuildCallbackContext ConvertToManaged(IntPtr ptr) => + ptr != IntPtr.Zero ? new BuildCallbackContext(ptr) : null; + } + + // Constructor used for wrapping native instances + private BuildCallbackContext(IntPtr nativePtr) + { + m_Self = nativePtr; + } + + [FreeFunction("BuildCallbackContextBindings::GetReport")] + private static extern BuildReport GetReportInternal(IntPtr self); + + public BuildReport Report + { + get + { + if (m_Self != IntPtr.Zero) + { + return GetReportInternal(m_Self); + } + return null; + } + } + + [FreeFunction("BuildCallbackContextBindings::IsPlayerBuild")] + private static extern bool IsPlayerBuildInternal(IntPtr self); + + public bool IsPlayerBuild + { + get + { + if (m_Self != IntPtr.Zero) + { + return IsPlayerBuildInternal(m_Self); + } + return false; + } + } + + [FreeFunction("BuildCallbackContextBindings::IsContentOnlyBuild")] + private static extern bool IsContentOnlyBuildInternal(IntPtr self); + + public bool IsContentOnlyBuild + { + get + { + if (m_Self != IntPtr.Zero) + { + return IsContentOnlyBuildInternal(m_Self); + } + return false; + } + } + } +} diff --git a/Editor/Mono/BuildPipeline/BuildPipelineInterfaces.cs b/Editor/Mono/BuildPipeline/BuildPipelineInterfaces.cs index 1dc65562f5..3c05495475 100644 --- a/Editor/Mono/BuildPipeline/BuildPipelineInterfaces.cs +++ b/Editor/Mono/BuildPipeline/BuildPipelineInterfaces.cs @@ -38,6 +38,10 @@ public interface IPreprocessBuildWithReport : IOrderedCallback { void OnPreprocessBuild(BuildReport report); } + public interface IPreprocessBuildWithContext : IOrderedCallback + { + void OnPreprocessBuild(BuildCallbackContext ctx); + } public interface IFilterBuildAssemblies : IOrderedCallback { @@ -54,6 +58,10 @@ public interface IPostprocessBuildWithReport : IOrderedCallback { void OnPostprocessBuild(BuildReport report); } + public interface IPostprocessBuildWithContext : IOrderedCallback + { + void OnPostprocessBuild(BuildCallbackContext ctx); + } public interface IPostBuildPlayerScriptDLLs : IOrderedCallback { @@ -162,6 +170,8 @@ internal class Processors public List buildPreprocessorsWithReport; public List buildPostprocessorsWithReport; + public List buildPreprocessorsWithContext; + public List buildPostprocessorsWithContext; public List launchPostprocessors; public List sceneProcessorsWithReport; @@ -297,8 +307,10 @@ internal static void InitializeBuildCallbacks(BuildCallbacks findFlags) AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPlayerProcessors); AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPreprocessors); AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPreprocessorsWithReport); + AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPreprocessorsWithContext); AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPostprocessors); AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPostprocessorsWithReport); + AddToListIfTypeImplementsInterface(t, ref instance, ref processors.buildPostprocessorsWithContext); } if (findSceneProcessors) @@ -362,10 +374,14 @@ internal static void InitializeBuildCallbacks(BuildCallbacks findFlags) processors.buildPreprocessors.Sort(CompareICallbackOrder); if (processors.buildPreprocessorsWithReport != null) processors.buildPreprocessorsWithReport.Sort(CompareICallbackOrder); + if (processors.buildPreprocessorsWithContext != null) + processors.buildPreprocessorsWithContext.Sort(CompareICallbackOrder); if (processors.buildPostprocessors != null) processors.buildPostprocessors.Sort(CompareICallbackOrder); if (processors.buildPostprocessorsWithReport != null) processors.buildPostprocessorsWithReport.Sort(CompareICallbackOrder); + if (processors.buildPostprocessorsWithContext != null) + processors.buildPostprocessorsWithContext.Sort(CompareICallbackOrder); if (processors.buildTargetProcessors != null) processors.buildTargetProcessors.Sort(CompareICallbackOrder); if (processors.sceneProcessors != null) @@ -524,6 +540,27 @@ internal static void OnBuildPreProcess(BuildReport report) profile.SerializePlayerSettings(); } + [RequiredByNativeCode] + internal static void OnBuildPreProcessWithContext(BuildCallbackContext context) + { + if (processors.buildPreprocessorsWithContext != null) + { + foreach (var processor in processors.buildPreprocessorsWithContext) + { + try + { + processor.OnPreprocessBuild(context); + } + catch (Exception e) + { + Debug.LogException(e); + if (context.Report != null && ((context.Report.summary.options & BuildOptions.StrictMode) != 0 || (context.Report.summary.assetBundleOptions & BuildAssetBundleOptions.StrictMode) != 0)) + return; + } + } + } + } + [RequiredByNativeCode] internal static void OnSceneProcess(UnityEngine.SceneManagement.Scene scene, BuildReport report) { @@ -588,6 +625,27 @@ internal static void OnBuildPostProcess(BuildReport report) #pragma warning restore 618 } + [RequiredByNativeCode] + internal static void OnBuildPostProcessWithContext(BuildCallbackContext context) + { + if (processors.buildPostprocessorsWithContext != null) + { + foreach (var processor in processors.buildPostprocessorsWithContext) + { + try + { + processor.OnPostprocessBuild(context); + } + catch (Exception e) + { + Debug.LogException(e); + if (context.Report != null && ((context.Report.summary.options & BuildOptions.StrictMode) != 0 || (context.Report.summary.assetBundleOptions & BuildAssetBundleOptions.StrictMode) != 0)) + return; + } + } + } + } + // Some platforms like Desktop, instead of launching the app via C#, perform their launch in C++ // See BuildPlayer.cpp LaunchPlayerIfSupported, which calls native LaunchApplication @@ -752,7 +810,9 @@ internal static void CleanupBuildCallbacks() processors.buildPostprocessors = null; processors.sceneProcessors = null; processors.buildPreprocessorsWithReport = null; + processors.buildPreprocessorsWithContext = null; processors.buildPostprocessorsWithReport = null; + processors.buildPostprocessorsWithContext = null; processors.sceneProcessorsWithReport = null; processors.filterBuildAssembliesProcessor = null; processors.unityLinkerProcessors = null; diff --git a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs index 81046899c4..918aaf2620 100644 --- a/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs +++ b/Editor/Mono/BuildPipeline/Il2Cpp/IL2CPPUtils.cs @@ -53,7 +53,6 @@ namespace UnityEditorInternal internal static class SysrootManager { private static Dictionary _knownSysroots = null; - private static Dictionary _archMap = null; private static string _hostPlatform = null; private static string _hostArch = null; @@ -71,25 +70,14 @@ private static string MakeKey(string targetPlatform, string targetArch) [InitializeOnLoadMethod] public static void Initialize() { - CreateArchMapping(); RegisterSysroots(); } - private static void CreateArchMapping() - { - _archMap = new Dictionary(); - _archMap.Add("amd64", "x86_64"); - _archMap.Add("i686", "x86"); - } - - private static string MapArch(string arch) - { - string mapped; - if (_archMap.TryGetValue(arch.ToLower(), out mapped)) - return mapped; - return arch.ToLower(); - } - + /// + /// Registers Sysroot and Toolchain packages to the Editor. Could occur in following two scenarios: + /// 1. Editor Start-up + /// 2. Adding a sysroot/toolchain package through UPM. + /// private static void RegisterSysroots() { _knownSysroots = new Dictionary(); @@ -105,46 +93,109 @@ private static void RegisterSysroots() } } - private static bool GetTargetPlatformAndArchFromBuildTarget(BuildTarget target, out string targetPlatform, out string targetArch) + /// + /// Returns target platform given build target + /// Note: At sysroot/toolchain package level. Desktop, Embedded and Linux Server is identified as Linux + /// + /// + /// + /// + /// + /// + private static bool GetTargetPlatform(BuildTarget target, out string targetPlatform) { switch (target) { case BuildTarget.StandaloneLinux64: case BuildTarget.LinuxHeadlessSimulation: + case BuildTarget.EmbeddedLinux: targetPlatform = "linux"; - targetArch = "x86_64"; return true; - case BuildTarget.WebGL: - targetPlatform = "webgl"; - targetArch = ""; + } + + targetPlatform = null; + return false; + } + + /// + /// Public method to retrieve target architecture as a string given OSArchitecture enum + /// + /// + /// + /// + public static bool GetTargetArchName(OSArchitecture targetArch, out string arch) + { + switch (targetArch) + { + case OSArchitecture.ARM64: + arch = "arm64"; return true; - case BuildTarget.EmbeddedLinux: - targetPlatform = "embeddedlinux"; - targetArch = ""; + case OSArchitecture.x64: + arch = "x86_64"; return true; } - targetPlatform = null; - targetArch = null; + arch = null; return false; } - private static void GetPosixPlatformAndArch() + /// + /// Given Build Target and target architecture, returns installed toolchain for the build target + /// + /// + /// + /// + public static Sysroot FindSysrootPackage(BuildTarget target, OSArchitecture arch) + { + Sysroot sysrootPackage = null; + string targetPlatform = ""; + string targetArch = ""; + + if (!GetTargetPlatform(target, out targetPlatform) || !GetTargetArchName(arch, out targetArch)) + return null; + + if (!_knownSysroots.TryGetValue(MakeKey(String.Empty, String.Empty, targetPlatform, targetArch), out sysrootPackage)) + return null; + + if (!sysrootPackage.Initialize()) + { + Debug.Log($"Failed to initialize sysroot {sysrootPackage.Name}"); + return null; + } + + return sysrootPackage; + } + + /// + /// Given Build Target, returns installed toolchain for the build target + /// + /// + /// Returns toolchain package installed/registered for build target + public static Sysroot FindToolchainPackage(BuildTarget target) { - var p = new Process(); - p.StartInfo.FileName = "uname"; - p.StartInfo.Arguments = "-s -m"; - p.StartInfo.RedirectStandardError = true; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.UseShellExecute = false; - p.Start(); - var parts = p.StandardOutput.ReadToEnd().Split(new char[] { ' ', '\r', '\n' }); - p.WaitForExit(); - if (parts.Length > 1) + Sysroot toolchainPackage = null; + string targetPlatform = ""; + + // Handling unsupported host platforms + if (!GetHostPlatformAndArch() || !GetTargetPlatform(target, out targetPlatform)) + return null; + + foreach (Sysroot package in _knownSysroots.Values) { - _hostPlatform = parts[0].ToLower(); - _hostArch = MapArch(parts[1]); + if (package.HostPlatform == _hostPlatform && package.HostArch == _hostArch && package.TargetPlatform == targetPlatform) + { + toolchainPackage = package; + break; + } } + + if (toolchainPackage != null && !toolchainPackage.Initialize()) + { + Debug.Log($"Failed to initialize toolchain {toolchainPackage.Name}"); + return null; + } + + return toolchainPackage; } private static string AllowEnvironmentOverride(string origValue, string envVar) @@ -158,16 +209,20 @@ private static bool GetHostPlatformAndArch() if (_hostPlatform != null && _hostArch != null) return true; - switch (Environment.OSVersion.Platform) + _hostPlatform = (true) switch { - case PlatformID.Win32NT: - _hostPlatform = "windows"; - _hostArch = MapArch(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")); - break; - case PlatformID.Unix: - GetPosixPlatformAndArch(); - break; - } + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Windows) => "windows", + _ when RuntimeInformation.IsOSPlatform(OSPlatform.Linux) => "linux", + _ when RuntimeInformation.IsOSPlatform(OSPlatform.OSX) => "macos", + _ => "Unknown" + }; + + _hostArch = RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => "x86_64", + Architecture.Arm64 => "arm64", + _ => RuntimeInformation.OSArchitecture.ToString() + }; _hostPlatform = AllowEnvironmentOverride(_hostPlatform, "UNITY_SYSROOT_HOST_PLATFORM"); _hostArch = AllowEnvironmentOverride(_hostArch, "UNITY_SYSROOT_HOST_ARCH"); @@ -175,45 +230,47 @@ private static bool GetHostPlatformAndArch() return _hostPlatform != null && _hostArch != null; } - public static Sysroot FindSysroot(BuildTarget target) - { - string targetPlatform, targetArch; - if (!GetTargetPlatformAndArchFromBuildTarget(target, out targetPlatform, out targetArch)) - return null; - - return FindSysroot(targetPlatform, targetArch); - } - - private static Sysroot FindSysroot(string targetPlatform, string targetArch) + /// + /// Public method to retrieve host platform and its architecture + /// + /// + public static string GetHostPlatformAndArchitecture() { - if (!GetHostPlatformAndArch()) - return null; - - Sysroot sysroot; - if (!_knownSysroots.TryGetValue(MakeKey(targetPlatform, targetArch), out sysroot)) - return null; + if (_hostPlatform == null || _hostArch == null) + GetHostPlatformAndArch(); - if (!sysroot.Initialize()) + switch (_hostPlatform) { - UnityEngine.Debug.Log($"Failed to initialize sysroot {sysroot.Name}"); - return null; + case "macos": + return $"macos-{_hostArch}"; + case "windows": + return $"win-{_hostArch}"; + case "linux": + return $"linux-{_hostArch}"; + default: + return $"{_hostPlatform}-{_hostArch}"; } - - return sysroot; } - public static string HostTargetTuple(BuildTarget buildTarget) + /// + /// Given Build Target and target architecture returns host target tuple in following format + /// [HOST PLATFORM]-[HOST ARCH]-[TARGET PLATFORM]-[TARGET ARCH] + /// + /// + /// + /// Host target tuple in "[HOST PLATFORM]-[HOST ARCH]-[TARGET PLATFORM]-[TARGET ARCH]" format + public static string HostTargetTuple(BuildTarget buildTarget, OSArchitecture arch) { if (GetHostPlatformAndArch()) { string targetPlatform; string targetArch; - if (GetTargetPlatformAndArchFromBuildTarget(buildTarget, out targetPlatform, out targetArch)) + if (GetTargetPlatform(buildTarget, out targetPlatform) && GetTargetArchName(arch, out targetArch)) { string host; switch (_hostPlatform) { - case "darwin": + case "macos": host = $"macos-{_hostArch}"; break; case "windows": @@ -230,6 +287,11 @@ public static string HostTargetTuple(BuildTarget buildTarget) return null; } + /// + /// Public method to enumerate through all the toolchain and sysroot packages + /// currently installed in the unity project + /// + /// public static IEnumerable EnumerateSysroots() { foreach (Sysroot sysroot in _knownSysroots.Values) diff --git a/Editor/Mono/BuildPlayerWindow.cs b/Editor/Mono/BuildPlayerWindow.cs index e4cd1eaf4b..a9322fc1af 100644 --- a/Editor/Mono/BuildPlayerWindow.cs +++ b/Editor/Mono/BuildPlayerWindow.cs @@ -55,7 +55,6 @@ class Styles public GUIContent eula = EditorGUIUtility.TrTextContent("Eula"); public string addToYourPro = L10n.Tr("Add {0} to your Unity Pro license"); public GUIContent installInBuildFolder = EditorGUIUtility.TrTextContent("Install into source code 'build' folder", "Install into source checkout 'build' folder, for debugging with source code"); - public GUIContent installInBuildFolderHelp = EditorGUIUtility.TrIconContent("_Help", "Open documentation about source code building and debugging"); public Texture2D activePlatformIcon = EditorGUIUtility.IconContent("BuildSettings.SelectedIcon").image as Texture2D; @@ -192,7 +191,7 @@ static bool BuildPlayerAndRunEnabled() [UsedImplicitly, RequiredByNativeCode] static void BuildPlayerAndRun() { - var buildTarget = EditorUserBuildSettingsUtils.CalculateSelectedBuildTarget(); + var buildTarget = EditorUserBuildSettings.activeBuildTarget; var lastBuildLocation = EditorUserBuildSettings.GetBuildLocation(buildTarget); bool buildLocationIsValid = BuildLocationIsValid(lastBuildLocation); @@ -959,14 +958,6 @@ private static void GUIBuildButtons(IBuildWindowExtension buildWindowExtension, { GUILayout.BeginHorizontal(); EditorUserBuildSettings.installInBuildFolder = GUILayout.Toggle(EditorUserBuildSettings.installInBuildFolder, styles.installInBuildFolder, GUILayout.ExpandWidth(false)); - if (GUILayout.Button(styles.installInBuildFolderHelp, EditorStyles.iconButton)) - { - var path = Path.Combine(Unsupported.GetBaseUnityDeveloperFolder(), "Documentation/BuildDocs/view"); - if (Application.platform == RuntimePlatform.WindowsEditor) - System.Diagnostics.Process.Start(path + ".cmd"); - else - System.Diagnostics.Process.Start("/bin/bash", path); - } GUILayout.EndHorizontal(); } else diff --git a/Editor/Mono/BuildPlayerWindowBuildMethods.cs b/Editor/Mono/BuildPlayerWindowBuildMethods.cs index 199130c791..c6e73a227f 100644 --- a/Editor/Mono/BuildPlayerWindowBuildMethods.cs +++ b/Editor/Mono/BuildPlayerWindowBuildMethods.cs @@ -71,7 +71,6 @@ public static void RegisterBuildPlayerHandler(Action func) /// /// Method called by the UI when the "Build" or "Build and Run" buttons are pressed. /// - /// internal static void CallBuildMethods(bool askForBuildLocation, BuildOptions defaultBuildOptions) { EditorCompilationInterface.IsCompiling(out var isCompiling); @@ -93,7 +92,7 @@ internal static void CallBuildMethods(bool askForBuildLocation, BuildOptions def if (getBuildPlayerOptionsHandler != null) options = getBuildPlayerOptionsHandler(options); else - options = DefaultBuildMethods.GetBuildPlayerOptionsInternal(askForBuildLocation, options); + options = DefaultBuildMethods.GetBuildPlayerOptionsFromActiveBuildTarget(askForBuildLocation, options); if (buildPlayerHandler != null) buildPlayerHandler(options); @@ -244,14 +243,42 @@ internal static bool IsInstallInBuildFolderOption() } internal static BuildPlayerOptions GetBuildPlayerOptionsInternal(bool askForBuildLocation, BuildPlayerOptions defaultBuildPlayerOptions) + { + return GetBuildPlayerOptions(askForBuildLocation, defaultBuildPlayerOptions, false); + } + + /// + /// Get build player options using the active build target instead of selected target. + /// + internal static BuildPlayerOptions GetBuildPlayerOptionsFromActiveBuildTarget(bool askForBuildLocation, BuildPlayerOptions defaultBuildPlayerOptions) + { + return GetBuildPlayerOptions(askForBuildLocation, defaultBuildPlayerOptions, true); + } + + private static BuildPlayerOptions GetBuildPlayerOptions(bool askForBuildLocation, BuildPlayerOptions defaultBuildPlayerOptions, bool activeTarget) { var options = defaultBuildPlayerOptions; bool updateExistingBuild = false; - BuildTarget buildTarget = EditorUserBuildSettingsUtils.CalculateSelectedBuildTarget(); - BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; - int subtarget = EditorUserBuildSettings.GetSelectedSubtargetFor(buildTarget); + BuildTarget buildTarget; + BuildTargetGroup buildTargetGroup; + int subtarget; + + if (activeTarget) + { + // Use the active target + buildTarget = EditorUserBuildSettings.activeBuildTarget; + buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); + subtarget = EditorUserBuildSettings.GetActiveSubtargetFor(buildTarget); + } + else + { + // Use the selected target + buildTarget = EditorUserBuildSettingsUtils.CalculateSelectedBuildTarget(); + buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; + subtarget = EditorUserBuildSettings.GetSelectedSubtargetFor(buildTarget); + } options.options = BuildProfileModuleUtil.GetBuildOptions(buildTarget, buildTargetGroup, string.Empty, options.options); @@ -396,9 +423,10 @@ private static bool PickBuildLocation(BuildTargetGroup targetGroup, BuildTarget if (!Directory.Exists(check_dir)) Directory.CreateDirectory(check_dir); - // On OSX we've got replace/update dialog, for other platforms warn about deleting - // files in target folder. - if ((target == BuildTarget.iOS) && (Application.platform != RuntimePlatform.OSXEditor)) + // All files are deleted in build path when building iOS/tvOS/visionOS project with replace option on WinEditor or MacEditor, we need to + // ask the user if they want to proceed by showing a warning dialog + bool isApplePlatform = target == BuildTarget.iOS || target == BuildTarget.tvOS || target == BuildTarget.VisionOS; + if (isApplePlatform && !updateExistingBuild) if (!FolderIsEmpty(path) && !UserWantsToDeleteFiles(path)) return false; diff --git a/Editor/Mono/BuildProfile/BuildProfile.cs b/Editor/Mono/BuildProfile/BuildProfile.cs index 5fb0007d86..dc91ad4e88 100644 --- a/Editor/Mono/BuildProfile/BuildProfile.cs +++ b/Editor/Mono/BuildProfile/BuildProfile.cs @@ -135,7 +135,33 @@ internal bool hasScriptingDefines public string[] scriptingDefines { get => m_ScriptingDefines; - set => m_ScriptingDefines = value; + set => SetAndApplyScriptingDefines(value); + } + + /// + /// Internal method for setting Scripting Defines. Cleans, applies and reloads. + /// + [VisibleToOtherModules("UnityEditor.BuildProfileModule")] + internal void SetAndApplyScriptingDefines(string[] defines) + { + var cleanedValue = BuildProfileModuleUtil.RemoveInvalidScriptingDefines(defines); + + if (!ArrayUtility.ArrayEquals(m_ScriptingDefines, cleanedValue)) + { + m_ScriptingDefines = cleanedValue; + EditorUtility.SetDirty(this); + } + + if (IsActiveBuildProfileOrPlatform()) + { + var lastCompiled = BuildProfileContext.instance.cachedEditorScriptingDefines; + + // Reload when actual changes happen, after cleaning has occurred + if (!ArrayUtility.ArrayEquals(m_ScriptingDefines, lastCompiled)) + { + BuildProfileModuleUtil.RequestScriptCompilation(this); + } + } } [VisibleToOtherModules] @@ -230,23 +256,6 @@ internal string GetLastRunnableBuildPathKey() return BuildProfileModuleUtil.GetLastRunnableBuildKeyFromAssetPath(assetPath, key); } - /// - /// Duplicate the build profile. Note this does not create a new asset. - /// - [VisibleToOtherModules] - internal BuildProfile Duplicate() - { - var duplicatedProfile = Instantiate(this); - - if (graphicsSettings != null) - duplicatedProfile.graphicsSettings = Instantiate(graphicsSettings); - - if (qualitySettings != null) - duplicatedProfile.qualitySettings = Instantiate(qualitySettings); - - return duplicatedProfile; - } - [VisibleToOtherModules] internal void ResetToGlobalQualitySettingsValues() { @@ -308,6 +317,7 @@ void OnEnable() // On disk changes invoke OnEnable, // Check against the last observed editor defines. string[] lastCompiledDefines = BuildProfileContext.instance.cachedEditorScriptingDefines; + m_ScriptingDefines = BuildProfileModuleUtil.RemoveInvalidScriptingDefines(m_ScriptingDefines); if (ArrayUtility.ArrayEquals(m_ScriptingDefines, lastCompiledDefines)) { return; @@ -349,7 +359,14 @@ void TryLoadQualitySettings() void OnDisable() { if (IsActiveBuildProfileOrPlatform()) + { + m_ScriptingDefines = BuildProfileModuleUtil.RemoveInvalidScriptingDefines(m_ScriptingDefines); EditorUserBuildSettings.SetActiveProfileScriptingDefines(m_ScriptingDefines); + if (!overrideGlobalScenes) + EditorUserBuildSettings.SetCachedActiveProfileScenes(EditorBuildSettings.globalScenes); + else + EditorUserBuildSettings.SetCachedActiveProfileScenes(scenes); + } var playerSettingsDirty = EditorUtility.IsDirty(m_PlayerSettings); if (playerSettingsDirty) diff --git a/Editor/Mono/BuildProfile/BuildProfileContext.cs b/Editor/Mono/BuildProfile/BuildProfileContext.cs index acc0d70ce6..c96b5d253f 100644 --- a/Editor/Mono/BuildProfile/BuildProfileContext.cs +++ b/Editor/Mono/BuildProfile/BuildProfileContext.cs @@ -68,7 +68,12 @@ internal static BuildProfile activeProfile { get { - return EditorUserBuildSettings.activeBuildProfile; + var profile = EditorUserBuildSettings.activeBuildProfile; + + if (profile == null || !profile) + return null; + + return profile; } set @@ -107,6 +112,7 @@ internal static BuildProfile activeProfile value.UpdateGlobalManagerPlayerSettings(); activeProfileChanged?.Invoke(prev, value); EditorGraphicsSettings.activeProfileHasGraphicsSettings = ActiveProfileHasGraphicsSettings(); + value.scriptingDefines = BuildProfileModuleUtil.RemoveInvalidScriptingDefines(value.scriptingDefines); BuildProfileModuleUtil.RequestScriptCompilation(value); } } @@ -171,19 +177,34 @@ static void OnActiveProfileChangedForSettingExtension(BuildProfile previous, Bui settingsExtension?.OnActiveProfileChanged(previous, newProfile); } - internal static void HandlePendingChangesBeforeEnterPlaymode() + internal static void HandleScriptingDefinesChanged() { - if (!EditorUserBuildSettings.isBuildProfileAvailable) - return; - var defines = BuildDefines.GetBuildProfileScriptDefines(); + if (!ArrayUtility.ArrayEquals(defines, instance.cachedEditorScriptingDefines)) { - instance.cachedEditorScriptingDefines = defines; - PlayerSettings.RecompileScripts("Build profile has been modified."); + bool isAutomatedEnvironment = Application.isBatchMode || BuildPipeline.isBuildingPlayer; + + if (isAutomatedEnvironment || EditorUtility.DisplayDialog(L10n.Tr("Active Build Profile Scripting Defines Have Been Modified"), L10n.Tr("Do you want to apply changes now?"), L10n.Tr("Apply"), L10n.Tr("Revert"))) + { + activeProfile.scriptingDefines = BuildProfileModuleUtil.RemoveInvalidScriptingDefines(defines); + BuildProfileModuleUtil.RequestScriptCompilation(activeProfile); + } + else + { + activeProfile.scriptingDefines = instance.cachedEditorScriptingDefines; + } } } + internal static void HandlePendingChangesBeforeEnterPlaymode() + { + if (!EditorUserBuildSettings.isBuildProfileAvailable) + return; + + HandleScriptingDefinesChanged(); + } + /// /// Callback invoked when the active build profile has been changed to a new value. /// @@ -525,19 +546,17 @@ void OnEnable() EditorGraphicsSettings.activeProfileHasGraphicsSettings = ActiveProfileHasGraphicsSettings(); - var buildProfile = activeProfile; + if (activeProfile != null) + return; + + var buildProfile = GetForClassicPlatform(EditorUserBuildSettings.activePlatformGuid); + // profile can be null if we're in the middle of creating classic profiles if (buildProfile == null) - { - buildProfile = GetForClassicPlatform(EditorUserBuildSettings.activePlatformGuid); - - // profile can be null if we're in the middle of creating classic profiles - if (buildProfile == null) - return; + return; - // We only copy EditorUserBuildSettings into the build profile for classic platforms as we don't want to modify actual user assets - EditorUserBuildSettings.CopyToBuildProfile(buildProfile); - } + // We only copy EditorUserBuildSettings into the build profile for classic platforms as we don't want to modify actual user assets + EditorUserBuildSettings.CopyToBuildProfile(buildProfile); var extension = ModuleManager.GetBuildProfileExtension(buildProfile.platformGuid); if (extension != null) @@ -660,9 +679,9 @@ static void CreateOrLoad() { s_Instance = buildProfileContext[0] as BuildProfileContext; if (s_Instance == null) - Debug.LogError("BuildProfileContext asset exists but could not be loaded."); + Debug.LogWarning("BuildProfileContext asset exists but could not be loaded. Creating a new one."); } - else if (s_Instance == null) + if (s_Instance == null) { s_Instance = CreateInstance(); s_Instance.hideFlags = HideFlags.DontSave; @@ -702,6 +721,12 @@ static void SetActiveOrClassicProfileRawPlatformSetting(string settingName, stri } } + [RequiredByNativeCode, UsedImplicitly] + static void SetProfileRawPlatformSetting(BuildProfile profile, string settingName, string settingValue) + { + profile?.platformBuildProfile?.SetRawPlatformSetting(settingName, settingValue); + } + [RequiredByNativeCode, UsedImplicitly] static void EnsureInitialized() { diff --git a/Editor/Mono/BuildProfile/BuildProfileCreate.cs b/Editor/Mono/BuildProfile/BuildProfileCreate.cs index f8f61d9efe..923ef8c986 100644 --- a/Editor/Mono/BuildProfile/BuildProfileCreate.cs +++ b/Editor/Mono/BuildProfile/BuildProfileCreate.cs @@ -108,9 +108,10 @@ static void ValidatePlatform(GUID platformGuid) /// The path to the build profile to be created. static void ValidateFileNameLength(string assetPath) { + var byteCount = System.Text.Encoding.UTF8.GetByteCount(Path.GetFileName(assetPath)); // File name length is limited by the asset database - if (Path.GetFileName(assetPath).Length > BuildProfileModuleUtil.k_MaxAssetFileNameLength) - throw new ArgumentException($"Build profile name is too long ({Path.GetFileName(assetPath).Length}) - max supported is {BuildProfileModuleUtil.k_MaxAssetFileNameLength}"); + if (byteCount > BuildProfileModuleUtil.k_MaxAssetFileNameLength) + throw new ArgumentException($"Build profile name is too long ({byteCount}) - max supported is {BuildProfileModuleUtil.k_MaxAssetFileNameLength} bytes."); } internal void NotifyBuildProfileExtensionOfCreation(int preconfiguredSettingsVariant) diff --git a/Editor/Mono/BuildProfile/BuildProfileGraphicsSettingsEditor.cs b/Editor/Mono/BuildProfile/BuildProfileGraphicsSettingsEditor.cs index e646ba5756..16e0acd398 100644 --- a/Editor/Mono/BuildProfile/BuildProfileGraphicsSettingsEditor.cs +++ b/Editor/Mono/BuildProfile/BuildProfileGraphicsSettingsEditor.cs @@ -24,6 +24,20 @@ class BuildProfileGraphicsSettingsEditor : Editor ShaderBuildSettingsUI m_ShaderBuildSettingsUI = new(); + void OnDisable() + { + if (!m_ShaderBuildSettingsUI.HasUnsavedChanges) + return; + + string profileName = null; + var assetPath = AssetDatabase.GetAssetPath(target); + var parentProfile = AssetDatabase.LoadMainAssetAtPath(assetPath) as BuildProfile; + if (parentProfile != null) + profileName = parentProfile.name; + + m_ShaderBuildSettingsUI.HandleUnsavedChangesDialog(profileName); + } + public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); @@ -60,11 +74,8 @@ void BindEnumFieldWithFadeGroup(VisualElement content, string id, Action buttonC var enumModeGroup = content.MandatoryQ($"{id}ModesGroup"); var enumModeProperty = serializedObject.FindProperty($"m_{id}Stripping"); - static bool IsModesGroupVisible(StrippingModes mode) => mode == StrippingModes.Custom; - UIElementsEditorUtility.SetVisibility(enumModeGroup, IsModesGroupVisible((StrippingModes)enumModeProperty.intValue)); - var lightmapModesUpdate = UIElementsEditorUtility.BindSerializedProperty(enumMode, enumModeProperty, - mode => UIElementsEditorUtility.SetVisibility(enumModeGroup, IsModesGroupVisible(mode))); - lightmapModesUpdate?.Invoke(); + UIElementsEditorUtility.BindSerializedProperty(enumMode, enumModeProperty, + mode => UIElementsEditorUtility.SetVisibility(enumModeGroup, mode == StrippingModes.Custom)); content.MandatoryQ