You've already forked taptap2024_GJ_chidouren
init
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
[System.Serializable]
|
||||
public class AnimatableProperty : ISerializationCallbackReceiver
|
||||
{
|
||||
public enum ShaderPropertyType
|
||||
{
|
||||
Color,
|
||||
Vector,
|
||||
Float,
|
||||
Range,
|
||||
Texture,
|
||||
}
|
||||
|
||||
[SerializeField] string m_Name = "";
|
||||
[SerializeField] ShaderPropertyType m_Type = ShaderPropertyType.Vector;
|
||||
public int id { get; private set; }
|
||||
|
||||
public ShaderPropertyType type
|
||||
{
|
||||
get { return m_Type; }
|
||||
}
|
||||
|
||||
public void UpdateMaterialProperties(Material material, MaterialPropertyBlock mpb)
|
||||
{
|
||||
if (!material.HasProperty(id)) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ShaderPropertyType.Color:
|
||||
var color = mpb.GetColor(id);
|
||||
if (color != default(Color))
|
||||
material.SetColor(id, color);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
var vector = mpb.GetVector(id);
|
||||
if (vector != default(Vector4))
|
||||
material.SetVector(id, vector);
|
||||
break;
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
var value = mpb.GetFloat(id);
|
||||
if (value != default(float))
|
||||
material.SetFloat(id, value);
|
||||
break;
|
||||
case ShaderPropertyType.Texture:
|
||||
var tex = mpb.GetTexture(id);
|
||||
if (tex != default(Texture))
|
||||
material.SetTexture(id, tex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
id = Shader.PropertyToID(m_Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c434da72184404a0cbdf8e7a529a41bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Coffee.UIParticle",
|
||||
"references": [],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7793e5cd8d2e94e21b56de41db6606bb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.coffee.ui-particle/Scripts/Editor.meta
Normal file
8
Packages/com.coffee.ui-particle/Scripts/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f479867169fb04929b98c38e646b83c5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,123 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ShaderPropertyType = Coffee.UIExtensions.AnimatableProperty.ShaderPropertyType;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
internal class AnimatedPropertiesEditor
|
||||
{
|
||||
private static readonly List<string> s_ActiveNames = new List<string>();
|
||||
private static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
|
||||
private static readonly HashSet<string> s_Names = new HashSet<string>();
|
||||
|
||||
private string _name;
|
||||
private ShaderPropertyType _type;
|
||||
|
||||
static string CollectActiveNames(SerializedProperty sp, List<string> result)
|
||||
{
|
||||
result.Clear();
|
||||
for (var i = 0; i < sp.arraySize; i++)
|
||||
{
|
||||
var spName = sp.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name");
|
||||
if (spName == null) continue;
|
||||
|
||||
result.Add(spName.stringValue);
|
||||
}
|
||||
|
||||
s_Sb.Length = 0;
|
||||
if (result.Count == 0)
|
||||
{
|
||||
s_Sb.Append("Nothing");
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Aggregate(s_Sb, (a, b) => s_Sb.AppendFormat("{0}, ", b));
|
||||
s_Sb.Length -= 2;
|
||||
}
|
||||
|
||||
return s_Sb.ToString();
|
||||
}
|
||||
|
||||
public static void DrawAnimatableProperties(SerializedProperty sp, Material[] mats)
|
||||
{
|
||||
bool isClicked;
|
||||
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false)))
|
||||
{
|
||||
var r = EditorGUI.PrefixLabel(EditorGUILayout.GetControlRect(true), new GUIContent(sp.displayName, sp.tooltip));
|
||||
var text = sp.hasMultipleDifferentValues ? "-" : CollectActiveNames(sp, s_ActiveNames);
|
||||
isClicked = GUI.Button(r, text, EditorStyles.popup);
|
||||
}
|
||||
|
||||
if (!isClicked) return;
|
||||
|
||||
var gm = new GenericMenu();
|
||||
gm.AddItem(new GUIContent("Nothing"), s_ActiveNames.Count == 0, () =>
|
||||
{
|
||||
sp.ClearArray();
|
||||
sp.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
|
||||
|
||||
if (!sp.hasMultipleDifferentValues)
|
||||
{
|
||||
for (var i = 0; i < sp.arraySize; i++)
|
||||
{
|
||||
var p = sp.GetArrayElementAtIndex(i);
|
||||
var name = p.FindPropertyRelative("m_Name").stringValue;
|
||||
var type = (ShaderPropertyType) p.FindPropertyRelative("m_Type").intValue;
|
||||
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = name, _type = type}, false);
|
||||
}
|
||||
}
|
||||
|
||||
s_Names.Clear();
|
||||
foreach (var mat in mats)
|
||||
{
|
||||
if (!mat || !mat.shader) continue;
|
||||
|
||||
for (var i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++)
|
||||
{
|
||||
var pName = ShaderUtil.GetPropertyName(mat.shader, i);
|
||||
var type = (ShaderPropertyType) ShaderUtil.GetPropertyType(mat.shader, i);
|
||||
var name = string.Format("{0} ({1})", pName, type);
|
||||
if (s_Names.Contains(name)) continue;
|
||||
s_Names.Add(name);
|
||||
|
||||
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName, _type = type}, true);
|
||||
|
||||
if (type != ShaderPropertyType.Texture) continue;
|
||||
|
||||
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_ST", _type = ShaderPropertyType.Vector}, true);
|
||||
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_HDR", _type = ShaderPropertyType.Vector}, true);
|
||||
AddMenu(gm, sp, new AnimatedPropertiesEditor {_name = pName + "_TexelSize", _type = ShaderPropertyType.Vector}, true);
|
||||
}
|
||||
}
|
||||
|
||||
gm.ShowAsContext();
|
||||
}
|
||||
|
||||
private static void AddMenu(GenericMenu menu, SerializedProperty sp, AnimatedPropertiesEditor property, bool add)
|
||||
{
|
||||
if (add && s_ActiveNames.Contains(property._name)) return;
|
||||
|
||||
menu.AddItem(new GUIContent(string.Format("{0} ({1})", property._name, property._type)), s_ActiveNames.Contains(property._name), () =>
|
||||
{
|
||||
var index = s_ActiveNames.IndexOf(property._name);
|
||||
if (0 <= index)
|
||||
{
|
||||
sp.DeleteArrayElementAtIndex(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.InsertArrayElementAtIndex(sp.arraySize);
|
||||
var p = sp.GetArrayElementAtIndex(sp.arraySize - 1);
|
||||
p.FindPropertyRelative("m_Name").stringValue = property._name;
|
||||
p.FindPropertyRelative("m_Type").intValue = (int) property._type;
|
||||
}
|
||||
|
||||
sp.serializedObject.ApplyModifiedProperties();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60feb9aeb134e4ec789c0e63b017997f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Coffee.UIParticle.Editor",
|
||||
"references": [
|
||||
"Coffee.UIParticle"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69deabfe8eed7495886c9daa69c9826a
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,85 @@
|
||||
#if !UNITY_2019_1_OR_NEWER
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
internal static class ImportSampleMenu_UIParticle
|
||||
{
|
||||
private const string k_DisplayName = "UI Particle";
|
||||
private const string k_JsonGuid = "823dc693d087a4b559c7e1547274cc7d";
|
||||
|
||||
[MenuItem("Assets/Samples/" + k_DisplayName + "/Demo")]
|
||||
private static void ImportSample()
|
||||
{
|
||||
ImportSample(k_JsonGuid, "Demo");
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Samples/" + k_DisplayName + "/Cartoon FX & War FX Demo")]
|
||||
private static void ImportSample_CFX()
|
||||
{
|
||||
ImportSample(k_JsonGuid, "Cartoon FX & War FX Demo");
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Samples/" + k_DisplayName + "/Cartoon FX & War FX Demo")]
|
||||
private static void ImportSample_PerformanceDemo()
|
||||
{
|
||||
ImportSample(k_JsonGuid, "Performance Demo");
|
||||
}
|
||||
|
||||
private static void ImportSample(string jsonGuid, string sampleName)
|
||||
{
|
||||
var jsonPath = AssetDatabase.GUIDToAssetPath(jsonGuid);
|
||||
var packageRoot = Path.GetDirectoryName(jsonPath).Replace('\\', '/');
|
||||
var json = File.ReadAllText(jsonPath);
|
||||
var version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
|
||||
var src = string.Format("{0}/Samples~/{1}", packageRoot, sampleName);
|
||||
var dst = string.Format("Assets/Samples/{0}/{1}/{2}", k_DisplayName, version, sampleName);
|
||||
var previousPath = GetPreviousSamplePath(k_DisplayName, sampleName);
|
||||
|
||||
// Remove the previous sample directory.
|
||||
if (!string.IsNullOrEmpty(previousPath))
|
||||
{
|
||||
var msg = "A different version of the sample is already imported at\n\n"
|
||||
+ previousPath
|
||||
+ "\n\nIt will be deleted when you update. Are you sure you want to continue?";
|
||||
if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel"))
|
||||
return;
|
||||
|
||||
FileUtil.DeleteFileOrDirectory(previousPath);
|
||||
|
||||
var metaFile = previousPath + ".meta";
|
||||
if (File.Exists(metaFile))
|
||||
FileUtil.DeleteFileOrDirectory(metaFile);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(dst))
|
||||
FileUtil.DeleteFileOrDirectory(dst);
|
||||
|
||||
var dstDir = Path.GetDirectoryName(dst);
|
||||
if (!Directory.Exists(dstDir))
|
||||
Directory.CreateDirectory(dstDir);
|
||||
|
||||
if (Directory.Exists(src))
|
||||
FileUtil.CopyFileOrDirectory(src, dst);
|
||||
else
|
||||
throw new DirectoryNotFoundException(src);
|
||||
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
|
||||
}
|
||||
|
||||
private static string GetPreviousSamplePath(string displayName, string sampleName)
|
||||
{
|
||||
var sampleRoot = string.Format("Assets/Samples/{0}", displayName);
|
||||
var sampleRootInfo = new DirectoryInfo(sampleRoot);
|
||||
if (!sampleRootInfo.Exists) return null;
|
||||
|
||||
return sampleRootInfo.GetDirectories()
|
||||
.Select(versionDir => Path.Combine(versionDir.ToString(), sampleName))
|
||||
.FirstOrDefault(Directory.Exists);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07047cbd3c4af489fbd41f4303a26fe2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,526 @@
|
||||
#if UNITY_2019_3_11 || UNITY_2019_3_12 || UNITY_2019_3_13 || UNITY_2019_3_14 || UNITY_2019_3_15 || UNITY_2019_4_OR_NEWER
|
||||
#define SERIALIZE_FIELD_MASKABLE
|
||||
#endif
|
||||
using UnityEditor;
|
||||
using UnityEditor.UI;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine.UI;
|
||||
using System;
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
using UnityEditor.Overlays;
|
||||
#else
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
[CustomEditor(typeof(UIParticle))]
|
||||
[CanEditMultipleObjects]
|
||||
internal class UIParticleEditor : GraphicEditor
|
||||
{
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#if UNITY_2022_1_OR_NEWER
|
||||
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true, defaultDockPosition = DockPosition.Bottom, defaultDockZone = DockZone.Floating, defaultLayout = Layout.Panel)]
|
||||
#else
|
||||
[Overlay(typeof(SceneView), "Scene View/UI Particles", "UI Particles", true)]
|
||||
#endif
|
||||
private class UIParticleOverlay : IMGUIOverlay, ITransientOverlay
|
||||
{
|
||||
public bool visible => s_SerializedObject != null;
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
WindowFunction(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//################################
|
||||
// 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_ContentRandom = new GUIContent("Random");
|
||||
private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
|
||||
private static SerializedObject s_SerializedObject;
|
||||
|
||||
#if !SERIALIZE_FIELD_MASKABLE
|
||||
private SerializedProperty m_Maskable;
|
||||
#endif
|
||||
private SerializedProperty m_Scale3D;
|
||||
private SerializedProperty m_AnimatableProperties;
|
||||
private SerializedProperty m_MeshSharing;
|
||||
private SerializedProperty m_GroupId;
|
||||
private SerializedProperty m_GroupMaxId;
|
||||
private SerializedProperty m_AbsoluteMode;
|
||||
|
||||
|
||||
private ReorderableList _ro;
|
||||
static private bool _xyzMode;
|
||||
private bool _showMax;
|
||||
|
||||
private static readonly HashSet<Shader> s_Shaders = new HashSet<Shader>();
|
||||
private static readonly List<ParticleSystemVertexStream> s_Streams = new List<ParticleSystemVertexStream>();
|
||||
private static readonly List<string> s_MaskablePropertyNames = new List<string>
|
||||
{
|
||||
"_Stencil",
|
||||
"_StencilComp",
|
||||
"_StencilOp",
|
||||
"_StencilWriteMask",
|
||||
"_StencilReadMask",
|
||||
"_ColorMask",
|
||||
};
|
||||
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void Init()
|
||||
{
|
||||
#if !UNITY_2021_2_OR_NEWER
|
||||
var miSceneViewOverlayWindow = Type.GetType("UnityEditor.SceneViewOverlay, UnityEditor")
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.First(x => x.Name == "Window" && 5 <= x.GetParameters().Length);
|
||||
var windowFunction = (Action<UnityEngine.Object, SceneView>)WindowFunction;
|
||||
var windowFunctionType = Type.GetType("UnityEditor.SceneViewOverlay+WindowFunction, UnityEditor");
|
||||
var windowFunctionDelegate = Delegate.CreateDelegate(windowFunctionType, windowFunction.Method);
|
||||
var windowTitle = new GUIContent(ObjectNames.NicifyVariableName(typeof(UIParticle).Name));
|
||||
#if UNITY_2019_2_OR_NEWER
|
||||
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option, EditorWindow window = null)
|
||||
var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2, null };
|
||||
#else
|
||||
//public static void Window(GUIContent title, WindowFunction sceneViewFunc, int order, Object target, WindowDisplayOption option)
|
||||
var sceneViewArgs = new object[] { windowTitle, windowFunctionDelegate, 599, null, 2 };
|
||||
#endif
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui += _ => { if (s_SerializedObject != null) miSceneViewOverlayWindow.Invoke(null, sceneViewArgs); };
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate += _ =>
|
||||
#endif
|
||||
{
|
||||
if (s_SerializedObject != null)
|
||||
{
|
||||
miSceneViewOverlayWindow.Invoke(null, sceneViewArgs);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
Func<SerializedObject> createSerializeObject = () =>
|
||||
{
|
||||
var uiParticles = Selection.gameObjects
|
||||
.Select(x => x.GetComponent<ParticleSystem>())
|
||||
.Where(x => x)
|
||||
.Select(x => x.GetComponentInParent<UIParticle>())
|
||||
.Where(x => x && x.canvas)
|
||||
.Concat(
|
||||
Selection.gameObjects
|
||||
.Select(x => x.GetComponent<UIParticle>())
|
||||
.Where(x => x && x.canvas)
|
||||
)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
return 0 < uiParticles.Length ? new SerializedObject(uiParticles) : null;
|
||||
};
|
||||
|
||||
s_SerializedObject = createSerializeObject();
|
||||
Selection.selectionChanged += () => s_SerializedObject = createSerializeObject();
|
||||
}
|
||||
|
||||
//################################
|
||||
// Public/Protected Members.
|
||||
//################################
|
||||
/// <summary>
|
||||
/// This function is called when the object becomes enabled and active.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
m_Maskable = serializedObject.FindProperty("m_Maskable");
|
||||
m_Scale3D = serializedObject.FindProperty("m_Scale3D");
|
||||
m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
|
||||
m_MeshSharing = serializedObject.FindProperty("m_MeshSharing");
|
||||
m_GroupId = serializedObject.FindProperty("m_GroupId");
|
||||
m_GroupMaxId = serializedObject.FindProperty("m_GroupMaxId");
|
||||
m_AbsoluteMode = serializedObject.FindProperty("m_AbsoluteMode");
|
||||
|
||||
var sp = serializedObject.FindProperty("m_Particles");
|
||||
_ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true);
|
||||
_ro.elementHeight = (EditorGUIUtility.singleLineHeight * 3) + 4;
|
||||
_ro.elementHeightCallback = _ => 3 * (EditorGUIUtility.singleLineHeight + 2);
|
||||
_ro.drawElementCallback = (rect, index, _, __) =>
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(sp.hasMultipleDifferentValues);
|
||||
rect.y += 1;
|
||||
rect.height = EditorGUIUtility.singleLineHeight;
|
||||
var p = sp.GetArrayElementAtIndex(index);
|
||||
EditorGUI.ObjectField(rect, p, GUIContent.none);
|
||||
rect.x += 15;
|
||||
rect.width -= 15;
|
||||
var ps = p.objectReferenceValue as ParticleSystem;
|
||||
var materials = ps
|
||||
? new SerializedObject(ps.GetComponent<ParticleSystemRenderer>()).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.hasModifiedProperties)
|
||||
{
|
||||
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);
|
||||
|
||||
if (GUI.Button(new Rect(rect.width - 35, rect.y, 60, rect.height), s_ContentRefresh, EditorStyles.miniButton))
|
||||
{
|
||||
foreach (UIParticle t in targets)
|
||||
{
|
||||
t.RefreshParticles();
|
||||
EditorUtility.SetDirty(t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// On select UIParticle, refresh particles.
|
||||
foreach (UIParticle t in targets)
|
||||
{
|
||||
if (Application.isPlaying || PrefabUtility.GetPrefabAssetType(t) != PrefabAssetType.NotAPrefab) continue;
|
||||
t.RefreshParticles(t.particles);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement this function to make a custom inspector.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var current = target as UIParticle;
|
||||
if (!current) return;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Maskable
|
||||
EditorGUILayout.PropertyField(m_Maskable);
|
||||
|
||||
// Scale
|
||||
EditorGUI.BeginDisabledGroup(!m_MeshSharing.hasMultipleDifferentValues && m_MeshSharing.intValue == 4);
|
||||
_xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
// AnimatableProperties
|
||||
var mats = current.particles
|
||||
.Where(x => x)
|
||||
.Select(x => x.GetComponent<ParticleSystemRenderer>().sharedMaterial)
|
||||
.Where(x => x)
|
||||
.ToArray();
|
||||
|
||||
// Animated properties
|
||||
AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats);
|
||||
|
||||
// Mesh sharing
|
||||
EditorGUI.BeginChangeCheck();
|
||||
_showMax = DrawMeshSharing(m_MeshSharing, m_GroupId, m_GroupMaxId, _showMax);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
foreach (var uip in targets.OfType<UIParticle>())
|
||||
{
|
||||
uip.ResetGroupId();
|
||||
}
|
||||
}
|
||||
|
||||
// Absolute Mode
|
||||
EditorGUILayout.PropertyField(m_AbsoluteMode);
|
||||
|
||||
// Target ParticleSystems.
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.BeginDisabledGroup(targets.OfType<UIParticle>().Any(x => !x.canvas));
|
||||
_ro.DoLayoutList();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
foreach (var uip in targets.OfType<UIParticle>())
|
||||
{
|
||||
uip.RefreshParticles(uip.particles);
|
||||
}
|
||||
}
|
||||
|
||||
// Does the shader support UI masks?
|
||||
if (current.maskable && current.GetComponentInParent<Mask>())
|
||||
{
|
||||
foreach (var mat in current.materials)
|
||||
{
|
||||
if (!mat || !mat.shader) continue;
|
||||
var shader = mat.shader;
|
||||
if (s_Shaders.Contains(shader)) continue;
|
||||
s_Shaders.Add(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
s_Shaders.Clear();
|
||||
|
||||
// UIParticle for trail should be removed.
|
||||
if (FixButton(current.m_IsTrail, "This UIParticle component should be removed. The UIParticle for trails is no longer needed."))
|
||||
{
|
||||
DestroyUIParticle(current);
|
||||
}
|
||||
|
||||
// #203: When using linear color space, the particle colors are not output correctly.
|
||||
// To fix, set 'Apply Active Color Space' in renderer module to false.
|
||||
var allPsRenderers = targets.OfType<UIParticle>()
|
||||
.SelectMany(x => x.particles)
|
||||
.Where(x => x)
|
||||
.Select(x => x.GetComponent<ParticleSystemRenderer>())
|
||||
.ToArray();
|
||||
if (0 < allPsRenderers.Length)
|
||||
{
|
||||
var so = new SerializedObject(allPsRenderers);
|
||||
var sp = so.FindProperty("m_ApplyActiveColorSpace");
|
||||
if (FixButton(sp.boolValue || sp.hasMultipleDifferentValues, "When using linear color space, the particle colors are not output correctly.\nTo fix, set 'Apply Active Color Space' in renderer module to false."))
|
||||
{
|
||||
sp.boolValue = false;
|
||||
so.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
// Check to use 'TEXCOORD*.zw' components as custom vertex stream.
|
||||
foreach (var psr in allPsRenderers)
|
||||
{
|
||||
if (!new SerializedObject(psr).FindProperty("m_UseCustomVertexStreams").boolValue) continue;
|
||||
if (psr.activeVertexStreamsCount == 0) continue;
|
||||
psr.GetActiveVertexStreams(s_Streams);
|
||||
|
||||
if (2 < s_Streams.Select(GetUsedComponentCount).Sum())
|
||||
{
|
||||
EditorGUILayout.HelpBox(string.Format("ParticleSystem '{0}' uses 'TEXCOORD*.zw' components as custom vertex stream.\nUIParticle does not support it (See README.md).", psr.name), MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetUsedComponentCount(ParticleSystemVertexStream s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case ParticleSystemVertexStream.Position:
|
||||
case ParticleSystemVertexStream.Normal:
|
||||
case ParticleSystemVertexStream.Tangent:
|
||||
case ParticleSystemVertexStream.Color:
|
||||
return 0;
|
||||
case ParticleSystemVertexStream.UV:
|
||||
case ParticleSystemVertexStream.UV2:
|
||||
case ParticleSystemVertexStream.UV3:
|
||||
case ParticleSystemVertexStream.UV4:
|
||||
case ParticleSystemVertexStream.SizeXY:
|
||||
case ParticleSystemVertexStream.StableRandomXY:
|
||||
case ParticleSystemVertexStream.VaryingRandomXY:
|
||||
case ParticleSystemVertexStream.Custom1XY:
|
||||
case ParticleSystemVertexStream.Custom2XY:
|
||||
case ParticleSystemVertexStream.NoiseSumXY:
|
||||
case ParticleSystemVertexStream.NoiseImpulseXY:
|
||||
return 2;
|
||||
case ParticleSystemVertexStream.AnimBlend:
|
||||
case ParticleSystemVertexStream.AnimFrame:
|
||||
case ParticleSystemVertexStream.VertexID:
|
||||
case ParticleSystemVertexStream.SizeX:
|
||||
case ParticleSystemVertexStream.Rotation:
|
||||
case ParticleSystemVertexStream.RotationSpeed:
|
||||
case ParticleSystemVertexStream.Velocity:
|
||||
case ParticleSystemVertexStream.Speed:
|
||||
case ParticleSystemVertexStream.AgePercent:
|
||||
case ParticleSystemVertexStream.InvStartLifetime:
|
||||
case ParticleSystemVertexStream.StableRandomX:
|
||||
case ParticleSystemVertexStream.VaryingRandomX:
|
||||
case ParticleSystemVertexStream.Custom1X:
|
||||
case ParticleSystemVertexStream.Custom2X:
|
||||
case ParticleSystemVertexStream.NoiseSumX:
|
||||
case ParticleSystemVertexStream.NoiseImpulseX:
|
||||
return 1;
|
||||
case ParticleSystemVertexStream.Center:
|
||||
case ParticleSystemVertexStream.SizeXYZ:
|
||||
case ParticleSystemVertexStream.Rotation3D:
|
||||
case ParticleSystemVertexStream.RotationSpeed3D:
|
||||
case ParticleSystemVertexStream.StableRandomXYZ:
|
||||
case ParticleSystemVertexStream.VaryingRandomXYZ:
|
||||
case ParticleSystemVertexStream.Custom1XYZ:
|
||||
case ParticleSystemVertexStream.Custom2XYZ:
|
||||
case ParticleSystemVertexStream.NoiseSumXYZ:
|
||||
case ParticleSystemVertexStream.NoiseImpulseXYZ:
|
||||
return 3;
|
||||
case ParticleSystemVertexStream.StableRandomXYZW:
|
||||
case ParticleSystemVertexStream.VaryingRandomXYZW:
|
||||
case ParticleSystemVertexStream.Custom1XYZW:
|
||||
case ParticleSystemVertexStream.Custom2XYZW:
|
||||
return 4;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
private static bool DrawMeshSharing(SerializedProperty spMeshSharing, SerializedProperty spGroupId, SerializedProperty spGroupMaxId, bool showMax)
|
||||
{
|
||||
showMax |= spGroupId.intValue != spGroupMaxId.intValue ||
|
||||
spGroupId.hasMultipleDifferentValues ||
|
||||
spGroupMaxId.hasMultipleDifferentValues;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(spMeshSharing);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
showMax = GUILayout.Toggle(showMax, s_ContentRandom, EditorStyles.miniButton, GUILayout.Width(60));
|
||||
if (EditorGUI.EndChangeCheck() && !showMax)
|
||||
spGroupMaxId.intValue = spGroupId.intValue;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(spMeshSharing.intValue == 0);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(spGroupId);
|
||||
if (showMax)
|
||||
{
|
||||
EditorGUILayout.PropertyField(spGroupMaxId);
|
||||
}
|
||||
else if (spMeshSharing.intValue == 1 || spMeshSharing.intValue == 4)
|
||||
{
|
||||
EditorGUI.BeginDisabledGroup(true);
|
||||
EditorGUILayout.ObjectField("Primary", UIParticleUpdater.GetPrimary(spGroupId.intValue), typeof(UIParticle), false);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
return showMax;
|
||||
}
|
||||
|
||||
private static void WindowFunction(UnityEngine.Object target, SceneView sceneView)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (s_SerializedObject.targetObjects.OfType<UIParticle>().Any(x => !x || !x.canvas)) return;
|
||||
|
||||
s_SerializedObject.Update();
|
||||
using (new EditorGUILayout.VerticalScope(GUILayout.Width(220f)))
|
||||
{
|
||||
var labelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.labelWidth = 100;
|
||||
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_Enabled"));
|
||||
_xyzMode = DrawFloatOrVector3Field(s_SerializedObject.FindProperty("m_Scale3D"), _xyzMode);
|
||||
EditorGUILayout.PropertyField(s_SerializedObject.FindProperty("m_AbsoluteMode"));
|
||||
EditorGUIUtility.labelWidth = labelWidth;
|
||||
}
|
||||
s_SerializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false)
|
||||
{
|
||||
if (!p || ignoreCurrent && target == p) return;
|
||||
|
||||
var cr = p.canvasRenderer;
|
||||
DestroyImmediate(p);
|
||||
DestroyImmediate(cr);
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
#elif UNITY_2018_3_OR_NEWER
|
||||
var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
#endif
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
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
|
||||
}
|
||||
|
||||
private static 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)
|
||||
{
|
||||
EditorGUILayout.PropertyField(sp);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(x, s_ContentScale);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
y.floatValue = z.floatValue = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37cdd12f98487483aba53394d2174e31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
internal class UIParticleMenu
|
||||
{
|
||||
[MenuItem("GameObject/UI/Particle System (Empty)", false, 2018)]
|
||||
private static void AddParticleEmpty(MenuCommand menuCommand)
|
||||
{
|
||||
// Create empty UI element.
|
||||
EditorApplication.ExecuteMenuItem("GameObject/UI/Image");
|
||||
var ui = Selection.activeGameObject;
|
||||
Object.DestroyImmediate(ui.GetComponent<Image>());
|
||||
|
||||
// Add UIParticle.
|
||||
var uiParticle = ui.AddComponent<UIParticle>();
|
||||
uiParticle.name = "UIParticle";
|
||||
uiParticle.scale = 10;
|
||||
uiParticle.rectTransform.sizeDelta = Vector2.zero;
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/UI/Particle System", false, 2019)]
|
||||
private static void AddParticle(MenuCommand menuCommand)
|
||||
{
|
||||
// Create empty UIEffect.
|
||||
AddParticleEmpty(menuCommand);
|
||||
var uiParticle = Selection.activeGameObject.GetComponent<UIParticle>();
|
||||
|
||||
// Create ParticleSystem.
|
||||
EditorApplication.ExecuteMenuItem("GameObject/Effects/Particle System");
|
||||
var ps = Selection.activeGameObject;
|
||||
ps.transform.SetParent(uiParticle.transform, false);
|
||||
ps.transform.localPosition = Vector3.zero;
|
||||
|
||||
// Assign default material.
|
||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
var defaultMat = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Particle.mat");
|
||||
renderer.material = defaultMat ? defaultMat : renderer.material;
|
||||
|
||||
// Refresh particles.
|
||||
uiParticle.RefreshParticles();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ad158a8abf7646d6b540eed4a91a2dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Packages/com.coffee.ui-particle/Scripts/ModifiedMaterial.cs
Normal file
74
Packages/com.coffee.ui-particle/Scripts/ModifiedMaterial.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Coffee.UIParticleExtensions
|
||||
{
|
||||
internal class ModifiedMaterial
|
||||
{
|
||||
private static readonly List<MatEntry> s_Entries = new List<MatEntry>();
|
||||
|
||||
public static Material Add(Material baseMat, Texture texture, int id)
|
||||
{
|
||||
MatEntry e;
|
||||
for (var i = 0; i < s_Entries.Count; ++i)
|
||||
{
|
||||
e = s_Entries[i];
|
||||
if (e.baseMat != baseMat || e.texture != texture || e.id != id) continue;
|
||||
++e.count;
|
||||
return e.customMat;
|
||||
}
|
||||
|
||||
e = new MatEntry();
|
||||
e.count = 1;
|
||||
e.baseMat = baseMat;
|
||||
e.texture = texture;
|
||||
e.id = id;
|
||||
e.customMat = new Material(baseMat);
|
||||
e.customMat.hideFlags = HideFlags.HideAndDontSave;
|
||||
if (texture)
|
||||
e.customMat.mainTexture = texture;
|
||||
s_Entries.Add(e);
|
||||
//Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id);
|
||||
return e.customMat;
|
||||
}
|
||||
|
||||
public static void Remove(Material customMat)
|
||||
{
|
||||
if (!customMat) return;
|
||||
|
||||
for (var i = 0; i < s_Entries.Count; ++i)
|
||||
{
|
||||
var e = s_Entries[i];
|
||||
if (e.customMat != customMat) continue;
|
||||
if (--e.count == 0)
|
||||
{
|
||||
//Debug.LogFormat(">>>> ModifiedMaterial.Remove -> count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count - 1, e.customMat, e.texture, e.id);
|
||||
DestroyImmediate(e.customMat);
|
||||
e.baseMat = null;
|
||||
e.texture = null;
|
||||
s_Entries.RemoveAt(i);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DestroyImmediate(Object obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
if (Application.isEditor)
|
||||
Object.DestroyImmediate(obj);
|
||||
else
|
||||
Object.Destroy(obj);
|
||||
}
|
||||
|
||||
private class MatEntry
|
||||
{
|
||||
public Material baseMat;
|
||||
public Material customMat;
|
||||
public int count;
|
||||
public Texture texture;
|
||||
public int id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0beae5bb1cb142b9ab90dc0d371f026
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
471
Packages/com.coffee.ui-particle/Scripts/UIParticle.cs
Normal file
471
Packages/com.coffee.ui-particle/Scripts/UIParticle.cs
Normal file
@@ -0,0 +1,471 @@
|
||||
#if UNITY_2019_3_11 || UNITY_2019_3_12 || UNITY_2019_3_13 || UNITY_2019_3_14 || UNITY_2019_3_15 || UNITY_2019_4_OR_NEWER
|
||||
#define SERIALIZE_FIELD_MASKABLE
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Coffee.UIParticleExtensions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")]
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Render maskable and sortable particle effect ,without Camera, RenderTexture or Canvas.
|
||||
/// </summary>
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
public class UIParticle : MaskableGraphic
|
||||
{
|
||||
public enum MeshSharing
|
||||
{
|
||||
None,
|
||||
Auto,
|
||||
Primary,
|
||||
PrimarySimulator,
|
||||
Reprica,
|
||||
}
|
||||
|
||||
[HideInInspector][SerializeField] internal bool m_IsTrail = false;
|
||||
|
||||
[Tooltip("Particle effect scale")]
|
||||
[SerializeField]
|
||||
private Vector3 m_Scale3D = new Vector3(10, 10, 10);
|
||||
|
||||
[Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")]
|
||||
[SerializeField]
|
||||
internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
|
||||
|
||||
[Tooltip("Particles")]
|
||||
[SerializeField]
|
||||
private List<ParticleSystem> m_Particles = new List<ParticleSystem>();
|
||||
|
||||
[Tooltip("Mesh sharing.None: disable mesh sharing.\nAuto: automatically select Primary/Reprica.\nPrimary: provides particle simulation results to the same group.\nPrimary Simulator: Primary, but do not render the particle (simulation only).\nReprica: render simulation results provided by the primary.")]
|
||||
[SerializeField]
|
||||
private MeshSharing m_MeshSharing = MeshSharing.None;
|
||||
|
||||
[Tooltip("Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.")]
|
||||
[SerializeField]
|
||||
private int m_GroupId = 0;
|
||||
|
||||
[SerializeField]
|
||||
private int m_GroupMaxId = 0;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The particles will be emitted at the ParticleSystem position.\nMove the UIParticle/ParticleSystem to move the particle.")]
|
||||
private bool m_AbsoluteMode = false;
|
||||
|
||||
private List<UIParticleRenderer> m_Renderers = new List<UIParticleRenderer>();
|
||||
|
||||
#if !SERIALIZE_FIELD_MASKABLE
|
||||
[SerializeField] private bool m_Maskable = true;
|
||||
#endif
|
||||
|
||||
private DrivenRectTransformTracker _tracker;
|
||||
private Camera _orthoCamera;
|
||||
private int _groupId;
|
||||
|
||||
/// <summary>
|
||||
/// Should this graphic be considered a target for raycasting?
|
||||
/// </summary>
|
||||
public override bool raycastTarget
|
||||
{
|
||||
get { return false; }
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mesh sharing.None: disable mesh sharing.
|
||||
/// Auto: automatically select Primary/Reprica.
|
||||
/// Primary: provides particle simulation results to the same group.
|
||||
/// Primary Simulator: Primary, but do not render the particle (simulation only).
|
||||
/// Reprica: render simulation results provided by the primary.
|
||||
/// </summary>
|
||||
public MeshSharing meshSharing
|
||||
{
|
||||
get { return m_MeshSharing; }
|
||||
set { m_MeshSharing = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mesh sharing group ID. If non-zero is specified, particle simulation results are shared within the group.
|
||||
/// </summary>
|
||||
public int groupId
|
||||
{
|
||||
get { return _groupId; }
|
||||
set
|
||||
{
|
||||
if (m_GroupId == value) return;
|
||||
m_GroupId = value;
|
||||
if (m_GroupId != m_GroupMaxId)
|
||||
ResetGroupId();
|
||||
}
|
||||
}
|
||||
|
||||
public int groupMaxId
|
||||
{
|
||||
get { return m_GroupMaxId; }
|
||||
set
|
||||
{
|
||||
if (m_GroupMaxId == value) return;
|
||||
m_GroupMaxId = value;
|
||||
ResetGroupId();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Absolute particle position mode.
|
||||
/// The particles will be emitted at the ParticleSystem position.
|
||||
/// Move the UIParticle/ParticleSystem to move the particle.
|
||||
/// </summary>
|
||||
public bool absoluteMode
|
||||
{
|
||||
get { return m_AbsoluteMode; }
|
||||
set { m_AbsoluteMode = value; }
|
||||
}
|
||||
|
||||
internal bool useMeshSharing
|
||||
{
|
||||
get { return m_MeshSharing != MeshSharing.None; }
|
||||
}
|
||||
|
||||
internal bool isPrimary
|
||||
{
|
||||
get { return m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; }
|
||||
}
|
||||
|
||||
internal bool canSimulate
|
||||
{
|
||||
get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.PrimarySimulator; }
|
||||
}
|
||||
|
||||
internal bool canRender
|
||||
{
|
||||
get { return m_MeshSharing == MeshSharing.None || m_MeshSharing == MeshSharing.Auto || m_MeshSharing == MeshSharing.Primary || m_MeshSharing == MeshSharing.Reprica; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Particle effect scale.
|
||||
/// </summary>
|
||||
public float scale
|
||||
{
|
||||
get { return m_Scale3D.x; }
|
||||
set { m_Scale3D = new Vector3(value, value, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Particle effect scale.
|
||||
/// </summary>
|
||||
public Vector3 scale3D
|
||||
{
|
||||
get { return m_Scale3D; }
|
||||
set { m_Scale3D = value; }
|
||||
}
|
||||
|
||||
public List<ParticleSystem> particles
|
||||
{
|
||||
get { return m_Particles; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all base materials to render.
|
||||
/// </summary>
|
||||
public IEnumerable<Material> materials
|
||||
{
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < m_Renderers.Count; i++)
|
||||
{
|
||||
if (!m_Renderers[i] || !m_Renderers[i].material) continue;
|
||||
yield return m_Renderers[i].material;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
public override Material materialForRendering
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Paused.
|
||||
/// </summary>
|
||||
public bool isPaused { get; internal set; }
|
||||
|
||||
public void Play()
|
||||
{
|
||||
particles.Exec(p => p.Simulate(0, false, true));
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
particles.Exec(p => p.Pause());
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
particles.Exec(p => p.Stop());
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
particles.Exec(p => p.Clear());
|
||||
isPaused = true;
|
||||
}
|
||||
|
||||
public void SetParticleSystemInstance(GameObject instance)
|
||||
{
|
||||
SetParticleSystemInstance(instance, true);
|
||||
}
|
||||
|
||||
public void SetParticleSystemInstance(GameObject instance, bool destroyOldParticles)
|
||||
{
|
||||
if (!instance) return;
|
||||
|
||||
foreach (Transform child in transform)
|
||||
{
|
||||
var go = child.gameObject;
|
||||
go.SetActive(false);
|
||||
if (!destroyOldParticles) continue;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
DestroyImmediate(go);
|
||||
else
|
||||
#endif
|
||||
Destroy(go);
|
||||
}
|
||||
|
||||
var tr = instance.transform;
|
||||
tr.SetParent(transform, false);
|
||||
tr.localPosition = Vector3.zero;
|
||||
|
||||
RefreshParticles(instance);
|
||||
}
|
||||
|
||||
public void SetParticleSystemPrefab(GameObject prefab)
|
||||
{
|
||||
if (!prefab) return;
|
||||
|
||||
SetParticleSystemInstance(Instantiate(prefab.gameObject), true);
|
||||
}
|
||||
|
||||
public void RefreshParticles()
|
||||
{
|
||||
RefreshParticles(gameObject);
|
||||
}
|
||||
|
||||
private void RefreshParticles(GameObject root)
|
||||
{
|
||||
if (!root) return;
|
||||
root.GetComponentsInChildren(particles);
|
||||
particles.RemoveAll(x => x.GetComponentInParent<UIParticle>() != this);
|
||||
|
||||
foreach (var ps in particles)
|
||||
{
|
||||
var tsa = ps.textureSheetAnimation;
|
||||
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
|
||||
tsa.uvChannelMask = UVChannelFlags.UV0;
|
||||
}
|
||||
|
||||
RefreshParticles(particles);
|
||||
}
|
||||
|
||||
public void RefreshParticles(List<ParticleSystem> particles)
|
||||
{
|
||||
GetComponentsInChildren(m_Renderers);
|
||||
|
||||
var j = 0;
|
||||
for (var i = 0; i < particles.Count; i++)
|
||||
{
|
||||
if (!particles[i]) continue;
|
||||
GetRenderer(j++).Set(this, particles[i], false);
|
||||
if (particles[i].trails.enabled)
|
||||
{
|
||||
GetRenderer(j++).Set(this, particles[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
for (; j < m_Renderers.Count; j++)
|
||||
{
|
||||
GetRenderer(j).Clear(j);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateTransformScale()
|
||||
{
|
||||
//var newScale = Vector3.one;
|
||||
//if (uiScaling)
|
||||
//{
|
||||
// newScale = transform.parent.lossyScale.Inverse();
|
||||
//}
|
||||
var newScale = transform.parent.lossyScale.Inverse();
|
||||
if (transform.localScale != newScale)
|
||||
{
|
||||
transform.localScale = newScale;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateRenderers()
|
||||
{
|
||||
if (!isActiveAndEnabled) return;
|
||||
|
||||
if (m_Renderers.Any(x => !x))
|
||||
{
|
||||
RefreshParticles(particles);
|
||||
}
|
||||
|
||||
var bakeCamera = GetBakeCamera();
|
||||
for (var i = 0; i < m_Renderers.Count; i++)
|
||||
{
|
||||
if (!m_Renderers[i]) continue;
|
||||
m_Renderers[i].UpdateMesh(bakeCamera);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateParticleCount()
|
||||
{
|
||||
for (var i = 0; i < m_Renderers.Count; i++)
|
||||
{
|
||||
if (!m_Renderers[i]) continue;
|
||||
m_Renderers[i].UpdateParticleCount();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
#if !SERIALIZE_FIELD_MASKABLE
|
||||
maskable = m_Maskable;
|
||||
#endif
|
||||
ResetGroupId();
|
||||
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
|
||||
UIParticleUpdater.Register(this);
|
||||
RegisterDirtyMaterialCallback(UpdateRendererMaterial);
|
||||
|
||||
if (0 < particles.Count)
|
||||
{
|
||||
RefreshParticles(particles);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshParticles();
|
||||
}
|
||||
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
internal void ResetGroupId()
|
||||
{
|
||||
if (m_GroupId == m_GroupMaxId)
|
||||
{
|
||||
_groupId = m_GroupId;
|
||||
}
|
||||
else
|
||||
{
|
||||
_groupId = Random.Range(m_GroupId, m_GroupMaxId + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function is called when the behaviour becomes disabled.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
_tracker.Clear();
|
||||
UIParticleUpdater.Unregister(this);
|
||||
m_Renderers.ForEach(r => r.Clear());
|
||||
UnregisterDirtyMaterialCallback(UpdateRendererMaterial);
|
||||
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
protected override void UpdateMaterial()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
|
||||
/// </summary>
|
||||
protected override void UpdateGeometry()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when properties have been changed by animation.
|
||||
/// </summary>
|
||||
protected override void OnDidApplyAnimationProperties()
|
||||
{
|
||||
}
|
||||
|
||||
private void UpdateRendererMaterial()
|
||||
{
|
||||
for (var i = 0; i < m_Renderers.Count; i++)
|
||||
{
|
||||
if (!m_Renderers[i]) continue;
|
||||
m_Renderers[i].maskable = maskable;
|
||||
m_Renderers[i].SetMaterialDirty();
|
||||
}
|
||||
}
|
||||
|
||||
internal UIParticleRenderer GetRenderer(int index)
|
||||
{
|
||||
if (m_Renderers.Count <= index)
|
||||
{
|
||||
m_Renderers.Add(UIParticleRenderer.AddRenderer(this, index));
|
||||
}
|
||||
if (!m_Renderers[index])
|
||||
{
|
||||
m_Renderers[index] = UIParticleRenderer.AddRenderer(this, index);
|
||||
}
|
||||
return m_Renderers[index];
|
||||
}
|
||||
|
||||
private Camera GetBakeCamera()
|
||||
{
|
||||
if (!canvas) return Camera.main;
|
||||
|
||||
// World camera.
|
||||
var root = canvas.rootCanvas;
|
||||
if (root.renderMode != RenderMode.ScreenSpaceOverlay) return root.worldCamera ? root.worldCamera : Camera.main;
|
||||
|
||||
// Create ortho-camera.
|
||||
if (!_orthoCamera)
|
||||
{
|
||||
_orthoCamera = GetComponentInChildren<Camera>();
|
||||
if (!_orthoCamera)
|
||||
{
|
||||
var go = new GameObject("UIParticleOverlayCamera")
|
||||
{
|
||||
hideFlags = HideFlags.DontSave,
|
||||
};
|
||||
go.SetActive(false);
|
||||
go.transform.SetParent(transform, false);
|
||||
_orthoCamera = go.AddComponent<Camera>();
|
||||
_orthoCamera.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
var size = ((RectTransform)root.transform).rect.size;
|
||||
_orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor;
|
||||
_orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity);
|
||||
_orthoCamera.orthographic = true;
|
||||
_orthoCamera.farClipPlane = 2000f;
|
||||
|
||||
return _orthoCamera;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.coffee.ui-particle/Scripts/UIParticle.cs.meta
Normal file
11
Packages/com.coffee.ui-particle/Scripts/UIParticle.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16f0b0b6d0b7542bfbd20a3e05b04ff1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
204
Packages/com.coffee.ui-particle/Scripts/UIParticleAttractor.cs
Normal file
204
Packages/com.coffee.ui-particle/Scripts/UIParticleAttractor.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using UnityEngine;
|
||||
using Coffee.UIParticleExtensions;
|
||||
using UnityEngine.Events;
|
||||
using System;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
[ExecuteAlways]
|
||||
public class UIParticleAttractor : MonoBehaviour
|
||||
{
|
||||
public enum Movement
|
||||
{
|
||||
Linear,
|
||||
Smooth,
|
||||
Sphere,
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private ParticleSystem m_ParticleSystem;
|
||||
|
||||
[Range(0.1f, 10f)]
|
||||
[SerializeField]
|
||||
private float m_DestinationRadius = 1;
|
||||
|
||||
[Range(0f, 0.95f)]
|
||||
[SerializeField]
|
||||
private float m_DelayRate = 0;
|
||||
|
||||
[Range(0.001f, 100f)]
|
||||
[SerializeField]
|
||||
private float m_MaxSpeed = 1;
|
||||
|
||||
[SerializeField]
|
||||
private Movement m_Movement;
|
||||
|
||||
[SerializeField]
|
||||
private UnityEvent m_OnAttracted;
|
||||
|
||||
public float delay
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_DelayRate;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_DelayRate = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float maxSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_MaxSpeed;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_MaxSpeed = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Movement movement
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Movement;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_Movement = value;
|
||||
}
|
||||
}
|
||||
|
||||
private UIParticle _uiParticle;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (m_ParticleSystem == null)
|
||||
{
|
||||
Debug.LogError("No particle system attached to particle attractor script", this);
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_uiParticle = m_ParticleSystem.GetComponentInParent<UIParticle>();
|
||||
if (_uiParticle && !_uiParticle.particles.Contains(m_ParticleSystem))
|
||||
{
|
||||
_uiParticle = null;
|
||||
}
|
||||
UIParticleUpdater.Register(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_uiParticle = null;
|
||||
UIParticleUpdater.Unregister(this);
|
||||
}
|
||||
|
||||
internal void Attract()
|
||||
{
|
||||
if (m_ParticleSystem == null) return;
|
||||
|
||||
var count = m_ParticleSystem.particleCount;
|
||||
if (count == 0) return;
|
||||
|
||||
var particles = ParticleSystemExtensions.GetParticleArray(count);
|
||||
m_ParticleSystem.GetParticles(particles, count);
|
||||
|
||||
var dstPos = GetDestinationPosition();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
// Attracted
|
||||
var p = particles[i];
|
||||
if (0f < p.remainingLifetime && Vector3.Distance(p.position, dstPos) < m_DestinationRadius)
|
||||
{
|
||||
p.remainingLifetime = 0f;
|
||||
particles[i] = p;
|
||||
|
||||
if (m_OnAttracted != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_OnAttracted.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calc attracting time
|
||||
var delayTime = p.startLifetime * m_DelayRate;
|
||||
var duration = p.startLifetime - delayTime;
|
||||
var time = Mathf.Max(0, p.startLifetime - p.remainingLifetime - delayTime);
|
||||
|
||||
// Delay
|
||||
if (time <= 0) continue;
|
||||
|
||||
// Attract
|
||||
p.position = GetAttractedPosition(p.position, dstPos, duration, time);
|
||||
p.velocity *= 0.5f;
|
||||
particles[i] = p;
|
||||
}
|
||||
|
||||
m_ParticleSystem.SetParticles(particles, count);
|
||||
}
|
||||
|
||||
private Vector3 GetDestinationPosition()
|
||||
{
|
||||
var isUI = _uiParticle && _uiParticle.enabled;
|
||||
var psPos = m_ParticleSystem.transform.position;
|
||||
var attractorPos = transform.position;
|
||||
var dstPos = attractorPos;
|
||||
if (m_ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local)
|
||||
{
|
||||
dstPos = m_ParticleSystem.transform.InverseTransformPoint(dstPos);
|
||||
if (isUI)
|
||||
{
|
||||
dstPos = dstPos.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying && isUI)
|
||||
{
|
||||
var diff = dstPos - psPos;
|
||||
diff = diff.GetScaled(_uiParticle.transform.localScale, _uiParticle.scale3D.Inverse());
|
||||
return psPos + diff;
|
||||
}
|
||||
#endif
|
||||
if (isUI)
|
||||
{
|
||||
dstPos.Scale(_uiParticle.transform.localScale);
|
||||
dstPos.Scale(_uiParticle.scale3D.Inverse());
|
||||
}
|
||||
}
|
||||
return dstPos;
|
||||
}
|
||||
|
||||
private Vector3 GetAttractedPosition(Vector3 current, Vector3 target, float duration, float time)
|
||||
{
|
||||
var speed = m_MaxSpeed;
|
||||
switch (m_Movement)
|
||||
{
|
||||
case Movement.Linear:
|
||||
speed /= duration;
|
||||
break;
|
||||
case Movement.Smooth:
|
||||
target = Vector3.Lerp(current, target, time / duration);
|
||||
break;
|
||||
case Movement.Sphere:
|
||||
target = Vector3.Slerp(current, target, time / duration);
|
||||
break;
|
||||
}
|
||||
|
||||
return Vector3.MoveTowards(current, target, speed);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00e55ae1441ff4583859c55384964d86
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
596
Packages/com.coffee.ui-particle/Scripts/UIParticleRenderer.cs
Normal file
596
Packages/com.coffee.ui-particle/Scripts/UIParticleRenderer.cs
Normal file
@@ -0,0 +1,596 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Coffee.UIParticleExtensions;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Rendering;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(RectTransform))]
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
[AddComponentMenu("")]
|
||||
internal class UIParticleRenderer : MaskableGraphic
|
||||
{
|
||||
private static readonly CombineInstance[] s_CombineInstances = new CombineInstance[] { new CombineInstance() };
|
||||
private static readonly List<Material> s_Materials = new List<Material>(2);
|
||||
private static MaterialPropertyBlock s_Mpb;
|
||||
private static readonly List<UIParticleRenderer> s_Renderers = new List<UIParticleRenderer>();
|
||||
private static readonly Vector3[] s_Corners = new Vector3[4];
|
||||
|
||||
private ParticleSystemRenderer _renderer;
|
||||
private ParticleSystem _particleSystem;
|
||||
private int _prevParticleCount = 0;
|
||||
private UIParticle _parent;
|
||||
private int _index;
|
||||
private bool _isTrail;
|
||||
private Material _modifiedMaterial;
|
||||
private Vector3 _prevScale;
|
||||
private Vector3 _prevPsPos;
|
||||
private Vector2Int _prevScreenSize;
|
||||
private bool _delay = false;
|
||||
private bool _prewarm = false;
|
||||
private Material _currentMaterialForRendering;
|
||||
private Bounds _lastBounds;
|
||||
|
||||
public override Texture mainTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isTrail ? null : _particleSystem.GetTextureForSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool raycastTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Rect rootCanvasRect
|
||||
{
|
||||
get
|
||||
{
|
||||
s_Corners[0] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.min.y, 0);
|
||||
s_Corners[1] = transform.TransformPoint(_lastBounds.min.x, _lastBounds.max.y, 0);
|
||||
s_Corners[2] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.max.y, 0);
|
||||
s_Corners[3] = transform.TransformPoint(_lastBounds.max.x, _lastBounds.min.y, 0);
|
||||
if (canvas)
|
||||
{
|
||||
var worldToLocalMatrix = canvas.rootCanvas.transform.worldToLocalMatrix;
|
||||
for (var i = 0; i < 4; ++i)
|
||||
s_Corners[i] = worldToLocalMatrix.MultiplyPoint(s_Corners[i]);
|
||||
}
|
||||
var corner1 = (Vector2) s_Corners[0];
|
||||
var corner2 = (Vector2) s_Corners[0];
|
||||
for (var i = 1; i < 4; ++i)
|
||||
{
|
||||
if (s_Corners[i].x < corner1.x)
|
||||
corner1.x = s_Corners[i].x;
|
||||
else if (s_Corners[i].x > corner2.x)
|
||||
corner2.x = s_Corners[i].x;
|
||||
if (s_Corners[i].y < corner1.y)
|
||||
corner1.y = s_Corners[i].y;
|
||||
else if (s_Corners[i].y > corner2.y)
|
||||
corner2.y = s_Corners[i].y;
|
||||
}
|
||||
return new Rect(corner1, corner2 - corner1);
|
||||
}
|
||||
}
|
||||
|
||||
public static UIParticleRenderer AddRenderer(UIParticle parent, int index)
|
||||
{
|
||||
// Create renderer object.
|
||||
var go = new GameObject("UIParticleRenderer", typeof(UIParticleRenderer))
|
||||
{
|
||||
hideFlags = HideFlags.DontSave,
|
||||
layer = parent.gameObject.layer,
|
||||
};
|
||||
|
||||
// Set parent.
|
||||
var transform = go.transform;
|
||||
transform.SetParent(parent.transform, false);
|
||||
transform.localPosition = Vector3.zero;
|
||||
transform.localRotation = Quaternion.identity;
|
||||
transform.localScale = Vector3.one;
|
||||
|
||||
// Add renderer component.
|
||||
var renderer = go.GetComponent<UIParticleRenderer>();
|
||||
renderer._parent = parent;
|
||||
renderer._index = index;
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform material modification in this function.
|
||||
/// </summary>
|
||||
public override Material GetModifiedMaterial(Material baseMaterial)
|
||||
{
|
||||
_currentMaterialForRendering = null;
|
||||
|
||||
if (!IsActive()) return baseMaterial;
|
||||
|
||||
var modifiedMaterial = base.GetModifiedMaterial(baseMaterial);
|
||||
|
||||
//
|
||||
var texture = mainTexture;
|
||||
if (texture == null && _parent.m_AnimatableProperties.Length == 0)
|
||||
{
|
||||
ModifiedMaterial.Remove(_modifiedMaterial);
|
||||
_modifiedMaterial = null;
|
||||
return modifiedMaterial;
|
||||
}
|
||||
|
||||
//
|
||||
var id = _parent.m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID();
|
||||
modifiedMaterial = ModifiedMaterial.Add(modifiedMaterial, texture, id);
|
||||
ModifiedMaterial.Remove(_modifiedMaterial);
|
||||
_modifiedMaterial = modifiedMaterial;
|
||||
|
||||
return modifiedMaterial;
|
||||
}
|
||||
|
||||
public void Clear(int index = -1)
|
||||
{
|
||||
if (_renderer)
|
||||
{
|
||||
_renderer.enabled = true;
|
||||
}
|
||||
_parent = null;
|
||||
_particleSystem = null;
|
||||
_renderer = null;
|
||||
_prevParticleCount = 0;
|
||||
if (0 <= index)
|
||||
{
|
||||
_index = index;
|
||||
}
|
||||
//_emitter = null;
|
||||
if (this && isActiveAndEnabled)
|
||||
{
|
||||
material = null;
|
||||
workerMesh.Clear();
|
||||
canvasRenderer.SetMesh(workerMesh);
|
||||
_lastBounds = new Bounds();
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(UIParticle parent, ParticleSystem particleSystem, bool isTrail)
|
||||
{
|
||||
_parent = parent;
|
||||
maskable = parent.maskable;
|
||||
|
||||
gameObject.layer = parent.gameObject.layer;
|
||||
|
||||
_particleSystem = particleSystem;
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
#endif
|
||||
if (_particleSystem.isPlaying)
|
||||
{
|
||||
_particleSystem.Clear();
|
||||
_particleSystem.Pause();
|
||||
}
|
||||
_prewarm = _particleSystem.main.prewarm;
|
||||
|
||||
_renderer = particleSystem.GetComponent<ParticleSystemRenderer>();
|
||||
_renderer.enabled = false;
|
||||
|
||||
//_emitter = emitter;
|
||||
_isTrail = isTrail;
|
||||
|
||||
_renderer.GetSharedMaterials(s_Materials);
|
||||
material = s_Materials[isTrail ? 1 : 0];
|
||||
s_Materials.Clear();
|
||||
|
||||
// Support sprite.
|
||||
var tsa = particleSystem.textureSheetAnimation;
|
||||
if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0)
|
||||
tsa.uvChannelMask = UVChannelFlags.UV0;
|
||||
|
||||
_prevScale = GetWorldScale();
|
||||
_prevPsPos = _particleSystem.transform.position;
|
||||
_prevScreenSize = new Vector2Int(Screen.width, Screen.height);
|
||||
_delay = true;
|
||||
_prevParticleCount = 0;
|
||||
|
||||
canvasRenderer.SetTexture(null);
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public void UpdateMesh(Camera bakeCamera)
|
||||
{
|
||||
// No particle to render: Clear mesh.
|
||||
if (
|
||||
!isActiveAndEnabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera
|
||||
|| _parent.meshSharing == UIParticle.MeshSharing.Reprica
|
||||
|| !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible.
|
||||
|| (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle.
|
||||
|| (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled.
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
|| canvasRenderer.GetInheritedAlpha() < 0.01f // #102: Do not bake particle system to mesh when the alpha is zero.
|
||||
#endif
|
||||
)
|
||||
{
|
||||
Profiler.BeginSample("[UIParticleRenderer] Clear Mesh");
|
||||
workerMesh.Clear();
|
||||
canvasRenderer.SetMesh(workerMesh);
|
||||
_lastBounds = new Bounds();
|
||||
Profiler.EndSample();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var main = _particleSystem.main;
|
||||
var scale = GetWorldScale();
|
||||
var psPos = _particleSystem.transform.position;
|
||||
|
||||
// Simulate particles.
|
||||
Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles");
|
||||
if (!_isTrail && _parent.canSimulate)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
SimulateForEditor(psPos - _prevPsPos, scale);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ResolveResolutionChange(psPos, scale);
|
||||
Simulate(scale, _parent.isPaused || _delay);
|
||||
|
||||
if (_delay && !_parent.isPaused)
|
||||
{
|
||||
Simulate(scale, _parent.isPaused);
|
||||
}
|
||||
|
||||
// When the ParticleSystem simulation is complete, stop it.
|
||||
if (!main.loop && main.duration <= _particleSystem.time && (_particleSystem.IsAlive() || _particleSystem.particleCount == 0))
|
||||
{
|
||||
_particleSystem.Stop(false);
|
||||
}
|
||||
}
|
||||
_prevScale = scale;
|
||||
_prevPsPos = psPos;
|
||||
_delay = false;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
// Bake mesh.
|
||||
Profiler.BeginSample("[UIParticleRenderer] Bake Mesh");
|
||||
if (_isTrail && _parent.canSimulate)
|
||||
{
|
||||
_renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true);
|
||||
}
|
||||
else if (_renderer.CanBakeMesh())
|
||||
{
|
||||
_renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_CombineInstances[0].mesh.Clear(false);
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
// Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local)
|
||||
Profiler.BeginSample("[UIParticleRenderer] Combine Mesh");
|
||||
if (_parent.canSimulate)
|
||||
{
|
||||
if (_parent.absoluteMode)
|
||||
{
|
||||
s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
var diff = _particleSystem.transform.position - _parent.transform.position;
|
||||
s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * Matrix4x4.Translate(diff.GetScaled(scale - Vector3.one)) * GetWorldMatrix(psPos, scale);
|
||||
}
|
||||
workerMesh.CombineMeshes(s_CombineInstances, true, true);
|
||||
|
||||
workerMesh.RecalculateBounds();
|
||||
var bounds = workerMesh.bounds;
|
||||
var center = bounds.center;
|
||||
center.z = 0;
|
||||
bounds.center = center;
|
||||
var extents = bounds.extents;
|
||||
extents.z = 0;
|
||||
bounds.extents = extents;
|
||||
workerMesh.bounds = bounds;
|
||||
_lastBounds = bounds;
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
|
||||
// Get grouped renderers.
|
||||
s_Renderers.Clear();
|
||||
if (_parent.useMeshSharing)
|
||||
{
|
||||
UIParticleUpdater.GetGroupedRenderers(_parent.groupId, _index, s_Renderers);
|
||||
}
|
||||
|
||||
// Set mesh to the CanvasRenderer.
|
||||
Profiler.BeginSample("[UIParticleRenderer] Set Mesh");
|
||||
for (int i = 0; i < s_Renderers.Count; i++)
|
||||
{
|
||||
if (s_Renderers[i] == this) continue;
|
||||
s_Renderers[i].canvasRenderer.SetMesh(workerMesh);
|
||||
s_Renderers[i]._lastBounds = _lastBounds;
|
||||
}
|
||||
|
||||
if (!_parent.canRender)
|
||||
{
|
||||
workerMesh.Clear();
|
||||
}
|
||||
canvasRenderer.SetMesh(workerMesh);
|
||||
Profiler.EndSample();
|
||||
|
||||
// Update animatable material properties.
|
||||
Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties");
|
||||
UpdateMaterialProperties();
|
||||
if (!_parent.useMeshSharing)
|
||||
{
|
||||
if (!_currentMaterialForRendering)
|
||||
{
|
||||
_currentMaterialForRendering = materialForRendering;
|
||||
}
|
||||
for (int i = 0; i < s_Renderers.Count; i++)
|
||||
{
|
||||
if (s_Renderers[i] == this) continue;
|
||||
|
||||
s_Renderers[i].canvasRenderer.materialCount = 1;
|
||||
s_Renderers[i].canvasRenderer.SetMaterial(_currentMaterialForRendering, 0);
|
||||
}
|
||||
}
|
||||
Profiler.EndSample();
|
||||
|
||||
s_Renderers.Clear();
|
||||
}
|
||||
|
||||
internal void UpdateParticleCount()
|
||||
{
|
||||
if (!_particleSystem) return;
|
||||
_prevParticleCount = _particleSystem.particleCount;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (!s_CombineInstances[0].mesh)
|
||||
{
|
||||
s_CombineInstances[0].mesh = new Mesh()
|
||||
{
|
||||
name = "[UIParticleRenderer] Combine Instance Mesh",
|
||||
hideFlags = HideFlags.HideAndDontSave,
|
||||
};
|
||||
}
|
||||
_currentMaterialForRendering = null;
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
ModifiedMaterial.Remove(_modifiedMaterial);
|
||||
_modifiedMaterial = null;
|
||||
_currentMaterialForRendering = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
|
||||
/// </summary>
|
||||
protected override void UpdateGeometry()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Cull(Rect clipRect, bool validRect)
|
||||
{
|
||||
var cull = _lastBounds.extents == Vector3.zero || !validRect || !clipRect.Overlaps(rootCanvasRect, true);
|
||||
if (canvasRenderer.cull == cull) return;
|
||||
|
||||
canvasRenderer.cull = cull;
|
||||
UISystemProfilerApi.AddMarker("MaskableGraphic.cullingChanged", this);
|
||||
onCullStateChanged.Invoke(cull);
|
||||
OnCullingChanged();
|
||||
}
|
||||
|
||||
private Vector3 GetWorldScale()
|
||||
{
|
||||
Profiler.BeginSample("[UIParticleRenderer] GetWorldScale");
|
||||
var scale = _parent.scale3D;
|
||||
//if (_parent.uiScaling)
|
||||
{
|
||||
scale.Scale(_parent.transform.localScale.Inverse());
|
||||
}
|
||||
//else if (_parent.scalingMode == UIParticle.ScalingMode.UI && _particleSystem.main.scalingMode != ParticleSystemScalingMode.Hierarchy)
|
||||
//{
|
||||
// var gscale = _parent.transform.lossyScale.GetScaled(canvas.transform.lossyScale.Inverse());
|
||||
// scale.Scale(gscale * canvas.scaleFactor);
|
||||
//}
|
||||
Profiler.EndSample();
|
||||
return scale;
|
||||
}
|
||||
|
||||
private Matrix4x4 GetWorldMatrix(Vector3 psPos, Vector3 scale)
|
||||
{
|
||||
var space = _particleSystem.GetActualSimulationSpace();
|
||||
if (_isTrail && _particleSystem.trails.worldSpace)
|
||||
{
|
||||
space = ParticleSystemSimulationSpace.World;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
switch (space)
|
||||
{
|
||||
case ParticleSystemSimulationSpace.World:
|
||||
return Matrix4x4.Translate(psPos)
|
||||
* Matrix4x4.Scale(scale)
|
||||
* Matrix4x4.Translate(-psPos);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (space)
|
||||
{
|
||||
case ParticleSystemSimulationSpace.Local:
|
||||
return Matrix4x4.Translate(psPos)
|
||||
* Matrix4x4.Scale(scale);
|
||||
case ParticleSystemSimulationSpace.World:
|
||||
return Matrix4x4.Scale(scale);
|
||||
case ParticleSystemSimulationSpace.Custom:
|
||||
return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale))
|
||||
//* Matrix4x4.Translate(wpos)
|
||||
* Matrix4x4.Scale(scale)
|
||||
//* Matrix4x4.Translate(-wpos)
|
||||
;
|
||||
default:
|
||||
throw new System.NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For world simulation, interpolate particle positions when the screen size is changed.
|
||||
/// </summary>
|
||||
/// <param name="psPos"></param>
|
||||
/// <param name="scale"></param>
|
||||
private void ResolveResolutionChange(Vector3 psPos, Vector3 scale)
|
||||
{
|
||||
var screenSize = new Vector2Int(Screen.width, Screen.height);
|
||||
//if ((_prevScreenSize != screenSize || _prevScale != scale) && _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World && _parent.uiScaling)
|
||||
if ((_prevScreenSize != screenSize || _prevScale != scale) && _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World)
|
||||
{
|
||||
// Update particle array size and get particles.
|
||||
var size = _particleSystem.particleCount;
|
||||
var particles = ParticleSystemExtensions.GetParticleArray(size);
|
||||
_particleSystem.GetParticles(particles, size);
|
||||
|
||||
// Resolusion resolver:
|
||||
// (psPos / scale) / (prevPsPos / prevScale) -> psPos * scale.inv * prevPsPos.inv * prevScale
|
||||
var modifier = psPos.GetScaled(scale.Inverse(), _prevPsPos.Inverse(), _prevScale);
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var particle = particles[i];
|
||||
particle.position = particle.position.GetScaled(modifier);
|
||||
particles[i] = particle;
|
||||
}
|
||||
_particleSystem.SetParticles(particles, size);
|
||||
|
||||
// Delay: Do not progress in the frame where the resolution has been changed.
|
||||
_delay = true;
|
||||
_prevScale = scale;
|
||||
_prevPsPos = psPos;
|
||||
}
|
||||
_prevScreenSize = screenSize;
|
||||
}
|
||||
|
||||
private void Simulate(Vector3 scale, bool paused)
|
||||
{
|
||||
var main = _particleSystem.main;
|
||||
var deltaTime = paused
|
||||
? 0
|
||||
: main.useUnscaledTime
|
||||
? Time.unscaledDeltaTime
|
||||
: Time.deltaTime;
|
||||
|
||||
// Prewarm:
|
||||
if (0 < deltaTime && _prewarm)
|
||||
{
|
||||
deltaTime += main.duration;
|
||||
_prewarm = false;
|
||||
}
|
||||
|
||||
// Emitted particles found.
|
||||
if (_prevParticleCount != _particleSystem.particleCount)
|
||||
{
|
||||
var size = _particleSystem.particleCount;
|
||||
var particles = ParticleSystemExtensions.GetParticleArray(size);
|
||||
_particleSystem.GetParticles(particles, size);
|
||||
for (var i = _prevParticleCount; i < size; i++)
|
||||
{
|
||||
var p = particles[i];
|
||||
p.position = p.position.GetScaled(scale.Inverse());
|
||||
particles[i] = p;
|
||||
}
|
||||
|
||||
_particleSystem.SetParticles(particles, size);
|
||||
}
|
||||
|
||||
// get world position.
|
||||
var psTransform = _particleSystem.transform;
|
||||
var originWorldPosition = psTransform.position;
|
||||
var originWorldRotation = psTransform.rotation;
|
||||
|
||||
var emission = _particleSystem.emission;
|
||||
var rateOverDistance = emission.enabled && 0 < emission.rateOverDistance.constant && 0 < emission.rateOverDistanceMultiplier;
|
||||
if (rateOverDistance)
|
||||
{
|
||||
// (For rate-over-distance emission,) Move to previous scaled position, simulate (delta = 0).
|
||||
Vector3 prevScaledPos = _prevPsPos.GetScaled(_prevScale.Inverse());
|
||||
psTransform.SetPositionAndRotation(prevScaledPos, originWorldRotation);
|
||||
_particleSystem.Simulate(0, false, false, false);
|
||||
}
|
||||
|
||||
// Move to scaled position, simulate, revert to origin position.
|
||||
var scaledPos = originWorldPosition.GetScaled(scale.Inverse());
|
||||
psTransform.SetPositionAndRotation(scaledPos, originWorldRotation);
|
||||
_particleSystem.Simulate(deltaTime, false, false, false);
|
||||
psTransform.SetPositionAndRotation(originWorldPosition, originWorldRotation);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void SimulateForEditor(Vector3 diffPos, Vector3 scale)
|
||||
{
|
||||
// Extra world simulation.
|
||||
if (_particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < Vector3.SqrMagnitude(diffPos))
|
||||
{
|
||||
Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation");
|
||||
diffPos.x *= 1f - 1f / Mathf.Max(0.001f, scale.x);
|
||||
diffPos.y *= 1f - 1f / Mathf.Max(0.001f, scale.y);
|
||||
diffPos.z *= 1f - 1f / Mathf.Max(0.001f, scale.z);
|
||||
|
||||
var size = _particleSystem.particleCount;
|
||||
var particles = ParticleSystemExtensions.GetParticleArray(size);
|
||||
_particleSystem.GetParticles(particles, size);
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var p = particles[i];
|
||||
p.position += diffPos;
|
||||
particles[i] = p;
|
||||
}
|
||||
|
||||
_particleSystem.SetParticles(particles, size);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private void UpdateMaterialProperties()
|
||||
{
|
||||
if (_parent.m_AnimatableProperties.Length == 0) return;
|
||||
|
||||
if (s_Mpb == null)
|
||||
s_Mpb = new MaterialPropertyBlock();
|
||||
|
||||
_renderer.GetPropertyBlock(s_Mpb);
|
||||
if (s_Mpb.isEmpty) return;
|
||||
|
||||
// #41: Copy the value from MaterialPropertyBlock to CanvasRenderer
|
||||
if (!_modifiedMaterial) return;
|
||||
|
||||
foreach (var ap in _parent.m_AnimatableProperties)
|
||||
{
|
||||
ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb);
|
||||
}
|
||||
|
||||
s_Mpb.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e66d2d1ba43c4cc4bc3e754e403297b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
135
Packages/com.coffee.ui-particle/Scripts/UIParticleUpdater.cs
Normal file
135
Packages/com.coffee.ui-particle/Scripts/UIParticleUpdater.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
internal static class UIParticleUpdater
|
||||
{
|
||||
static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
|
||||
static readonly List<UIParticleAttractor> s_ActiveAttractors = new List<UIParticleAttractor>();
|
||||
static readonly HashSet<int> s_UpdatedGroupIds = new HashSet<int>();
|
||||
private static int frameCount = 0;
|
||||
|
||||
public static int uiParticleCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return s_ActiveParticles.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Register(UIParticle particle)
|
||||
{
|
||||
if (!particle) return;
|
||||
s_ActiveParticles.Add(particle);
|
||||
}
|
||||
|
||||
public static void Unregister(UIParticle particle)
|
||||
{
|
||||
if (!particle) return;
|
||||
s_ActiveParticles.Remove(particle);
|
||||
}
|
||||
|
||||
public static void Register(UIParticleAttractor attractor)
|
||||
{
|
||||
if (!attractor) return;
|
||||
s_ActiveAttractors.Add(attractor);
|
||||
}
|
||||
|
||||
public static void Unregister(UIParticleAttractor attractor)
|
||||
{
|
||||
if (!attractor) return;
|
||||
s_ActiveAttractors.Remove(attractor);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoadMethod]
|
||||
#endif
|
||||
[RuntimeInitializeOnLoadMethod]
|
||||
private static void InitializeOnLoad()
|
||||
{
|
||||
Canvas.willRenderCanvases -= Refresh;
|
||||
Canvas.willRenderCanvases += Refresh;
|
||||
}
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
// Do not allow it to be called in the same frame.
|
||||
if (frameCount == Time.frameCount) return;
|
||||
frameCount = Time.frameCount;
|
||||
|
||||
// Simulate -> Primary
|
||||
for (var i = 0; i < s_ActiveParticles.Count; i++)
|
||||
{
|
||||
var uip = s_ActiveParticles[i];
|
||||
if (!uip || !uip.canvas || !uip.isPrimary || s_UpdatedGroupIds.Contains(uip.groupId)) continue;
|
||||
|
||||
s_UpdatedGroupIds.Add(uip.groupId);
|
||||
uip.UpdateTransformScale();
|
||||
uip.UpdateRenderers();
|
||||
}
|
||||
|
||||
// Simulate -> Others
|
||||
for (var i = 0; i < s_ActiveParticles.Count; i++)
|
||||
{
|
||||
var uip = s_ActiveParticles[i];
|
||||
if (!uip || !uip.canvas) continue;
|
||||
|
||||
uip.UpdateTransformScale();
|
||||
|
||||
if (!uip.useMeshSharing)
|
||||
{
|
||||
uip.UpdateRenderers();
|
||||
}
|
||||
else if (!s_UpdatedGroupIds.Contains(uip.groupId))
|
||||
{
|
||||
s_UpdatedGroupIds.Add(uip.groupId);
|
||||
uip.UpdateRenderers();
|
||||
}
|
||||
}
|
||||
|
||||
s_UpdatedGroupIds.Clear();
|
||||
|
||||
// Attract
|
||||
for (var i = 0; i < s_ActiveAttractors.Count; i++)
|
||||
{
|
||||
s_ActiveAttractors[i].Attract();
|
||||
}
|
||||
|
||||
// UpdateParticleCount.
|
||||
for (var i = 0; i < s_ActiveParticles.Count; i++)
|
||||
{
|
||||
var uip = s_ActiveParticles[i];
|
||||
if (!uip || !uip.canvas) continue;
|
||||
|
||||
uip.UpdateParticleCount();
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetGroupedRenderers(int groupId, int index, List<UIParticleRenderer> results)
|
||||
{
|
||||
results.Clear();
|
||||
for (var i = 0; i < s_ActiveParticles.Count; i++)
|
||||
{
|
||||
var uip = s_ActiveParticles[i];
|
||||
if (uip.useMeshSharing && uip.groupId == groupId)
|
||||
{
|
||||
results.Add(uip.GetRenderer(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static UIParticle GetPrimary(int groupId)
|
||||
{
|
||||
UIParticle primary = null;
|
||||
for (var i = 0; i < s_ActiveParticles.Count; i++)
|
||||
{
|
||||
var uip = s_ActiveParticles[i];
|
||||
if (!uip.useMeshSharing || uip.groupId != groupId) continue;
|
||||
if (uip.isPrimary) return uip;
|
||||
if (!primary && uip.canSimulate) primary = uip;
|
||||
}
|
||||
return primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0e708dc2e3034ba9a5c51db4252c7e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: -100
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
186
Packages/com.coffee.ui-particle/Scripts/Utils.cs
Normal file
186
Packages/com.coffee.ui-particle/Scripts/Utils.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Coffee.UIParticleExtensions
|
||||
{
|
||||
public static class Vector3Extensions
|
||||
{
|
||||
public static Vector3 Inverse(this Vector3 self)
|
||||
{
|
||||
self.x = Mathf.Approximately(self.x, 0) ? 1 : 1 / self.x;
|
||||
self.y = Mathf.Approximately(self.y, 0) ? 1 : 1 / self.y;
|
||||
self.z = Mathf.Approximately(self.z, 0) ? 1 : 1 / self.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
public static Vector3 GetScaled(this Vector3 self, Vector3 other1)
|
||||
{
|
||||
self.Scale(other1);
|
||||
return self;
|
||||
}
|
||||
|
||||
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2)
|
||||
{
|
||||
self.Scale(other1);
|
||||
self.Scale(other2);
|
||||
return self;
|
||||
}
|
||||
|
||||
public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2, Vector3 other3)
|
||||
{
|
||||
self.Scale(other1);
|
||||
self.Scale(other2);
|
||||
self.Scale(other3);
|
||||
return self;
|
||||
}
|
||||
|
||||
public static bool IsVisible(this Vector3 self)
|
||||
{
|
||||
return 0 < Mathf.Abs(self.x * self.y * self.z);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SpriteExtensions
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
private static Type tSpriteEditorExtension =
|
||||
Type.GetType("UnityEditor.Experimental.U2D.SpriteEditorExtension, UnityEditor")
|
||||
?? Type.GetType("UnityEditor.U2D.SpriteEditorExtension, UnityEditor");
|
||||
|
||||
private static MethodInfo miGetActiveAtlasTexture = tSpriteEditorExtension
|
||||
.GetMethod("GetActiveAtlasTexture", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
public static Texture2D GetActualTexture(this Sprite self)
|
||||
{
|
||||
if (!self) return null;
|
||||
|
||||
if (Application.isPlaying) return self.texture;
|
||||
var ret = miGetActiveAtlasTexture.Invoke(null, new[] { self }) as Texture2D;
|
||||
return ret ? ret : self.texture;
|
||||
}
|
||||
#else
|
||||
internal static Texture2D GetActualTexture(this Sprite self)
|
||||
{
|
||||
return self ? self.texture : null;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class ParticleSystemExtensions
|
||||
{
|
||||
private static ParticleSystem.Particle[] s_TmpParticles = new ParticleSystem.Particle[2048];
|
||||
|
||||
public static ParticleSystem.Particle[] GetParticleArray(int size)
|
||||
{
|
||||
if (s_TmpParticles.Length < size)
|
||||
{
|
||||
while(s_TmpParticles.Length < size)
|
||||
{
|
||||
size = Mathf.NextPowerOfTwo(size);
|
||||
}
|
||||
s_TmpParticles = new ParticleSystem.Particle[size];
|
||||
}
|
||||
return s_TmpParticles;
|
||||
}
|
||||
|
||||
public static bool CanBakeMesh(this ParticleSystemRenderer self)
|
||||
{
|
||||
// #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh`
|
||||
if (self.renderMode == ParticleSystemRenderMode.Mesh && self.mesh == null) return false;
|
||||
|
||||
// #61: When `ParticleSystem.RenderMode = None`, an error occurs
|
||||
if (self.renderMode == ParticleSystemRenderMode.None) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ParticleSystemSimulationSpace GetActualSimulationSpace(this ParticleSystem self)
|
||||
{
|
||||
var main = self.main;
|
||||
var space = main.simulationSpace;
|
||||
if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace)
|
||||
space = ParticleSystemSimulationSpace.Local;
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
public static void SortForRendering(this List<ParticleSystem> self, Transform transform, bool sortByMaterial)
|
||||
{
|
||||
self.Sort((a, b) =>
|
||||
{
|
||||
var tr = transform;
|
||||
var aRenderer = a.GetComponent<ParticleSystemRenderer>();
|
||||
var bRenderer = b.GetComponent<ParticleSystemRenderer>();
|
||||
|
||||
// Render queue: ascending
|
||||
var aMat = aRenderer.sharedMaterial ?? aRenderer.trailMaterial;
|
||||
var bMat = bRenderer.sharedMaterial ?? bRenderer.trailMaterial;
|
||||
if (!aMat && !bMat) return 0;
|
||||
if (!aMat) return -1;
|
||||
if (!bMat) return 1;
|
||||
|
||||
if (sortByMaterial)
|
||||
return aMat.GetInstanceID() - bMat.GetInstanceID();
|
||||
|
||||
if (aMat.renderQueue != bMat.renderQueue)
|
||||
return aMat.renderQueue - bMat.renderQueue;
|
||||
|
||||
// Sorting layer: ascending
|
||||
if (aRenderer.sortingLayerID != bRenderer.sortingLayerID)
|
||||
return SortingLayer.GetLayerValueFromID(aRenderer.sortingLayerID) - SortingLayer.GetLayerValueFromID(bRenderer.sortingLayerID);
|
||||
|
||||
// Sorting order: ascending
|
||||
if (aRenderer.sortingOrder != bRenderer.sortingOrder)
|
||||
return aRenderer.sortingOrder - bRenderer.sortingOrder;
|
||||
|
||||
// Z position & sortingFudge: descending
|
||||
var aTransform = a.transform;
|
||||
var bTransform = b.transform;
|
||||
var aPos = tr.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge;
|
||||
var bPos = tr.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge;
|
||||
if (!Mathf.Approximately(aPos, bPos))
|
||||
return (int)Mathf.Sign(bPos - aPos);
|
||||
|
||||
return (int)Mathf.Sign(GetIndex(self, a) - GetIndex(self, b));
|
||||
});
|
||||
}
|
||||
|
||||
private static int GetIndex(IList<ParticleSystem> list, Object ps)
|
||||
{
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (list[i].GetInstanceID() == ps.GetInstanceID()) return i;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Texture2D GetTextureForSprite(this ParticleSystem self)
|
||||
{
|
||||
if (!self) return null;
|
||||
|
||||
// Get sprite's texture.
|
||||
var tsaModule = self.textureSheetAnimation;
|
||||
if (!tsaModule.enabled || tsaModule.mode != ParticleSystemAnimationMode.Sprites) return null;
|
||||
|
||||
for (var i = 0; i < tsaModule.spriteCount; i++)
|
||||
{
|
||||
var sprite = tsaModule.GetSprite(i);
|
||||
if (!sprite) continue;
|
||||
|
||||
return sprite.GetActualTexture();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Exec(this List<ParticleSystem> self, Action<ParticleSystem> action)
|
||||
{
|
||||
self.RemoveAll(p => !p);
|
||||
self.ForEach(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Packages/com.coffee.ui-particle/Scripts/Utils.cs.meta
Normal file
11
Packages/com.coffee.ui-particle/Scripts/Utils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d188d31b140094ebc84a9caafbc7ac71
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user