using UnityEditor; using UnityEditor.UI; using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityEditor.IMGUI.Controls; using System; using System.Reflection; namespace Coffee.UIExtensions { [CustomEditor (typeof (UIParticle))] [CanEditMultipleObjects] public class UIParticleEditor : GraphicEditor { //################################ // Constant or Static Members. //################################ static readonly GUIContent s_ContentParticleMaterial = new GUIContent ("Particle Material", "The material for rendering particles"); static readonly GUIContent s_ContentTrailMaterial = new GUIContent ("Trail Material", "The material for rendering particle trails"); static readonly List s_ParticleSystems = new List (); static readonly Matrix4x4 s_ArcHandleOffsetMatrix = Matrix4x4.TRS (Vector3.zero, Quaternion.AngleAxis (90f, Vector3.right) * Quaternion.AngleAxis (90f, Vector3.up), Vector3.one); static readonly Dictionary s_InternalMethods = new Dictionary (); static readonly Color s_GizmoColor = new Color (1f, 0.7f, 0.7f, 0.9f); static readonly Color s_ShapeGizmoThicknessTint = new Color (0.7f, 0.7f, 0.7f, 1.0f); static Material s_Material; //################################ // Public/Protected Members. //################################ /// /// This function is called when the object becomes enabled and active. /// protected override void OnEnable () { base.OnEnable (); _spParticleSystem = serializedObject.FindProperty ("m_ParticleSystem"); _spTrailParticle = serializedObject.FindProperty ("m_TrailParticle"); _spScale = serializedObject.FindProperty ("m_Scale"); _spIgnoreParent = serializedObject.FindProperty ("m_IgnoreParent"); if (!s_Material) { s_Material = Call (typeof (Material), "GetDefaultMaterial"); } _particles = targets.Cast ().ToArray (); } /// /// Implement this function to make a custom inspector. /// public override void OnInspectorGUI () { serializedObject.Update (); EditorGUILayout.PropertyField (_spParticleSystem); EditorGUI.indentLevel++; var ps = _spParticleSystem.objectReferenceValue as ParticleSystem; if (ps) { var pr = ps.GetComponent (); var sp = new SerializedObject (pr).FindProperty ("m_Materials"); EditorGUILayout.PropertyField (sp.GetArrayElementAtIndex (0), s_ContentParticleMaterial); EditorGUILayout.PropertyField (sp.GetArrayElementAtIndex (1), s_ContentTrailMaterial); sp.serializedObject.ApplyModifiedProperties (); if (!Application.isPlaying && pr.enabled) { EditorGUILayout.HelpBox ("ParticleSystemRenderer will be disable on playing.", MessageType.Info); } } EditorGUI.indentLevel--; EditorGUI.BeginDisabledGroup (true); EditorGUILayout.PropertyField (_spTrailParticle); EditorGUI.EndDisabledGroup (); var current = target as UIParticle; EditorGUILayout.PropertyField (_spIgnoreParent); EditorGUI.BeginDisabledGroup (!current.isRoot); EditorGUILayout.PropertyField (_spScale); EditorGUI.EndDisabledGroup (); current.GetComponentsInChildren (true, s_ParticleSystems); if (s_ParticleSystems.Any (x => x.GetComponent () == null)) { GUILayout.BeginHorizontal (); EditorGUILayout.HelpBox ("There are child ParticleSystems that does not have a UIParticle component.\nAdd UIParticle component to them.", MessageType.Warning); GUILayout.BeginVertical (); if (GUILayout.Button ("Fix")) { foreach (var p in s_ParticleSystems.Where (x => !x.GetComponent ())) { p.gameObject.AddComponent (); } } GUILayout.EndVertical (); GUILayout.EndHorizontal (); } s_ParticleSystems.Clear (); serializedObject.ApplyModifiedProperties (); } //################################ // Private Members. //################################ SerializedProperty _spParticleSystem; SerializedProperty _spTrailParticle; SerializedProperty _spScale; SerializedProperty _spIgnoreParent; UIParticle [] _particles; ArcHandle _arcHandle = new ArcHandle (); BoxBoundsHandle _boxBoundsHandle = new BoxBoundsHandle (); SphereBoundsHandle _sphereBoundsHandle = new SphereBoundsHandle (); Mesh _spriteMesh; static T Call (Type type, string method, params object [] args) { MethodInfo mi; if (!s_InternalMethods.TryGetValue (method, out mi)) { mi = type.GetMethod (method, BindingFlags.Static | BindingFlags.NonPublic); s_InternalMethods.Add (method, mi); } return (T)mi.Invoke (null, args); } void OnSceneGUI () { Color origCol = Handles.color; Handles.color = s_GizmoColor; Matrix4x4 orgMatrix = Handles.matrix; EditorGUI.BeginChangeCheck (); foreach (UIParticle uip in _particles) { ParticleSystem ps = uip.cachedParticleSystem; if (!ps) { continue; } var shapeModule = ps.shape; var mainModule = ps.main; ParticleSystemShapeType type = shapeModule.shapeType; Matrix4x4 transformMatrix = new Matrix4x4 (); if (mainModule.scalingMode == ParticleSystemScalingMode.Local) { transformMatrix.SetTRS (ps.transform.position, ps.transform.rotation, ps.transform.localScale); } else if (mainModule.scalingMode == ParticleSystemScalingMode.Hierarchy) { transformMatrix = ps.transform.localToWorldMatrix; } else { transformMatrix.SetTRS (ps.transform.position, ps.transform.rotation, ps.transform.lossyScale); } bool isBox = (type == ParticleSystemShapeType.Box || type == ParticleSystemShapeType.BoxShell || type == ParticleSystemShapeType.BoxEdge || type == ParticleSystemShapeType.Rectangle); Vector3 emitterScale = isBox ? Vector3.one : shapeModule.scale; Matrix4x4 emitterMatrix = Matrix4x4.TRS (shapeModule.position, Quaternion.Euler (shapeModule.rotation), emitterScale); transformMatrix *= emitterMatrix; Handles.matrix = transformMatrix; if(uip.canvas.renderMode == RenderMode.ScreenSpaceOverlay || ps.main.scalingMode == ParticleSystemScalingMode.Hierarchy) { Handles.matrix = Handles.matrix * Matrix4x4.Scale (Vector3.one * uip.scale); } else { Handles.matrix = Handles.matrix * Matrix4x4.Scale (uip.canvas.rootCanvas.transform.localScale * uip.scale); } if (type == ParticleSystemShapeType.Sphere) { // Thickness Handles.color *= s_ShapeGizmoThicknessTint; EditorGUI.BeginChangeCheck (); //float radiusThickness = Handles.DoSimpleRadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius * (1.0f - shapeModule.radiusThickness), false, shapeModule.arc); float radiusThickness = Handles.RadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius * (1.0f - shapeModule.radiusThickness), false); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Sphere Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (radiusThickness / shapeModule.radius); } // Sphere Handles.color = s_GizmoColor; EditorGUI.BeginChangeCheck (); //float radius = Handles.DoSimpleRadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius, false, shapeModule.arc); float radius = Handles.RadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius, false); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Sphere Handle Change"); shapeModule.radius = radius; } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (Vector3.zero, Quaternion.identity, Vector3.one * shapeModule.radius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_SphereMesh, false, s_SphereTextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.Circle) { // Thickness EditorGUI.BeginChangeCheck (); _arcHandle.angle = shapeModule.arc; _arcHandle.radius = shapeModule.radius * (1.0f - shapeModule.radiusThickness); _arcHandle.SetColorWithRadiusHandle (s_ShapeGizmoThicknessTint, 0f); _arcHandle.angleHandleColor = Color.clear; using (new Handles.DrawingScope (Handles.matrix * s_ArcHandleOffsetMatrix)) _arcHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Circle Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (_arcHandle.radius / shapeModule.radius); } // Circle EditorGUI.BeginChangeCheck (); _arcHandle.radius = shapeModule.radius; _arcHandle.SetColorWithRadiusHandle (Color.white, 0f); using (new Handles.DrawingScope (Handles.matrix * s_ArcHandleOffsetMatrix)) _arcHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Circle Handle Change"); shapeModule.radius = _arcHandle.radius; shapeModule.arc = _arcHandle.angle; } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (Vector3.zero, Quaternion.Euler (90.0f, 0.0f, 180.0f), Vector3.one * shapeModule.radius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_CircleMesh, true, s_TextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.Hemisphere) { // Thickness Handles.color *= s_ShapeGizmoThicknessTint; EditorGUI.BeginChangeCheck (); //float radiusThickness = Handles.DoSimpleRadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius * (1.0f - shapeModule.radiusThickness), true, shapeModule.arc); //float radiusThickness = Call (typeof (Handles), "DoSimpleRadiusHandle", Quaternion.identity, Vector3.zero, shapeModule.radius * (1.0f - shapeModule.radiusThickness), true, shapeModule.arc); float radiusThickness = Handles.RadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius * (1.0f - shapeModule.radiusThickness), true); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Hemisphere Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (radiusThickness / shapeModule.radius); } // Hemisphere Handles.color = s_GizmoColor; EditorGUI.BeginChangeCheck (); //float radius = Handles.DoSimpleRadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius, true, shapeModule.arc); float radius = Handles.RadiusHandle (Quaternion.identity, Vector3.zero, shapeModule.radius, true); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Hemisphere Handle Change"); shapeModule.radius = radius; } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (Vector3.zero, Quaternion.identity, Vector3.one * shapeModule.radius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_HemisphereMesh, false, s_SphereTextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.Cone) { // Thickness Handles.color *= s_ShapeGizmoThicknessTint; EditorGUI.BeginChangeCheck (); float angleThickness = Mathf.Lerp (shapeModule.angle, 0.0f, shapeModule.radiusThickness); Vector3 radiusThicknessAngleRange = new Vector3 (shapeModule.radius * (1.0f - shapeModule.radiusThickness), angleThickness, mainModule.startSpeedMultiplier); //radiusThicknessAngleRange = Handles.ConeFrustrumHandle (Quaternion.identity, Vector3.zero, radiusThicknessAngleRange, Handles.ConeHandles.Radius); #if UNITY_2018_3_OR_NEWER radiusThicknessAngleRange = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusThicknessAngleRange, 1); #else radiusThicknessAngleRange = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusThicknessAngleRange); #endif if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Cone Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (radiusThicknessAngleRange.x / shapeModule.radius); } // Cone Handles.color = s_GizmoColor; EditorGUI.BeginChangeCheck (); Vector3 radiusAngleRange = new Vector3 (shapeModule.radius, shapeModule.angle, mainModule.startSpeedMultiplier); //radiusAngleRange = Handles.ConeFrustrumHandle (Quaternion.identity, Vector3.zero, radiusAngleRange); #if UNITY_2018_3_OR_NEWER radiusAngleRange = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusAngleRange, 7); #else radiusAngleRange = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusAngleRange); #endif if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Cone Handle Change"); shapeModule.radius = radiusAngleRange.x; shapeModule.angle = radiusAngleRange.y; mainModule.startSpeedMultiplier = radiusAngleRange.z; } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (Vector3.zero, Quaternion.Euler (90.0f, 0.0f, 180.0f), Vector3.one * shapeModule.radius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_CircleMesh, true, s_TextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.ConeVolume) { // Thickness Handles.color *= s_ShapeGizmoThicknessTint; EditorGUI.BeginChangeCheck (); float angleThickness = Mathf.Lerp (shapeModule.angle, 0.0f, shapeModule.radiusThickness); Vector3 radiusThicknessAngleLength = new Vector3 (shapeModule.radius * (1.0f - shapeModule.radiusThickness), angleThickness, shapeModule.length); //radiusThicknessAngleLength = Handles.ConeFrustrumHandle (Quaternion.identity, Vector3.zero, radiusThicknessAngleLength, Handles.ConeHandles.Radius); #if UNITY_2018_3_OR_NEWER radiusThicknessAngleLength = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusThicknessAngleLength, 1); #else radiusThicknessAngleLength = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusThicknessAngleLength); #endif if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Cone Volume Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (radiusThicknessAngleLength.x / shapeModule.radius); } // Cone Handles.color = s_GizmoColor; EditorGUI.BeginChangeCheck (); Vector3 radiusAngleLength = new Vector3 (shapeModule.radius, shapeModule.angle, shapeModule.length); //radiusAngleLength = Handles.ConeFrustrumHandle (Quaternion.identity, Vector3.zero, radiusAngleLength); #if UNITY_2018_3_OR_NEWER radiusAngleLength = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusAngleLength, 7); #else radiusAngleLength = Call (typeof (Handles), "ConeFrustrumHandle", Quaternion.identity, Vector3.zero, radiusAngleLength); #endif if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Cone Volume Handle Change"); shapeModule.radius = radiusAngleLength.x; shapeModule.angle = radiusAngleLength.y; shapeModule.length = radiusAngleLength.z; } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (Vector3.zero, Quaternion.Euler (90.0f, 0.0f, 180.0f), Vector3.one * shapeModule.radius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_CircleMesh, true, s_TextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.Box || type == ParticleSystemShapeType.BoxShell || type == ParticleSystemShapeType.BoxEdge) { EditorGUI.BeginChangeCheck (); _boxBoundsHandle.center = Vector3.zero; _boxBoundsHandle.size = shapeModule.scale; _boxBoundsHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Box Handle Change"); shapeModule.scale = _boxBoundsHandle.size; } //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (new Vector3 (0.0f, 0.0f, -m_BoxBoundsHandle.size.z * 0.5f), Quaternion.identity, m_BoxBoundsHandle.size); //OnSceneViewTextureGUI (shapeModule, s_QuadMesh, true, s_TextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.Donut) { // Radius EditorGUI.BeginChangeCheck (); _arcHandle.radius = shapeModule.radius; _arcHandle.angle = shapeModule.arc; _arcHandle.SetColorWithRadiusHandle (Color.white, 0f); _arcHandle.wireframeColor = Color.clear; using (new Handles.DrawingScope (Handles.matrix * s_ArcHandleOffsetMatrix)) _arcHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Donut Handle Change"); shapeModule.radius = _arcHandle.radius; shapeModule.arc = _arcHandle.angle; } // Donut extents using (new Handles.DrawingScope (Handles.matrix * s_ArcHandleOffsetMatrix)) { float excessAngle = shapeModule.arc % 360f; float angle = Mathf.Abs (shapeModule.arc) >= 360f ? 360f : excessAngle; Handles.DrawWireArc (new Vector3 (0.0f, shapeModule.donutRadius, 0.0f), Vector3.up, Vector3.forward, angle, shapeModule.radius); Handles.DrawWireArc (new Vector3 (0.0f, -shapeModule.donutRadius, 0.0f), Vector3.up, Vector3.forward, angle, shapeModule.radius); Handles.DrawWireArc (Vector3.zero, Vector3.up, Vector3.forward, angle, shapeModule.radius + shapeModule.donutRadius); Handles.DrawWireArc (Vector3.zero, Vector3.up, Vector3.forward, angle, shapeModule.radius - shapeModule.donutRadius); if (shapeModule.arc != 360.0f) { Quaternion arcRotation = Quaternion.AngleAxis (shapeModule.arc, Vector3.up); Vector3 capCenter = arcRotation * Vector3.forward * shapeModule.radius; Handles.DrawWireDisc (capCenter, arcRotation * Vector3.right, shapeModule.donutRadius); } } // Donut thickness _sphereBoundsHandle.axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y; _sphereBoundsHandle.radius = shapeModule.donutRadius * (1.0f - shapeModule.radiusThickness); _sphereBoundsHandle.center = Vector3.zero; _sphereBoundsHandle.SetColor (s_ShapeGizmoThicknessTint); const float handleInterval = 90.0f; int numOuterRadii = Mathf.Max (1, (int)Mathf.Ceil (shapeModule.arc / handleInterval)); Matrix4x4 donutRadiusStartMatrix = Matrix4x4.TRS (new Vector3 (shapeModule.radius, 0.0f, 0.0f), Quaternion.Euler (90.0f, 0.0f, 0.0f), Vector3.one); for (int i = 0; i < numOuterRadii; i++) { EditorGUI.BeginChangeCheck (); using (new Handles.DrawingScope (Handles.matrix * (Matrix4x4.Rotate (Quaternion.Euler (0.0f, 0.0f, handleInterval * i)) * donutRadiusStartMatrix))) _sphereBoundsHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Donut Radius Thickness Handle Change"); shapeModule.radiusThickness = 1.0f - (_sphereBoundsHandle.radius / shapeModule.donutRadius); } } // Donut radius _sphereBoundsHandle.radius = shapeModule.donutRadius; _sphereBoundsHandle.SetColor (Color.white); for (int i = 0; i < numOuterRadii; i++) { EditorGUI.BeginChangeCheck (); using (new Handles.DrawingScope (Handles.matrix * (Matrix4x4.Rotate (Quaternion.Euler (0.0f, 0.0f, handleInterval * i)) * donutRadiusStartMatrix))) _sphereBoundsHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Donut Radius Handle Change"); shapeModule.donutRadius = _sphereBoundsHandle.radius; } } // Texture //Matrix4x4 textureTransform = transformMatrix * Matrix4x4.TRS (new Vector3 (shapeModule.radius, 0.0f, 0.0f), Quaternion.Euler (180.0f, 0.0f, 180.0f), Vector3.one * shapeModule.donutRadius * 2.0f); //OnSceneViewTextureGUI (shapeModule, s_CircleMesh, true, s_TextureMaterial, textureTransform); } else if (type == ParticleSystemShapeType.SingleSidedEdge) { EditorGUI.BeginChangeCheck (); //float radius = Handles.DoSimpleEdgeHandle (Quaternion.identity, Vector3.zero, shapeModule.radius); float radius = Call (typeof (Handles), "DoSimpleEdgeHandle", Quaternion.identity, Vector3.zero, shapeModule.radius); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Edge Handle Change"); shapeModule.radius = radius; } } else if (type == ParticleSystemShapeType.Mesh) { Mesh mesh = shapeModule.mesh; if (mesh) { bool orgWireframeMode = GL.wireframe; GL.wireframe = true; s_Material.SetPass (0); Graphics.DrawMeshNow (mesh, transformMatrix); GL.wireframe = orgWireframeMode; //OnSceneViewTextureGUI (shapeModule, mesh, false, s_TextureMaterial, transformMatrix); } } else if (type == ParticleSystemShapeType.Rectangle) { EditorGUI.BeginChangeCheck (); _boxBoundsHandle.center = Vector3.zero; _boxBoundsHandle.size = new Vector3 (shapeModule.scale.x, shapeModule.scale.y, 0.0f); _boxBoundsHandle.DrawHandle (); if (EditorGUI.EndChangeCheck ()) { Undo.RecordObject (ps, "Rectangle Handle Change"); shapeModule.scale = new Vector3 (_boxBoundsHandle.size.x, _boxBoundsHandle.size.y, 0.0f); } //OnSceneViewTextureGUI (shapeModule, s_QuadMesh, true, s_TextureMaterial, transformMatrix * Matrix4x4.Scale (m_BoxBoundsHandle.size)); } else if (type == ParticleSystemShapeType.Sprite) { Sprite sprite = shapeModule.sprite; if (sprite) { if (!_spriteMesh) { _spriteMesh = new Mesh (); _spriteMesh.name = "ParticleSpritePreview"; _spriteMesh.hideFlags |= HideFlags.HideAndDontSave; } _spriteMesh.vertices = Array.ConvertAll (sprite.vertices, i => (Vector3)i); _spriteMesh.uv = sprite.uv; _spriteMesh.triangles = Array.ConvertAll (sprite.triangles, i => (int)i); bool orgWireframeMode = GL.wireframe; GL.wireframe = true; s_Material.SetPass (0); Graphics.DrawMeshNow (_spriteMesh, transformMatrix); GL.wireframe = orgWireframeMode; //OnSceneViewTextureGUI (shapeModule, m_SpriteMesh, false, s_TextureMaterial, transformMatrix); } } } Handles.color = origCol; Handles.matrix = orgMatrix; } } }