using UnityEditor; using UnityEditor.UI; using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityEditorInternal; using UnityEngine.UI; namespace Coffee.UIExtensions { [CustomEditor(typeof(UIParticle))] [CanEditMultipleObjects] internal class UIParticleEditor : GraphicEditor { //################################ // Constant or Static Members. //################################ private static readonly GUIContent s_ContentRenderingOrder = new GUIContent("Rendering Order"); private static readonly GUIContent s_ContentRefresh = new GUIContent("Refresh"); private static readonly GUIContent s_ContentFix = new GUIContent("Fix"); private static readonly GUIContent s_ContentMaterial = new GUIContent("Material"); private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material"); private static readonly GUIContent s_Content3D = new GUIContent("3D"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); private SerializedProperty _spMaskable; private SerializedProperty _spScale; private SerializedProperty _spIgnoreCanvasScaler; private SerializedProperty _spAnimatableProperties; private SerializedProperty _spShrinkByMaterial; private ReorderableList _ro; private bool _xyzMode; private bool _showMaterials; private static readonly List s_MaskablePropertyNames = new List { "_Stencil", "_StencilComp", "_StencilOp", "_StencilWriteMask", "_StencilReadMask", "_ColorMask", }; //################################ // Public/Protected Members. //################################ /// /// This function is called when the object becomes enabled and active. /// protected override void OnEnable() { base.OnEnable(); _spMaskable = serializedObject.FindProperty("m_Maskable"); _spScale = serializedObject.FindProperty("m_Scale3D"); _spIgnoreCanvasScaler = serializedObject.FindProperty("m_IgnoreCanvasScaler"); _spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); _spShrinkByMaterial = serializedObject.FindProperty("m_ShrinkByMaterial"); _showMaterials = EditorPrefs.GetBool("Coffee.UIExtensions.UIParticleEditor._showMaterials", true); var sp = serializedObject.FindProperty("m_Particles"); _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true); _ro.elementHeight = EditorGUIUtility.singleLineHeight * 3 + 4; _ro.elementHeightCallback = _ => _showMaterials ? 3 * (EditorGUIUtility.singleLineHeight + 2) : EditorGUIUtility.singleLineHeight + 2; _ro.drawElementCallback = (rect, index, active, focused) => { EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues); rect.y += 1; rect.height = EditorGUIUtility.singleLineHeight; var p = sp.GetArrayElementAtIndex(index); EditorGUI.ObjectField(rect, p, GUIContent.none); if (!_showMaterials) return; rect.x += 15; rect.width -= 15; var ps = p.objectReferenceValue as ParticleSystem; var materials = ps ? new SerializedObject(ps.GetComponent()).FindProperty("m_Materials") : null; rect.y += rect.height + 1; MaterialField(rect, s_ContentMaterial, materials, 0); rect.y += rect.height + 1; MaterialField(rect, s_ContentTrailMaterial, materials, 1); EditorGUI.EndDisabledGroup(); if (materials != null) { materials.serializedObject.ApplyModifiedProperties(); } }; _ro.drawHeaderCallback += rect => { #if !UNITY_2019_3_OR_NEWER rect.y -= 1; #endif EditorGUI.LabelField(new Rect(rect.x, rect.y, 150, rect.height), s_ContentRenderingOrder); var content = EditorGUIUtility.IconContent(_showMaterials ? "VisibilityOn" : "VisibilityOff"); _showMaterials = GUI.Toggle(new Rect(rect.width - 55, rect.y, 24, 20), _showMaterials, content, EditorStyles.label); if (GUI.Button(new Rect(rect.width - 35, rect.y, 60, rect.height), s_ContentRefresh, EditorStyles.miniButton)) { foreach (UIParticle t in targets) { t.RefreshParticles(); } } }; } private static void MaterialField(Rect rect, GUIContent label, SerializedProperty sp, int index) { if (sp == null || sp.arraySize <= index) { EditorGUI.BeginDisabledGroup(true); EditorGUI.ObjectField(rect, label, null, typeof(Material), true); EditorGUI.EndDisabledGroup(); } else { EditorGUI.PropertyField(rect, sp.GetArrayElementAtIndex(index), label); } } /// /// Implement this function to make a custom inspector. /// public override void OnInspectorGUI() { var current = target as UIParticle; if (current == null) return; serializedObject.Update(); // Maskable EditorGUILayout.PropertyField(_spMaskable); // IgnoreCanvasScaler using (var ccs = new EditorGUI.ChangeCheckScope()) { EditorGUILayout.PropertyField(_spIgnoreCanvasScaler); if (ccs.changed) { foreach (UIParticle p in targets) { p.ignoreCanvasScaler = _spIgnoreCanvasScaler.boolValue; } } } // Scale _xyzMode = DrawFloatOrVector3Field(_spScale, _xyzMode); // AnimatableProperties var mats = current.particles .Where(x => x) .Select(x => x.GetComponent().sharedMaterial) .Where(x => x) .ToArray(); // Animated properties EditorGUI.BeginChangeCheck(); AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, mats); if (EditorGUI.EndChangeCheck()) { foreach (UIParticle t in targets) t.SetMaterialDirty(); } // ShrinkByMaterial EditorGUILayout.PropertyField(_spShrinkByMaterial); // Target ParticleSystems. _ro.DoLayoutList(); serializedObject.ApplyModifiedProperties(); // Does the shader support UI masks? if (current.maskable && current.GetComponentInParent()) { foreach (var mat in current.materials) { if (!mat || !mat.shader) continue; var shader = mat.shader; foreach (var propName in s_MaskablePropertyNames) { if (mat.HasProperty(propName)) continue; EditorGUILayout.HelpBox(string.Format("Shader '{0}' doesn't have '{1}' property. This graphic cannot be masked.", shader.name, propName), MessageType.Warning); break; } } } // Does the shader support UI masks? if (FixButton(current.m_IsTrail, "This UIParticle component should be removed. The UIParticle for trails is no longer needed.")) { DestroyUIParticle(current); return; } } void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false) { if (!p || ignoreCurrent && target == p) return; var cr = p.canvasRenderer; DestroyImmediate(p); DestroyImmediate(cr); #if UNITY_2018_3_OR_NEWER var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); if (stage != null && stage.scene.isLoaded) { #if UNITY_2020_1_OR_NEWER string prefabAssetPath = stage.assetPath; #else string prefabAssetPath = stage.prefabAssetPath; #endif PrefabUtility.SaveAsPrefabAsset(stage.prefabContentsRoot, prefabAssetPath); } #endif } bool FixButton(bool show, string text) { if (!show) return false; using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true))) { EditorGUILayout.HelpBox(text, MessageType.Warning, true); using (new EditorGUILayout.VerticalScope()) { return GUILayout.Button(s_ContentFix, GUILayout.Width(30)); } } } private static bool DrawFloatOrVector3Field(SerializedProperty sp, bool showXyz) { var x = sp.FindPropertyRelative("x"); var y = sp.FindPropertyRelative("y"); var z = sp.FindPropertyRelative("z"); showXyz |= !Mathf.Approximately(x.floatValue, y.floatValue) || !Mathf.Approximately(y.floatValue, z.floatValue) || y.hasMultipleDifferentValues || z.hasMultipleDifferentValues; EditorGUILayout.BeginHorizontal(); if (showXyz) { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(sp); if (EditorGUI.EndChangeCheck()) { x.floatValue = Mathf.Max(0.001f, x.floatValue); y.floatValue = Mathf.Max(0.001f, y.floatValue); z.floatValue = Mathf.Max(0.001f, z.floatValue); } } else { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(x, s_ContentScale); if (EditorGUI.EndChangeCheck()) { x.floatValue = Mathf.Max(0.001f, x.floatValue); y.floatValue = Mathf.Max(0.001f, x.floatValue); z.floatValue = Mathf.Max(0.001f, x.floatValue); } } EditorGUI.BeginChangeCheck(); showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30)); if (EditorGUI.EndChangeCheck() && !showXyz) z.floatValue = y.floatValue = x.floatValue; EditorGUILayout.EndHorizontal(); return showXyz; } } }