From eccb3ecb5e04e0b01d8920d7a674e18268142e5e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 19 Aug 2020 19:42:16 +0000 Subject: [PATCH] 3.0.0-preview.18 # [3.0.0-preview.18](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.17...v3.0.0-preview.18) (2020-08-19) ### Bug Fixes * AsmdefEx is no longer required ([50e749c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/50e749c183def5e97affa7e6ae9f3ceb69247825)) * fix camera for baking mesh ([6395a4f](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/6395a4fa744a46551593185711d6545a94d8b456)) * support .Net Framework 3.5 (again) ([23fcb06](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/23fcb06bf9169ee160ccd8adb2cc3aab1a30186a)) ### Features * 3.0.0 updater ([f99292b](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f99292b9a15c9c085efacc0330d6b848669fadfa)) * add menu to create UIParticle ([14f1c78](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/14f1c782ff0f2b67d85d7c9ad0cf662da1dd1fc6)) * Combine baked meshes to improve performance ([633d058](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/633d058756fde776a7e5192a0f023adf6eb0ca7b)) * improve performance ([77c056a](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/77c056ad5f2918efe457883f3b0361f952600568)) * optimization for vertices transforms and adding node for trails ([e070e8d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e070e8d5ee205c25a1e3be5d3178821d4a8265d0)), closes [#75](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/75) * option to ignoring canvas scaling ([fe85fed](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/fe85fed3c0ad2881578ff68722863d65dfa4db7a)) * support 3d scaling ([42a84bc](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/42a84bc5e130aed3cf5e57dcd6a9d8dc94deb641)) * support custom simulation space ([a83e647](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a83e64761c008e88ff328a2609118806e97f19cf)), closes [#78](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/78) * support for particle systems including trail only ([f389d39](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f389d39953c85b97482b12d1c8578ecaeddacd18)), closes [#61](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/61) ### BREAKING CHANGES * The bake-task has changed significantly. It may look different from previous versions. --- CHANGELOG.md | 27 + Scripts/AnimatableProperty.cs | 56 ++ ...fEx.cs.meta => AnimatableProperty.cs.meta} | 2 +- Scripts/BakingCamera.cs | 119 +++ ...layCamera.cs.meta => BakingCamera.cs.meta} | 2 +- Scripts/Editor/AnimatedPropertiesEditor.cs | 114 +++ .../Editor/AnimatedPropertiesEditor.cs.meta | 11 + Scripts/Editor/AsmdefEx.cs | 510 ------------ .../Coffee.UIParticle.Editor.asmdef.meta | 2 +- Scripts/Editor/UIParticleEditor.cs | 237 ++---- Scripts/Editor/UIParticleMenu.cs | 66 +- Scripts/MeshHelper.cs | 85 ++ Scripts/MeshHelper.cs.meta | 11 + Scripts/SpriteExtensions.cs | 31 + Scripts/SpriteExtensions.cs.meta | 11 + Scripts/UIParticle.cs | 737 +++++------------- Scripts/UIParticleOverlayCamera.cs | 170 ---- Scripts/UIParticleUpdater.cs | 256 ++++++ Scripts/UIParticleUpdater.cs.meta | 11 + package.json | 2 +- 20 files changed, 1057 insertions(+), 1403 deletions(-) create mode 100644 Scripts/AnimatableProperty.cs rename Scripts/{Editor/AsmdefEx.cs.meta => AnimatableProperty.cs.meta} (83%) create mode 100644 Scripts/BakingCamera.cs rename Scripts/{UIParticleOverlayCamera.cs.meta => BakingCamera.cs.meta} (83%) create mode 100644 Scripts/Editor/AnimatedPropertiesEditor.cs create mode 100644 Scripts/Editor/AnimatedPropertiesEditor.cs.meta delete mode 100644 Scripts/Editor/AsmdefEx.cs create mode 100644 Scripts/MeshHelper.cs create mode 100644 Scripts/MeshHelper.cs.meta create mode 100644 Scripts/SpriteExtensions.cs create mode 100644 Scripts/SpriteExtensions.cs.meta delete mode 100644 Scripts/UIParticleOverlayCamera.cs create mode 100755 Scripts/UIParticleUpdater.cs create mode 100644 Scripts/UIParticleUpdater.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6ff9d..45913a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# [3.0.0-preview.18](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.17...v3.0.0-preview.18) (2020-08-19) + + +### Bug Fixes + +* AsmdefEx is no longer required ([50e749c](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/50e749c183def5e97affa7e6ae9f3ceb69247825)) +* fix camera for baking mesh ([6395a4f](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/6395a4fa744a46551593185711d6545a94d8b456)) +* support .Net Framework 3.5 (again) ([23fcb06](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/23fcb06bf9169ee160ccd8adb2cc3aab1a30186a)) + + +### Features + +* 3.0.0 updater ([f99292b](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f99292b9a15c9c085efacc0330d6b848669fadfa)) +* add menu to create UIParticle ([14f1c78](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/14f1c782ff0f2b67d85d7c9ad0cf662da1dd1fc6)) +* Combine baked meshes to improve performance ([633d058](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/633d058756fde776a7e5192a0f023adf6eb0ca7b)) +* improve performance ([77c056a](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/77c056ad5f2918efe457883f3b0361f952600568)) +* optimization for vertices transforms and adding node for trails ([e070e8d](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/e070e8d5ee205c25a1e3be5d3178821d4a8265d0)), closes [#75](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/75) +* option to ignoring canvas scaling ([fe85fed](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/fe85fed3c0ad2881578ff68722863d65dfa4db7a)) +* support 3d scaling ([42a84bc](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/42a84bc5e130aed3cf5e57dcd6a9d8dc94deb641)) +* support custom simulation space ([a83e647](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/a83e64761c008e88ff328a2609118806e97f19cf)), closes [#78](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/78) +* support for particle systems including trail only ([f389d39](https://github.com/mob-sakai/ParticleEffectForUGUI/commit/f389d39953c85b97482b12d1c8578ecaeddacd18)), closes [#61](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/61) + + +### BREAKING CHANGES + +* The bake-task has changed significantly. It may look different from previous versions. + # [3.0.0-preview.17](https://github.com/mob-sakai/ParticleEffectForUGUI/compare/v3.0.0-preview.16...v3.0.0-preview.17) (2020-08-13) diff --git a/Scripts/AnimatableProperty.cs b/Scripts/AnimatableProperty.cs new file mode 100644 index 0000000..e5869e6 --- /dev/null +++ b/Scripts/AnimatableProperty.cs @@ -0,0 +1,56 @@ +using System; +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) + { + switch (type) + { + case ShaderPropertyType.Color: + material.SetColor(id, mpb.GetColor(id)); + break; + case ShaderPropertyType.Vector: + material.SetVector(id, mpb.GetVector(id)); + break; + case ShaderPropertyType.Float: + case ShaderPropertyType.Range: + material.SetFloat(id, mpb.GetFloat(id)); + break; + case ShaderPropertyType.Texture: + material.SetTexture(id, mpb.GetTexture(id)); + break; + } + } + + public void OnBeforeSerialize() + { + } + + public void OnAfterDeserialize() + { + id = Shader.PropertyToID(m_Name); + } + } +} diff --git a/Scripts/Editor/AsmdefEx.cs.meta b/Scripts/AnimatableProperty.cs.meta similarity index 83% rename from Scripts/Editor/AsmdefEx.cs.meta rename to Scripts/AnimatableProperty.cs.meta index 3e8920f..c49b44a 100644 --- a/Scripts/Editor/AsmdefEx.cs.meta +++ b/Scripts/AnimatableProperty.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0defeb6d13f61475eacce2b101cb2248 +guid: c434da72184404a0cbdf8e7a529a41bf MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/BakingCamera.cs b/Scripts/BakingCamera.cs new file mode 100644 index 0000000..b848d3e --- /dev/null +++ b/Scripts/BakingCamera.cs @@ -0,0 +1,119 @@ +using System; +using UnityEngine; + +namespace Coffee.UIExtensions +{ + internal class BakingCamera : MonoBehaviour + { + static BakingCamera s_Instance; + +#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR + static BakingCamera s_InstanceForPrefab; + + private static BakingCamera InstanceForPrefab + { + get + { + // If current scene is prefab mode, create OverlayCamera for editor. + var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); + if (prefabStage == null || !prefabStage.scene.isLoaded) return null; + if (s_InstanceForPrefab) return s_InstanceForPrefab; + + s_InstanceForPrefab = Create(); + s_InstanceForPrefab.name += " (For Prefab Stage)"; + UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(s_InstanceForPrefab.gameObject, prefabStage.scene); + + return s_InstanceForPrefab; + } + } +#endif + + private static BakingCamera Instance + { + get + { +#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR + var inst = InstanceForPrefab; + if (inst) return inst; +#endif + // Find instance in scene, or create new one. + return s_Instance + ? s_Instance + : (s_Instance = FindObjectOfType() ?? Create()); + } + } + + private Camera _camera; + private int _refCount; + + private static BakingCamera Create() + { + var gameObject = new GameObject(typeof(BakingCamera).Name); + + // This camera object is just for internal use + gameObject.hideFlags = HideFlags.HideAndDontSave; + gameObject.hideFlags = HideFlags.DontSave; + + var inst = gameObject.AddComponent(); + inst._camera = gameObject.AddComponent(); + inst._camera.orthographic = true; + + // Turn camera off because particle mesh baker will use only camera matrix + gameObject.SetActive(false); + + return inst; + } + + private void Awake() + { + if (this == s_Instance) + DontDestroyOnLoad(gameObject); + } + + public static void Register() + { + Instance._refCount++; + } + + public static void Unregister() + { + if (s_Instance == null) return; + + Instance._refCount--; + if (0 < Instance._refCount) return; + + if (Application.isPlaying) + Destroy(Instance.gameObject); + else + DestroyImmediate(Instance.gameObject); + + s_Instance = null; + } + + public static Camera GetCamera(Canvas canvas) + { + if (!canvas) return Camera.main; + + canvas = canvas.rootCanvas; + // Adjust camera orthographic size to canvas size + // for canvas-based coordinates of particles' size and speed. + var size = ((RectTransform) canvas.transform).rect.size; + Instance._camera.orthographicSize = Mathf.Max(size.x, size.y) * canvas.scaleFactor; + + var camera = canvas.worldCamera; + var transform = Instance.transform; + if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && camera) + { + var cameraTr = camera.transform; + transform.SetPositionAndRotation(cameraTr.position, cameraTr.rotation); + } + else + { + Instance._camera.orthographic = true; + transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + } + + return Instance._camera; + } + } +} diff --git a/Scripts/UIParticleOverlayCamera.cs.meta b/Scripts/BakingCamera.cs.meta similarity index 83% rename from Scripts/UIParticleOverlayCamera.cs.meta rename to Scripts/BakingCamera.cs.meta index cf7098e..baee49a 100644 --- a/Scripts/UIParticleOverlayCamera.cs.meta +++ b/Scripts/BakingCamera.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f69dfb25b53b14addbd71dbebdbaa132 +guid: 999f0ea10cb5f48ed89190a0ca83dd53 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/Editor/AnimatedPropertiesEditor.cs b/Scripts/Editor/AnimatedPropertiesEditor.cs new file mode 100644 index 0000000..6e2660d --- /dev/null +++ b/Scripts/Editor/AnimatedPropertiesEditor.cs @@ -0,0 +1,114 @@ +using UnityEditor; +using UnityEngine; +using System.Collections.Generic; +using System.Linq; +using ShaderPropertyType = Coffee.UIExtensions.AnimatableProperty.ShaderPropertyType; + +namespace Coffee.UIExtensions +{ + internal class AnimatedPropertiesEditor + { + static readonly List s_ActiveNames = new List(); + static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder(); + + private string _name; + private ShaderPropertyType _type; + + static string CollectActiveNames(SerializedProperty sp, List 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 mat) + { + if (!mat || !mat.shader) return; + + 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); + } + } + + 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); + 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(); + }); + } + } +} diff --git a/Scripts/Editor/AnimatedPropertiesEditor.cs.meta b/Scripts/Editor/AnimatedPropertiesEditor.cs.meta new file mode 100644 index 0000000..e3b6329 --- /dev/null +++ b/Scripts/Editor/AnimatedPropertiesEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60feb9aeb134e4ec789c0e63b017997f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/AsmdefEx.cs b/Scripts/Editor/AsmdefEx.cs deleted file mode 100644 index 1146492..0000000 --- a/Scripts/Editor/AsmdefEx.cs +++ /dev/null @@ -1,510 +0,0 @@ -#if UNITY_EDITOR -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using UnityEditor; -using UnityEngine; -using UnityEditor.Compilation; - -namespace __GENARATED_ASMDEF__.Coffee.UIParticle.Editor -{ - internal static class PackageSettings - { - public const string PackageId = "OpenSesame.Net.Compilers.3.4.0-beta.1"; - } - - internal static class ReflectionExtensions - { - const BindingFlags FLAGS = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - - static object Inst(this object self) - { - return (self is Type) ? null : self; - } - - static Type Type(this object self) - { - return (self as Type) ?? self.GetType(); - } - - public static object New(this Type self, params object[] args) - { - var types = args.Select(x => x.GetType()).ToArray(); - return self.Type().GetConstructor(types) - .Invoke(args); - } - - public static object Call(this object self, string methodName, params object[] args) - { - var types = args.Select(x => x.GetType()).ToArray(); - return self.Type().GetMethod(methodName, types) - .Invoke(self.Inst(), args); - } - - public static object Call(this object self, Type[] genericTypes, string methodName, params object[] args) - { - return self.Type().GetMethod(methodName, FLAGS) - .MakeGenericMethod(genericTypes) - .Invoke(self.Inst(), args); - } - - public static object Get(this object self, string memberName, MemberInfo mi = null) - { - mi = mi ?? self.Type().GetMember(memberName, FLAGS)[0]; - return mi is PropertyInfo - ? (mi as PropertyInfo).GetValue(self.Inst(), new object[0]) - : (mi as FieldInfo).GetValue(self.Inst()); - } - - public static void Set(this object self, string memberName, object value, MemberInfo mi = null) - { - mi = mi ?? self.Type().GetMember(memberName, FLAGS)[0]; - if (mi is PropertyInfo) - (mi as PropertyInfo).SetValue(self.Inst(), value, new object[0]); - else - (mi as FieldInfo).SetValue(self.Inst(), value); - } - } - - internal class Settings - { - public bool IgnoreAccessChecks; - public string ModifySymbols = ""; - public string CustomCompiler = ""; - - public bool SholdChangeCompilerProcess - { - get { return IgnoreAccessChecks || !string.IsNullOrEmpty(ModifySymbols) || !string.IsNullOrEmpty(CustomCompiler); } - } - - public bool UseCustomCompiler - { - get { return IgnoreAccessChecks || !string.IsNullOrEmpty(CustomCompiler); } - } - - public static Settings GetAtPath(string path) - { - var setting = new Settings(); - if (string.IsNullOrEmpty(path)) - return setting; - - // If input path is directory, find asmdef file. - if (Directory.Exists(path)) - path = Directory.GetFiles(path, "*.asmdef") - .Select(x => x.Replace(Environment.CurrentDirectory + Path.DirectorySeparatorChar, "")) - .FirstOrDefault(); - - // Not find asmdef file. - if (string.IsNullOrEmpty(path) || !File.Exists(path) || !File.Exists(path + ".meta")) - return setting; - - try - { - var json = AssetImporter.GetAtPath(path).userData; - GetSettingssFromJson(json, out setting.IgnoreAccessChecks, out setting.ModifySymbols, out setting.CustomCompiler); - } - catch - { - } - - return setting; - } - - public string ToJson() - { - return string.Format("{{" + - "\"IgnoreAccessChecks\":{0}," + - "\"ModifySymbols\":\"{1}\"," + - "\"CustomCompiler\":\"{2}\"" + - "}}", - IgnoreAccessChecks ? "true" : "false", - ModifySymbols ?? "", - CustomCompiler ?? "" - ); - } - - public static Settings CreateFromJson(string json = "") - { - var setting = new Settings(); - GetSettingssFromJson(json, out setting.IgnoreAccessChecks, out setting.ModifySymbols, out setting.CustomCompiler); - return setting; - } - - static void GetSettingssFromJson(string json, out bool ignoreAccessChecks, out string modifySymbols, out string customCompiler) - { - ignoreAccessChecks = false; - modifySymbols = ""; - customCompiler = ""; - if (string.IsNullOrEmpty(json)) - return; - - ignoreAccessChecks = Regex.Match(json, "\"IgnoreAccessChecks\":\\s*(true|false)").Groups[1].Value == "true"; - modifySymbols = Regex.Match(json, "\"ModifySymbols\":\\s*\"([^\"]*)\"").Groups[1].Value; - customCompiler = Regex.Match(json, "\"CustomCompiler\":\\s*\"([^\"]*)\"").Groups[1].Value; - } - } - - internal static class CustomCompiler - { - static string s_InstallPath; - const string k_LogHeader = "[CustomCompiler] "; - - static void Log(string format, params object[] args) - { - if (Core.LogEnabled) - UnityEngine.Debug.LogFormat(k_LogHeader + format, args); - } - - public static string GetInstalledPath(string packageId = "") - { - if (!string.IsNullOrEmpty(s_InstallPath) && File.Exists(s_InstallPath)) - return s_InstallPath; - - try - { - packageId = string.IsNullOrEmpty(packageId) ? PackageSettings.PackageId : packageId; - s_InstallPath = Install(packageId); - } - catch (Exception ex) - { - UnityEngine.Debug.LogException(new Exception(k_LogHeader + ex.Message, ex.InnerException)); - } - finally - { - EditorUtility.ClearProgressBar(); - } - - return s_InstallPath; - } - - static string Install(string packageId) - { - char sep = Path.DirectorySeparatorChar; - string url = "https://globalcdn.nuget.org/packages/" + packageId.ToLower() + ".nupkg"; - string downloadPath = ("Temp/" + Path.GetFileName(Path.GetTempFileName())).Replace('/', sep); - string installPath = ("Library/" + packageId).Replace('/', sep); - string cscToolExe = (installPath + "/tools/csc.exe").Replace('/', sep); - - // Custom compiler is already installed. - if (File.Exists(cscToolExe)) - { - Log("Custom compiler '{0}' is already installed at {1}", packageId, cscToolExe); - return cscToolExe; - } - - if (Directory.Exists(installPath)) - Directory.Delete(installPath, true); - - try - { - UnityEngine.Debug.LogFormat(k_LogHeader + "Install custom compiler '{0}'", packageId); - - // Download custom compiler package from nuget. - { - UnityEngine.Debug.LogFormat(k_LogHeader + "Download {0} from nuget: {1}", packageId, url); - EditorUtility.DisplayProgressBar("Custom Compiler Installer", string.Format("Download {0} from nuget", packageId), 0.2f); - - switch (Application.platform) - { - case RuntimePlatform.WindowsEditor: - ExecuteCommand("PowerShell.exe", string.Format("curl -O {0} {1}", downloadPath, url)); - break; - case RuntimePlatform.OSXEditor: - ExecuteCommand("curl", string.Format("-o {0} -L {1}", downloadPath, url)); - break; - case RuntimePlatform.LinuxEditor: - ExecuteCommand("wget", string.Format("-O {0} {1}", downloadPath, url)); - break; - } - } - - // Extract nuget package (unzip). - { - UnityEngine.Debug.LogFormat(k_LogHeader + "Extract {0} to {1} with 7z", downloadPath, installPath); - EditorUtility.DisplayProgressBar("Custom Compiler Installer", string.Format("Extract {0}", downloadPath), 0.4f); - - string appPath = EditorApplication.applicationContentsPath.Replace('/', sep); - string args = string.Format("x {0} -o{1}", downloadPath, installPath); - - switch (Application.platform) - { - case RuntimePlatform.WindowsEditor: - ExecuteCommand(appPath + "\\Tools\\7z.exe", args); - break; - case RuntimePlatform.OSXEditor: - case RuntimePlatform.LinuxEditor: - ExecuteCommand(appPath + "/Tools/7za", args); - break; - } - } - - UnityEngine.Debug.LogFormat(k_LogHeader + "Custom compiler '{0}' has been installed in {1}.", packageId, installPath); - } - catch - { - throw new Exception(string.Format("Custom compiler '{0}' installation failed.", packageId)); - } - finally - { - EditorUtility.ClearProgressBar(); - } - - if (File.Exists(cscToolExe)) - return cscToolExe; - - throw new FileNotFoundException(string.Format("Custom compiler '{0}' is not found at {1}", packageId, cscToolExe)); - } - - static void ExecuteCommand(string exe, string args) - { - UnityEngine.Debug.LogFormat(k_LogHeader + "Execute command: {0} {1}", exe, args); - - Process p = Process.Start(new ProcessStartInfo() - { - FileName = exe, - Arguments = args, - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardError = true, - }); - p.WaitForExit(); - - if (p.ExitCode != 0) - { - UnityEngine.Debug.LogErrorFormat(k_LogHeader + p.StandardError.ReadToEnd()); - throw new Exception(); - } - } - } - - [InitializeOnLoad] - internal static class Core - { - public static bool LogEnabled { get; private set; } - public static string k_LogHeader = "[AsmdefEx] "; - - static void Log(string format, params object[] args) - { - if (LogEnabled) - LogEx(format, args); - } - - public static void LogEx(string format, params object[] args) - { - UnityEngine.Debug.LogFormat(k_LogHeader + format, args); - } - - public static void Error(Exception e) - { - UnityEngine.Debug.LogException(new Exception(k_LogHeader + e.Message, e.InnerException)); - } - - public static object GetScriptAssembly(string assemblyName) - { - Type tEditorCompilationInterface = Type.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface, UnityEditor"); - Type tCSharpLanguage = Type.GetType("UnityEditor.Scripting.Compilers.CSharpLanguage, UnityEditor"); - return tEditorCompilationInterface.Call(new[] {tCSharpLanguage}, "GetScriptAssemblyForLanguage", assemblyName); - } - - public static string[] ModifyDefines(IEnumerable defines, bool ignoreAccessChecks, string modifySymbols) - { - var symbols = modifySymbols.Split(';', ','); - var add = symbols.Where(x => 0 < x.Length && !x.StartsWith("!")); - var remove = symbols.Where(x => 1 < x.Length && x.StartsWith("!")).Select(x => x.Substring(1)); - return defines - .Union(add ?? Enumerable.Empty()) - .Except(remove ?? Enumerable.Empty()) - .Union(ignoreAccessChecks ? new[] {"IGNORE_ACCESS_CHECKS"} : Enumerable.Empty()) - .Union(new[] {"ASMDEF_EX"}) - .Distinct() - .ToArray(); - } - - public static void ModifyFiles(IEnumerable files, bool ignoreAccessChecks) - { - const string s_If = "#if IGNORE_ACCESS_CHECKS // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY."; - const string s_EndIf = "#endif // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY."; - - // Add #if and #endif to all source files. - foreach (var file in files) - { - var text = File.ReadAllText(file); - Log("ModifyFiles: {0} {1} {2}", file, ignoreAccessChecks, text.Contains(s_If)); - if (text.Contains(s_If) == ignoreAccessChecks) - continue; - - var m = Regex.Match(text, "[\r\n]+"); - if (!m.Success) - continue; - - var nl = m.Value; - if (ignoreAccessChecks) - { - text = s_If + nl + text + nl + s_EndIf; - } - else - { - text = text.Replace(s_If + nl, ""); - text = text.Replace(nl + s_EndIf, ""); - } - - Log("ModifyFiles: Write {0} {1} {2}", file, ignoreAccessChecks, text.Contains(s_If)); - File.WriteAllText(file, text); - } - } - - public static void ChangeCompilerProcess(object compiler, Settings setting) - { - Type tProgram = Type.GetType("UnityEditor.Utils.Program, UnityEditor"); - Type tScriptCompilerBase = Type.GetType("UnityEditor.Scripting.Compilers.ScriptCompilerBase, UnityEditor"); - FieldInfo fiProcess = tScriptCompilerBase.GetField("process", BindingFlags.NonPublic | BindingFlags.Instance); - - Log("Kill previous compiler process"); - var psi = compiler.Get("process", fiProcess).Call("GetProcessStartInfo") as ProcessStartInfo; - compiler.Call("Dispose"); - - // Convert response file for Mono to .Net. - // - Add preferreduilang option (en-US) - // - Change language version to 'latest' - // - Change 'debug' to 'debug:portable' - // - Change compiler switch prefix '-' to '/' - string responseFile = Regex.Replace(psi.Arguments, "^.*@(.+)$", "$1"); - bool isMono = compiler.GetType().Name == "MonoCSharpCompiler"; - var text = File.ReadAllText(responseFile); - text = Regex.Replace(text, "[\r\n]+", "\n"); - text = Regex.Replace(text, "^-", "/", RegexOptions.Multiline); - - // Modify scripting define symbols. - { - Log("Modify scripting define symbols: {0}", responseFile); - var defines = Regex.Matches(text, "^/define:(.*)$", RegexOptions.Multiline) - .Cast() - .Select(x => x.Groups[1].Value); - - text = Regex.Replace(text, "[\r\n]+/define:[^\r\n]+", ""); - foreach (var d in ModifyDefines(defines, setting.IgnoreAccessChecks, setting.ModifySymbols)) - { - text += "\n/define:" + d; - } - } - - // Add/remove '#if IGNORE_ACCESS_CHECKS' and '#endif' preprocessor. - var files = Regex.Matches(text, "^\"(.*)\"$", RegexOptions.Multiline) - .Cast() - .Select(x => x.Groups[1].Value) - .Where(x => Path.GetExtension(x) == ".cs") - .Where(x => Path.GetFileName(x) != "AsmdefEx.cs"); - ModifyFiles(files, setting.IgnoreAccessChecks); - - // To access to non-publics in other assemblies, use custom compiler instead of default csc. - if (setting.UseCustomCompiler) - { - text = Regex.Replace(text, "^/langversion:\\d+$", "/langversion:latest", RegexOptions.Multiline); - text = Regex.Replace(text, "^/debug$", "/debug:portable", RegexOptions.Multiline); - text += "\n/preferreduilang:en-US"; - - // Change exe file path. - var cscToolExe = CustomCompiler.GetInstalledPath(setting.CustomCompiler); - Log("Change csc tool exe to {0}", cscToolExe); - if (Application.platform == RuntimePlatform.WindowsEditor) - { - psi.FileName = Path.GetFullPath(cscToolExe); - psi.Arguments = "/shared /noconfig @" + responseFile; - } - else - { - psi.FileName = Path.Combine(EditorApplication.applicationContentsPath, "MonoBleedingEdge/bin/mono"); - psi.Arguments = cscToolExe + " /noconfig @" + responseFile; - } - } - // Revert prefix symbols for mono compiler - else if (isMono) - { - text = Regex.Replace(text, "^/", "-", RegexOptions.Multiline); - } - - text = Regex.Replace(text, "\n", System.Environment.NewLine); - File.WriteAllText(responseFile, text); - - Log("Restart compiler process: {0} {1}", psi.FileName, psi.Arguments); - var program = tProgram.New(psi); - program.Call("Start"); - compiler.Set("process", program, fiProcess); - } - - static void OnAssemblyCompilationStarted(string name) - { - try - { - string assemblyName = Path.GetFileNameWithoutExtension(name); - string assemblyFilename = assemblyName + ".dll"; - - if (assemblyName != typeof(Core).Assembly.GetName().Name) - return; - - Type tEditorCompilationInterface = Type.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface, UnityEditor"); - var compilerTasks = tEditorCompilationInterface.Get("Instance").Get("compilationTask").Get("compilerTasks") as IDictionary; - var scriptAssembly = compilerTasks.Keys.Cast().FirstOrDefault(x => (x.Get("Filename") as string) == assemblyFilename); - - // Should change compiler process for the assembly? - var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assemblyName); - var setting = Settings.GetAtPath(asmdefPath); - if (!setting.SholdChangeCompilerProcess) - return; - - // Create new compiler to recompile. - Log("Assembly compilation started: {0} should be recompiled.", assemblyName); - Core.ChangeCompilerProcess(compilerTasks[scriptAssembly], setting); - } - catch (Exception e) - { - UnityEngine.Debug.LogException(new Exception(k_LogHeader + e.Message, e.InnerException)); - } - } - - static Core() - { - var assemblyName = typeof(Core).Assembly.GetName().Name; - if (assemblyName == "Coffee.AsmdefEx") - return; - - k_LogHeader = string.Format("[AsmdefEx ({0})] ", assemblyName); - LogEnabled = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup) - .Split(';', ',') - .Any(x => x == "ASMDEF_EX_LOG"); - - Log("Start watching assembly '{0}' compilation.", typeof(Core).Assembly.GetName().Name); - CompilationPipeline.assemblyCompilationStarted += OnAssemblyCompilationStarted; - } - } - -#if !ASMDEF_EX - [InitializeOnLoad] - internal class RecompileRequest - { - static RecompileRequest() - { - var assemblyName = typeof(RecompileRequest).Assembly.GetName().Name; - if (assemblyName == "Coffee.AsmdefEx") - return; - - // Should change compiler process for the assembly? - var asmdefPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName(assemblyName); - var setting = Settings.GetAtPath(asmdefPath); - if (!setting.SholdChangeCompilerProcess) - return; - - if (Core.LogEnabled) - UnityEngine.Debug.LogFormat("Request to recompile: {0} ({1})", assemblyName, asmdefPath); - - AssetDatabase.ImportAsset(asmdefPath); - } - } -#endif -} -#endif diff --git a/Scripts/Editor/Coffee.UIParticle.Editor.asmdef.meta b/Scripts/Editor/Coffee.UIParticle.Editor.asmdef.meta index 63d621d..46b5918 100644 --- a/Scripts/Editor/Coffee.UIParticle.Editor.asmdef.meta +++ b/Scripts/Editor/Coffee.UIParticle.Editor.asmdef.meta @@ -2,6 +2,6 @@ fileFormatVersion: 2 guid: 69deabfe8eed7495886c9daa69c9826a AssemblyDefinitionImporter: externalObjects: {} - userData: '{"IgnoreAccessChecks":true,"ModifySymbols":""}' + userData: assetBundleName: assetBundleVariant: diff --git a/Scripts/Editor/UIParticleEditor.cs b/Scripts/Editor/UIParticleEditor.cs index 415f720..7a774cc 100644 --- a/Scripts/Editor/UIParticleEditor.cs +++ b/Scripts/Editor/UIParticleEditor.cs @@ -1,129 +1,32 @@ -#if IGNORE_ACCESS_CHECKS // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY. using UnityEditor; using UnityEditor.UI; using UnityEngine; using System.Collections.Generic; using System.Linq; -using System; -using ShaderPropertyType = Coffee.UIExtensions.UIParticle.AnimatableProperty.ShaderPropertyType; namespace Coffee.UIExtensions { [CustomEditor(typeof(UIParticle))] [CanEditMultipleObjects] - public class UIParticleEditor : GraphicEditor + internal class UIParticleEditor : GraphicEditor { - class AnimatedPropertiesEditor - { - static readonly List s_ActiveNames = new List(); - static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder(); - - public string name; - public ShaderPropertyType type; - - static string CollectActiveNames(SerializedProperty sp, List result) - { - result.Clear(); - for (int i = 0; i < sp.arraySize; i++) - { - result.Add(sp.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name").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 mat) - { - if (!mat || !mat.shader) - return; - bool isClicked = false; - using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) - { - var r = EditorGUI.PrefixLabel(EditorGUILayout.GetControlRect(true), new GUIContent(sp.displayName, sp.tooltip)); - isClicked = GUI.Button(r, CollectActiveNames(sp, s_ActiveNames), EditorStyles.popup); - } - - if (isClicked) - { - GenericMenu gm = new GenericMenu(); - gm.AddItem(new GUIContent("Nothing"), s_ActiveNames.Count == 0, () => - { - sp.ClearArray(); - sp.serializedObject.ApplyModifiedProperties(); - }); - - - for (int 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); - } - - for (int i = 0; i < ShaderUtil.GetPropertyCount(mat.shader); i++) - { - var pName = ShaderUtil.GetPropertyName(mat.shader, i); - var type = (ShaderPropertyType) ShaderUtil.GetPropertyType(mat.shader, i); - AddMenu(gm, sp, new AnimatedPropertiesEditor() {name = pName, type = type}, true); - - if (type == ShaderPropertyType.Texture) - { - 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(); - } - } - - public 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(); - }); - } - } - //################################ // 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 Color s_GizmoColor = new Color(1f, 0.7f, 0.7f, 0.9f); + private static readonly GUIContent s_ContentParticleMaterial = new GUIContent("Particle Material", "The material for rendering particles"); + private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material", "The material for rendering particle trails"); + private static readonly GUIContent s_ContentAdvancedOptions = new GUIContent("Advanced Options"); + private static readonly GUIContent s_Content3D = new GUIContent("3D"); + private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); + private static readonly List s_ParticleSystems = new List(); - static readonly List s_MaskablePropertyNames = new List() + private SerializedProperty _spParticleSystem; + private SerializedProperty _spScale3D; + private SerializedProperty _spIgnoreCanvasScaler; + private SerializedProperty _spAnimatableProperties; + private bool _xyzMode; + + private static readonly List s_MaskablePropertyNames = new List { "_Stencil", "_StencilComp", @@ -133,6 +36,7 @@ namespace Coffee.UIExtensions "_ColorMask", }; + //################################ // Public/Protected Members. //################################ @@ -143,16 +47,9 @@ namespace Coffee.UIExtensions { base.OnEnable(); _spParticleSystem = serializedObject.FindProperty("m_ParticleSystem"); - _spTrailParticle = serializedObject.FindProperty("m_TrailParticle"); - _spScale = serializedObject.FindProperty("m_Scale"); - _spIgnoreParent = serializedObject.FindProperty("m_IgnoreParent"); + _spScale3D = serializedObject.FindProperty("m_Scale3D"); + _spIgnoreCanvasScaler = serializedObject.FindProperty("m_IgnoreCanvasScaler"); _spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); - _particles = targets.Cast().ToArray(); - _shapeModuleUIs = null; - - var targetsGos = targets.Cast().Select(x => x.gameObject).ToArray(); - _inspector = Resources.FindObjectsOfTypeAll() - .FirstOrDefault(x => x.targets.Cast().Select(x => x.gameObject).SequenceEqual(targetsGos)); } /// @@ -160,15 +57,19 @@ namespace Coffee.UIExtensions /// public override void OnInspectorGUI() { + var current = target as UIParticle; + if (current == null) return; + serializedObject.Update(); EditorGUI.BeginDisabledGroup(true); EditorGUILayout.PropertyField(_spParticleSystem); EditorGUI.EndDisabledGroup(); + // Draw materials. EditorGUI.indentLevel++; var ps = _spParticleSystem.objectReferenceValue as ParticleSystem; - if (ps) + if (ps != null) { var pr = ps.GetComponent(); var sp = new SerializedObject(pr).FindProperty("m_Materials"); @@ -194,22 +95,19 @@ namespace Coffee.UIExtensions EditorGUI.indentLevel--; - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.PropertyField(_spTrailParticle); - EditorGUI.EndDisabledGroup(); + // Advanced Options + EditorGUILayout.Space(); + EditorGUILayout.LabelField(s_ContentAdvancedOptions, EditorStyles.boldLabel); - var current = target as UIParticle; + _xyzMode = DrawFloatOrVector3Field(_spScale3D, _xyzMode); - EditorGUILayout.PropertyField(_spIgnoreParent); - - EditorGUI.BeginDisabledGroup(!current.isRoot); - EditorGUILayout.PropertyField(_spScale); - EditorGUI.EndDisabledGroup(); + EditorGUILayout.PropertyField(_spIgnoreCanvasScaler); // AnimatableProperties AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, current.material); - current.GetComponentsInChildren(true, s_ParticleSystems); + // Fix + current.GetComponentsInChildren(true, s_ParticleSystems); if (s_ParticleSystems.Any(x => x.GetComponent() == null)) { GUILayout.BeginHorizontal(); @@ -229,75 +127,54 @@ namespace Coffee.UIExtensions s_ParticleSystems.Clear(); + // Does the shader support UI masks? if (current.maskable && current.material && current.material.shader) { var mat = current.material; var shader = mat.shader; foreach (var propName in s_MaskablePropertyNames) { - if (!mat.HasProperty(propName)) - { - EditorGUILayout.HelpBox(string.Format("Shader {0} doesn't have '{1}' property. This graphic is not maskable.", shader.name, propName), MessageType.Warning); - break; - } + 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; } } serializedObject.ApplyModifiedProperties(); } - - //################################ - // Private Members. - //################################ - SerializedProperty _spParticleSystem; - SerializedProperty _spTrailParticle; - SerializedProperty _spScale; - SerializedProperty _spIgnoreParent; - SerializedProperty _spAnimatableProperties; - UIParticle[] _particles; - ShapeModuleUI[] _shapeModuleUIs; - ParticleSystemInspector _inspector; - - void OnSceneGUI() + private static bool DrawFloatOrVector3Field(SerializedProperty sp, bool showXyz) { - _shapeModuleUIs = _shapeModuleUIs ?? _inspector?.m_ParticleEffectUI?.m_Emitters?.SelectMany(x => x.m_Modules).OfType()?.ToArray(); - if (_shapeModuleUIs == null || _shapeModuleUIs.Length == 0 || _shapeModuleUIs[0].GetParticleSystem() != (target as UIParticle).cachedParticleSystem) - return; + var x = sp.FindPropertyRelative("x"); + var y = sp.FindPropertyRelative("y"); + var z = sp.FindPropertyRelative("z"); - Action postAction = () => { }; - Color origin = ShapeModuleUI.s_GizmoColor.m_Color; - Color originDark = ShapeModuleUI.s_GizmoColor.m_Color; - ShapeModuleUI.s_GizmoColor.m_Color = s_GizmoColor; - ShapeModuleUI.s_GizmoColor.m_OptionalDarkColor = s_GizmoColor; + showXyz |= !Mathf.Approximately(x.floatValue, y.floatValue) || + !Mathf.Approximately(y.floatValue, z.floatValue) || + y.hasMultipleDifferentValues || + z.hasMultipleDifferentValues; - _particles - .Distinct() - .Select(x => new {canvas = x.canvas, ps = x.cachedParticleSystem, scale = x.scale}) - .Where(x => x.ps && x.canvas) - .ToList() - .ForEach(x => - { - var trans = x.ps.transform; - var hasChanged = trans.hasChanged; - var localScale = trans.localScale; - postAction += () => trans.localScale = localScale; - trans.localScale = Vector3.Scale(localScale, x.canvas.rootCanvas.transform.localScale * x.scale); - }); - - try + EditorGUILayout.BeginHorizontal(); + if (showXyz) { - foreach (var ui in _shapeModuleUIs) - ui.OnSceneViewGUI(); + EditorGUILayout.PropertyField(sp); } - catch + else { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(x, s_ContentScale); + if (EditorGUI.EndChangeCheck()) + z.floatValue = y.floatValue = x.floatValue; } - postAction(); - ShapeModuleUI.s_GizmoColor.m_Color = origin; - ShapeModuleUI.s_GizmoColor.m_OptionalDarkColor = originDark; + 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; } } } -#endif // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY. diff --git a/Scripts/Editor/UIParticleMenu.cs b/Scripts/Editor/UIParticleMenu.cs index 1a9ec9b..f30cb96 100644 --- a/Scripts/Editor/UIParticleMenu.cs +++ b/Scripts/Editor/UIParticleMenu.cs @@ -1,16 +1,16 @@ -#if IGNORE_ACCESS_CHECKS // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY. -#if !UNITY_2019_1_OR_NEWER using System.IO; using System.Text.RegularExpressions; using UnityEditor; +using UnityEngine; namespace Coffee.UIExtensions { public class UIParticleMenu { +#if !UNITY_2019_1_OR_NEWER static string GetPreviousSamplePath(string displayName, string sampleName) { - string sampleRoot = $"Assets/Samples/{displayName}"; + string sampleRoot = string.Format("Assets/Samples/{0}", displayName); var sampleRootInfo = new DirectoryInfo(sampleRoot); if (!sampleRootInfo.Exists) return null; @@ -27,12 +27,12 @@ namespace Coffee.UIExtensions static void ImportSample(string packageName, string sampleName) { - string jsonPath = $"Packages/{packageName}/package.json"; + string jsonPath = string.Format("Packages/{0}/package.json", packageName); string json = File.ReadAllText(jsonPath); string version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value; string displayName = Regex.Match(json, "\"displayName\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value; - string src = $"{Path.GetDirectoryName(jsonPath)}/Samples~/{sampleName}"; - string dst = $"Assets/Samples/{displayName}/{version}/{sampleName}"; + string src = string.Format("{0}/Samples~/{1}", Path.GetDirectoryName(jsonPath), sampleName); + string dst = string.Format("Assets/Samples/{0}/{1}/{2}", displayName, version, sampleName); string previous = GetPreviousSamplePath(displayName, sampleName); if (!string.IsNullOrEmpty(previous)) @@ -43,11 +43,19 @@ namespace Coffee.UIExtensions if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel")) return; - FileUtil.DeleteFileOrDirectory(previous); FileUtil.DeleteFileOrDirectory(previous + ".meta"); + FileUtil.DeleteFileOrDirectory(previous); + + string versionDir = Path.GetDirectoryName(previous); + if (Directory.GetFiles(versionDir, "*.meta", SearchOption.TopDirectoryOnly).Length == 0) + { + FileUtil.DeleteFileOrDirectory(versionDir + ".meta"); + FileUtil.DeleteFileOrDirectory(versionDir); + } } - FileUtil.CopyDirectoryRecursive(src, dst); + Directory.CreateDirectory(Path.GetDirectoryName(dst)); + FileUtil.CopyFileOrDirectory(src, dst); AssetDatabase.ImportAsset(dst, ImportAssetOptions.ImportRecursive); } @@ -56,7 +64,45 @@ namespace Coffee.UIExtensions { ImportSample("com.coffee.ui-particle", "Demo"); } +#endif + + + [MenuItem("GameObject/UI/Particle System", false, 2019)] + public static void AddParticle(MenuCommand menuCommand) + { + // Create UI element. + EditorApplication.ExecuteMenuItem("GameObject/UI/Image"); + var ui = Selection.activeGameObject; + + // Create ParticleSystem. + EditorApplication.ExecuteMenuItem("GameObject/Effects/Particle System"); + var ps = Selection.activeGameObject; + var transform = ps.transform; + var localRotation = transform.localRotation; + + transform.SetParent(ui.transform.parent, true); + var pos = transform.localPosition; + pos.z = 0; + ps.transform.localPosition = pos; + ps.transform.localRotation = localRotation; + + // Destroy UI elemant + Object.DestroyImmediate(ui); + + // Assign default material. + var renderer = ps.GetComponent(); + var defaultMat = AssetDatabase.GetBuiltinExtraResource("Default-Particle.mat"); + renderer.material = defaultMat ? defaultMat : renderer.material; + + // Set to hierarchy mode + var particleSystem = ps.GetComponent(); + var main = particleSystem.main; + main.scalingMode = ParticleSystemScalingMode.Hierarchy; + + // Add UIParticle. + var uiParticle = ps.AddComponent(); + uiParticle.ignoreCanvasScaler = true; + uiParticle.scale = 10; + } } } -#endif -#endif // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY. diff --git a/Scripts/MeshHelper.cs b/Scripts/MeshHelper.cs new file mode 100644 index 0000000..e9efec3 --- /dev/null +++ b/Scripts/MeshHelper.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Coffee.UIExtensions +{ + internal static class MeshHelper + { + private static CombineInstance[] s_CombineInstances; + private static int s_CurrentIndex; + static readonly List s_Colors = new List(); + private static int s_RefCount; + + public static void Register() + { + if (0 < s_RefCount++) return; + s_CombineInstances = new CombineInstance[2]; + } + + public static void Unregister() + { + s_RefCount--; + + if (0 < s_RefCount || s_CombineInstances == null) return; + + for (var i = 0; i < s_CombineInstances.Length; i++) + { +#if UNITY_EDITOR + if (!Application.isPlaying) + Object.DestroyImmediate(s_CombineInstances[i].mesh); + else +#endif + { + Object.Destroy(s_CombineInstances[i].mesh); + } + } + + s_CombineInstances = null; + } + + public static Mesh GetTemporaryMesh() + { + return s_CombineInstances[s_CurrentIndex++].mesh; + } + + public static void Clear() + { + s_CurrentIndex = 0; + for (var i = 0; i < s_CombineInstances.Length; i++) + { + if (!s_CombineInstances[i].mesh) + { + var mesh = new Mesh(); + mesh.MarkDynamic(); + s_CombineInstances[i].mesh = mesh; + } + else + { + s_CombineInstances[i].mesh.Clear(false); + } + } + } + + public static void CombineMesh(Mesh result, Matrix4x4 transform) + { + if (!result || s_CurrentIndex == 0) return; + + for (var i = 0; i < 2; i++) + s_CombineInstances[i].transform = transform; + + result.CombineMeshes(s_CombineInstances, false, true); + result.RecalculateBounds(); + } + + public static void ModifyColorSpaceToLinear(this Mesh self) + { + self.GetColors(s_Colors); + + for (var i = 0; i < s_Colors.Count; i++) + s_Colors[i] = ((Color) s_Colors[i]).gamma; + + self.SetColors(s_Colors); + s_Colors.Clear(); + } + } +} diff --git a/Scripts/MeshHelper.cs.meta b/Scripts/MeshHelper.cs.meta new file mode 100644 index 0000000..a903bf0 --- /dev/null +++ b/Scripts/MeshHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f689ea5a2e9f140288c8874127aa9ee0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/SpriteExtensions.cs b/Scripts/SpriteExtensions.cs new file mode 100644 index 0000000..b1c5f13 --- /dev/null +++ b/Scripts/SpriteExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; +using UnityEngine; + +namespace Coffee.UIExtensions +{ + 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); + + internal 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 + } +} diff --git a/Scripts/SpriteExtensions.cs.meta b/Scripts/SpriteExtensions.cs.meta new file mode 100644 index 0000000..5a68a0f --- /dev/null +++ b/Scripts/SpriteExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d188d31b140094ebc84a9caafbc7ac71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/UIParticle.cs b/Scripts/UIParticle.cs index ae629c9..263abcf 100755 --- a/Scripts/UIParticle.cs +++ b/Scripts/UIParticle.cs @@ -1,12 +1,6 @@ -using System.Collections.Generic; using UnityEngine; -using UnityEngine.Profiling; +using UnityEngine.Rendering; using UnityEngine.UI; -using ShaderPropertyType = Coffee.UIExtensions.UIParticle.AnimatableProperty.ShaderPropertyType; -#if UNITY_EDITOR -using System; -using System.Reflection; -#endif namespace Coffee.UIExtensions { @@ -17,16 +11,6 @@ namespace Coffee.UIExtensions [RequireComponent(typeof(CanvasRenderer))] public class UIParticle : MaskableGraphic { - //################################ - // Constant or Readonly Static Members. - //################################ - static readonly int s_IdMainTex = Shader.PropertyToID("_MainTex"); - static readonly List s_Vertices = new List(); - static readonly List s_Colors = new List(); - static readonly List s_TempRelatables = new List(); - static readonly List s_ActiveParticles = new List(); - - //################################ // Serialize Members. //################################ @@ -34,153 +18,36 @@ namespace Coffee.UIExtensions ParticleSystem m_ParticleSystem; [Tooltip("The UIParticle to render trail effect")] [SerializeField] - UIParticle m_TrailParticle; + internal UIParticle m_TrailParticle; [HideInInspector] [SerializeField] bool m_IsTrail = false; - [Tooltip("Particle effect scale")] [SerializeField] - float m_Scale = 1; + [Tooltip("Ignore canvas scaler")] [SerializeField] + bool m_IgnoreCanvasScaler = false; [Tooltip("Ignore parent scale")] [SerializeField] bool m_IgnoreParent = false; - [Tooltip("Animatable material properties. AnimationでParticleSystemのマテリアルプロパティを変更する場合、有効にしてください。")] [SerializeField] - AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; + [Tooltip("Particle effect scale")] [SerializeField] + float m_Scale = 0; - static MaterialPropertyBlock s_Mpb; + [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]; - [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 OnBeforeSerialize() - { - } - - public void OnAfterDeserialize() - { - id = Shader.PropertyToID(m_Name); - } - } + [Tooltip("Particle effect scale")] [SerializeField] + internal Vector3 m_Scale3D = Vector3.one; + private readonly Material[] _maskMaterials = new Material[2]; + private DrivenRectTransformTracker _tracker; + private Mesh _bakedMesh; + private ParticleSystemRenderer _renderer; + private int _cachedSharedMaterialId; + private int _cachedTrailMaterialId; + private bool _cachedSpritesModeAndHasTrail; //################################ // Public/Protected Members. //################################ - public override Texture mainTexture - { - get - { - Texture tex = null; - if (!m_IsTrail && cachedParticleSystem) - { - Profiler.BeginSample("Check TextureSheetAnimation module"); - var textureSheet = cachedParticleSystem.textureSheetAnimation; - if (textureSheet.enabled && textureSheet.mode == ParticleSystemAnimationMode.Sprites && 0 < textureSheet.spriteCount) - { - tex = GetActualTexture(textureSheet.GetSprite(0)); - } - - Profiler.EndSample(); - } - - if (!tex && _renderer) - { - Profiler.BeginSample("Check material"); - var mat = material; - if (mat && mat.HasProperty(s_IdMainTex)) - { - tex = mat.mainTexture; - } - - Profiler.EndSample(); - } - - return tex ?? s_WhiteTexture; - } - } - - public override Material material - { - get - { - return _renderer - ? m_IsTrail - ? _renderer.trailMaterial - : _renderer.sharedMaterial - : null; - } - - set - { - if (!_renderer) - { - } - else if (m_IsTrail && _renderer.trailMaterial != value) - { - _renderer.trailMaterial = value; - SetMaterialDirty(); - } - else if (!m_IsTrail && _renderer.sharedMaterial != value) - { - _renderer.sharedMaterial = value; - SetMaterialDirty(); - } - } - } - - /// - /// Particle effect scale. - /// - public float scale - { - get { return _parent ? _parent.scale : m_Scale; } - set { m_Scale = value; } - } - - /// - /// Should the soft mask ignore parent soft masks? - /// - /// If set to true the soft mask will ignore any parent soft mask settings. - public bool ignoreParent - { - get { return m_IgnoreParent; } - set - { - if (m_IgnoreParent != value) - { - m_IgnoreParent = value; - OnTransformParentChanged(); - } - } - } - - /// - /// Is this the root UIParticle? - /// - public bool isRoot - { - get { return !_parent; } - } - /// /// Should this graphic be considered a target for raycasting? /// @@ -191,7 +58,7 @@ namespace Coffee.UIExtensions } /// - /// ParticleSystem. + /// Cached ParticleSystem. /// public ParticleSystem cachedParticleSystem { @@ -199,21 +66,134 @@ namespace Coffee.UIExtensions } /// - /// Perform material modification in this function. + /// Cached ParticleSystem. /// - /// Modified material. - /// Configured Material. - public override Material GetModifiedMaterial(Material baseMaterial) + internal ParticleSystemRenderer cachedRenderer { - Material mat = null; - if (!_renderer) - mat = baseMaterial; - else if (m_AnimatableProperties.Length == 0) - mat = _renderer.sharedMaterial; - else - mat = new Material(material); + get { return _renderer; } + } - return base.GetModifiedMaterial(mat); + public bool ignoreCanvasScaler + { + get { return m_IgnoreCanvasScaler; } + set { m_IgnoreCanvasScaler = value; } + } + + /// + /// Particle effect scale. + /// + public float scale + { + get { return m_Scale3D.x; } + set { m_Scale3D.Set(value, value, value); } + } + + /// + /// Particle effect scale. + /// + public Vector3 scale3D + { + get { return m_Scale3D; } + set { m_Scale3D = value; } + } + + internal bool isTrailParticle + { + get { return m_IsTrail; } + } + + internal bool isSpritesMode + { + get { return textureSheetAnimationModule.enabled && textureSheetAnimationModule.mode == ParticleSystemAnimationMode.Sprites; } + } + + private bool isSpritesModeAndHasTrail + { + get { return isSpritesMode && trailModule.enabled; } + } + + private ParticleSystem.TextureSheetAnimationModule textureSheetAnimationModule + { + get { return cachedParticleSystem.textureSheetAnimation; } + } + + internal ParticleSystem.TrailModule trailModule + { + get { return cachedParticleSystem.trails; } + } + + internal ParticleSystem.MainModule mainModule + { + get { return cachedParticleSystem.main; } + } + + public bool isValid + { + get { return m_ParticleSystem && _renderer && canvas; } + } + + public Mesh bakedMesh + { + get { return _bakedMesh; } + } + + protected override void UpdateMaterial() + { + if (!_renderer) return; + + if (!isSpritesMode) // Non sprite mode: canvas renderer has main and trail materials. + { + canvasRenderer.materialCount = trailModule.enabled ? 2 : 1; + canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.sharedMaterial, 0), 0); + if (trailModule.enabled) + canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.trailMaterial, 1), 1); + } + else if (isTrailParticle) // Sprite mode (Trail): canvas renderer has trail material. + { + canvasRenderer.materialCount = 1; + canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.trailMaterial, 0), 0); + } + else // Sprite mode (Main): canvas renderer has main material. + { + canvasRenderer.materialCount = 1; + canvasRenderer.SetMaterial(GetModifiedMaterial(_renderer.sharedMaterial, 0), 0); + } + } + + private Material GetModifiedMaterial(Material baseMaterial, int index) + { + if (!baseMaterial || 1 < index || !isActiveAndEnabled) return null; + + var hasAnimatableProperties = 0 < m_AnimatableProperties.Length && index == 0; + if (hasAnimatableProperties || isTrailParticle) + baseMaterial = new Material(baseMaterial); + + var baseMat = baseMaterial; + if (m_ShouldRecalculateStencil) + { + m_ShouldRecalculateStencil = false; + + if (maskable) + { + var sortOverrideCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); + m_StencilValue = MaskUtilities.GetStencilDepth(transform, sortOverrideCanvas) + index; + } + else + { + m_StencilValue = 0; + } + } + + var component = GetComponent(); + if (m_StencilValue <= 0 || (component != null && component.IsActive())) return baseMat; + + var stencilId = (1 << m_StencilValue) - 1; + var maskMaterial = StencilMaterial.Add(baseMat, stencilId, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, stencilId, 0); + StencilMaterial.Remove(_maskMaterials[index]); + _maskMaterials[index] = maskMaterial; + baseMat = _maskMaterials[index]; + + return baseMat; } /// @@ -221,41 +201,24 @@ namespace Coffee.UIExtensions /// protected override void OnEnable() { - // Register. - if (s_ActiveParticles.Count == 0) - { - Canvas.willRenderCanvases += UpdateMeshes; - s_Mpb = new MaterialPropertyBlock(); - } + UpdateVersionIfNeeded(); - s_ActiveParticles.Add(this); - - // Reset the parent-child relation. - GetComponentsInChildren(false, s_TempRelatables); - for (int i = s_TempRelatables.Count - 1; 0 <= i; i--) - { - s_TempRelatables[i].OnTransformParentChanged(); - } - - s_TempRelatables.Clear(); + _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); + // Initialize. _renderer = cachedParticleSystem ? cachedParticleSystem.GetComponent() : null; - if (_renderer && Application.isPlaying) - { + if (_renderer != null) _renderer.enabled = false; - } + + CheckMaterials(); // Create objects. - _mesh = new Mesh(); - _mesh.MarkDynamic(); - CheckTrail(); + _bakedMesh = new Mesh(); + _bakedMesh.MarkDynamic(); - if (cachedParticleSystem) - { - _oldPos = cachedParticleSystem.main.scalingMode == ParticleSystemScalingMode.Local - ? rectTransform.localPosition - : rectTransform.position; - } + MeshHelper.Register(); + BakingCamera.Register(); + UIParticleUpdater.Register(this); base.OnEnable(); } @@ -265,46 +228,28 @@ namespace Coffee.UIExtensions /// protected override void OnDisable() { - // Unregister. - s_ActiveParticles.Remove(this); - if (s_ActiveParticles.Count == 0) + _tracker.Clear(); + + // Destroy object. + DestroyImmediate(_bakedMesh); + _bakedMesh = null; + + MeshHelper.Unregister(); + BakingCamera.Unregister(); + UIParticleUpdater.Unregister(this); + + CheckMaterials(); + + // Remove mask materials. + for (var i = 0; i < _maskMaterials.Length; i++) { - Canvas.willRenderCanvases -= UpdateMeshes; + StencilMaterial.Remove(_maskMaterials[i]); + _maskMaterials[i] = null; } - // Reset the parent-child relation. - for (int i = _children.Count - 1; 0 <= i; i--) - { - _children[i].SetParent(_parent); - } - - _children.Clear(); - SetParent(null); - - // Destroy objects. - DestroyImmediate(_mesh); - _mesh = null; - CheckTrail(); - base.OnDisable(); } -#if UNITY_EDITOR - /// - /// Reset to default values. - /// - protected override void Reset() - { - // Disable ParticleSystemRenderer on reset. - if (cachedParticleSystem) - { - cachedParticleSystem.GetComponent().enabled = false; - } - - base.Reset(); - } -#endif - /// /// Call to update the geometry of the Graphic onto the CanvasRenderer. /// @@ -317,20 +262,6 @@ namespace Coffee.UIExtensions /// protected override void OnTransformParentChanged() { - UIParticle newParent = null; - if (isActiveAndEnabled && !m_IgnoreParent) - { - var parentTransform = transform.parent; - while (parentTransform && (!newParent || !newParent.enabled)) - { - newParent = parentTransform.GetComponent(); - parentTransform = parentTransform.parent; - } - } - - SetParent(newParent); - - base.OnTransformParentChanged(); } /// @@ -340,327 +271,75 @@ namespace Coffee.UIExtensions { } -#if UNITY_EDITOR - /// - /// This function is called when the script is loaded or a value is changed in the inspector(Called in the editor only). - /// - protected override void OnValidate() - { - OnTransformParentChanged(); - base.OnValidate(); - } -#endif - //################################ // Private Members. //################################ - Mesh _mesh; - ParticleSystemRenderer _renderer; - UIParticle _parent; - List _children = new List(); - Matrix4x4 scaleaMatrix = default(Matrix4x4); - Vector3 _oldPos; - static readonly Vector3 minimumVec3 = new Vector3(0.0000001f, 0.0000001f, 0.0000001f); - static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[4096]; - - /// - /// Update meshes. - /// - static void UpdateMeshes() + private static bool HasMaterialChanged(Material material, ref int current) { - for (int i = 0; i < s_ActiveParticles.Count; i++) - { - if (s_ActiveParticles[i]) - { - s_ActiveParticles[i].UpdateMesh(); - } - } + var old = current; + current = material ? material.GetInstanceID() : 0; + return current != old; } - /// - /// Update meshe. - /// - void UpdateMesh() + internal void UpdateTrailParticle() { - try - { - Profiler.BeginSample("CheckTrail"); - CheckTrail(); - Profiler.EndSample(); - - if (m_ParticleSystem && canvas) - { - // I do not know why, but it worked fine when setting `transform.localPosition.z` to `0.01`. (#34, #39) - { - Vector3 pos = rectTransform.localPosition; - if (Mathf.Abs(pos.z) < 0.01f) - { - pos.z = 0.01f; - rectTransform.localPosition = pos; - } - } - - var rootCanvas = canvas.rootCanvas; - Profiler.BeginSample("Disable ParticleSystemRenderer"); - if (Application.isPlaying) - { - _renderer.enabled = false; - } - - Profiler.EndSample(); - - // #69: Editor crashes when mesh is set to null when ParticleSystem.RenderMode=Mesh - if (_renderer.renderMode == ParticleSystemRenderMode.Mesh && !_renderer.mesh) - return; - - // #61: When ParticleSystem.RenderMode=None, an error occurs - if (_renderer.renderMode == ParticleSystemRenderMode.None) - return; - - Profiler.BeginSample("Make Matrix"); - ParticleSystem.MainModule main = m_ParticleSystem.main; - scaleaMatrix = main.scalingMode == ParticleSystemScalingMode.Hierarchy - ? Matrix4x4.Scale(scale * Vector3.one) - : Matrix4x4.Scale(scale * rootCanvas.transform.localScale); - Matrix4x4 matrix = default(Matrix4x4); - switch (main.simulationSpace) - { - case ParticleSystemSimulationSpace.Local: - matrix = - scaleaMatrix - * Matrix4x4.Rotate(rectTransform.rotation).inverse - * Matrix4x4.Scale(rectTransform.lossyScale + minimumVec3).inverse; - break; - case ParticleSystemSimulationSpace.World: - matrix = - scaleaMatrix - * rectTransform.worldToLocalMatrix; - - bool isLocalScaling = main.scalingMode == ParticleSystemScalingMode.Local; - Vector3 newPos = rectTransform.position; - Vector3 delta = (newPos - _oldPos); - _oldPos = newPos; - - if (!Mathf.Approximately(scale, 0) && 0 < delta.sqrMagnitude) - { - if (isLocalScaling) - { - var s = rootCanvas.transform.localScale * scale; - delta.x *= 1f - 1f / s.x; - delta.y *= 1f - 1f / s.y; - delta.z *= 1f - 1f / s.z; - } - else - { - delta = delta * (1 - 1 / scale); - } - - int count = m_ParticleSystem.particleCount; - if (s_Particles.Length < count) - { - s_Particles = new ParticleSystem.Particle[s_Particles.Length * 2]; - } - - m_ParticleSystem.GetParticles(s_Particles); - for (int i = 0; i < count; i++) - { - var p = s_Particles[i]; - p.position = p.position + delta; - s_Particles[i] = p; - } - - m_ParticleSystem.SetParticles(s_Particles, count); - } - - break; - case ParticleSystemSimulationSpace.Custom: - break; - } - - Profiler.EndSample(); - - _mesh.Clear(); - if (0 < m_ParticleSystem.particleCount) - { - Profiler.BeginSample("Bake Mesh"); - var cam = rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay - ? UIParticleOverlayCamera.GetCameraForOvrelay(rootCanvas) - : canvas.worldCamera ?? Camera.main; - - if (!cam) - { - Profiler.EndSample(); - return; - } - - if (m_IsTrail) - { - _renderer.BakeTrailsMesh(_mesh, cam, true); - } - else - { - _renderer.BakeMesh(_mesh, cam, true); - } - - Profiler.EndSample(); - - // Apply matrix. - Profiler.BeginSample("Apply matrix to position"); - - if (QualitySettings.activeColorSpace == ColorSpace.Linear) - { - _mesh.GetColors(s_Colors); - var count_c = s_Colors.Count; - for (int i = 0; i < count_c; i++) - { - s_Colors[i] = ((Color) s_Colors[i]).gamma; - } - - _mesh.SetColors(s_Colors); - } - - _mesh.GetVertices(s_Vertices); - var count = s_Vertices.Count; - for (int i = 0; i < count; i++) - { - s_Vertices[i] = matrix.MultiplyPoint3x4(s_Vertices[i]); - } - - _mesh.SetVertices(s_Vertices); - _mesh.RecalculateBounds(); - s_Vertices.Clear(); - s_Colors.Clear(); - Profiler.EndSample(); - } - - - // Set mesh to CanvasRenderer. - Profiler.BeginSample("Set mesh and texture to CanvasRenderer"); - canvasRenderer.SetMesh(_mesh); - canvasRenderer.SetTexture(mainTexture); - - // Copy the value from MaterialPropertyBlock to CanvasRenderer (#41) - UpdateAnimatableMaterialProperties(); - - Profiler.EndSample(); - } - } - catch (System.Exception e) - { - Debug.LogException(e); - } - } - - /// - /// Checks the trail. - /// - void CheckTrail() - { - if (isActiveAndEnabled && !m_IsTrail && m_ParticleSystem && m_ParticleSystem.trails.enabled) + // Should have a UIParticle for trail particle? + if (isActiveAndEnabled && isValid && !isTrailParticle && isSpritesModeAndHasTrail) { if (!m_TrailParticle) { + // Create new UIParticle for trail particle m_TrailParticle = new GameObject("[UIParticle] Trail").AddComponent(); var trans = m_TrailParticle.transform; - trans.SetParent(transform); - trans.localPosition = Vector3.zero; - trans.localRotation = Quaternion.identity; + trans.SetPositionAndRotation(Vector3.zero, Quaternion.identity); trans.localScale = Vector3.one; + trans.SetParent(transform, false); - m_TrailParticle._renderer = GetComponent(); - m_TrailParticle.m_ParticleSystem = GetComponent(); + m_TrailParticle._renderer = _renderer; + m_TrailParticle.m_ParticleSystem = m_ParticleSystem; m_TrailParticle.m_IsTrail = true; } - m_TrailParticle.enabled = true; + m_TrailParticle.gameObject.hideFlags = HideFlags.DontSave; } else if (m_TrailParticle) { - m_TrailParticle.enabled = false; - } - } - - /// - /// Set the parent of the soft mask. - /// - /// The parent soft mask to use. - void SetParent(UIParticle newParent) - { - if (_parent != newParent && this != newParent) - { - if (_parent && _parent._children.Contains(this)) - { - _parent._children.Remove(this); - _parent._children.RemoveAll(x => x == null); - } - - _parent = newParent; - } - - if (_parent && !_parent._children.Contains(this)) - { - _parent._children.Add(this); - } - } - - /// - /// Copy the value from MaterialPropertyBlock to CanvasRenderer (#41) - /// - void UpdateAnimatableMaterialProperties() - { + // Destroy a UIParticle for trail particle. #if UNITY_EDITOR - if (!Application.isPlaying) - return; + if (!Application.isPlaying) + DestroyImmediate(m_TrailParticle.gameObject); + else #endif - if (0 == m_AnimatableProperties.Length) - return; + Destroy(m_TrailParticle.gameObject); - _renderer.GetPropertyBlock(s_Mpb); - for (int i = 0; i < canvasRenderer.materialCount; i++) - { - var mat = canvasRenderer.GetMaterial(i); - foreach (var ap in m_AnimatableProperties) - { - switch (ap.type) - { - case ShaderPropertyType.Color: - mat.SetColor(ap.id, s_Mpb.GetColor(ap.id)); - break; - case ShaderPropertyType.Vector: - mat.SetVector(ap.id, s_Mpb.GetVector(ap.id)); - break; - case ShaderPropertyType.Float: - case ShaderPropertyType.Range: - mat.SetFloat(ap.id, s_Mpb.GetFloat(ap.id)); - break; - case ShaderPropertyType.Texture: - mat.SetTexture(ap.id, s_Mpb.GetTexture(ap.id)); - break; - } - } + m_TrailParticle = null; } } -#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); - - static Texture2D GetActualTexture(Sprite sprite) + internal void CheckMaterials() { - if (!sprite) return null; + if (!_renderer) return; - if (Application.isPlaying) return sprite.texture; - var ret = miGetActiveAtlasTexture.Invoke(null, new[] {sprite}) as Texture2D; - return ret ? ret : sprite.texture; + var matChanged = HasMaterialChanged(_renderer.sharedMaterial, ref _cachedSharedMaterialId); + var matChanged2 = HasMaterialChanged(_renderer.trailMaterial, ref _cachedTrailMaterialId); + var modeChanged = _cachedSpritesModeAndHasTrail != isSpritesModeAndHasTrail; + _cachedSpritesModeAndHasTrail = isSpritesModeAndHasTrail; + + if (matChanged || matChanged2 || modeChanged) + SetMaterialDirty(); } -#else - static Texture2D GetActualTexture(Sprite sprite) + + private void UpdateVersionIfNeeded() { - return sprite ? sprite.texture : null; + if (Mathf.Approximately(m_Scale, 0)) return; + + var parent = GetComponentInParent(); + if (m_IgnoreParent || !parent) + scale3D = m_Scale * transform.localScale; + else + scale3D = transform.localScale; + m_Scale = 0; } -#endif } } diff --git a/Scripts/UIParticleOverlayCamera.cs b/Scripts/UIParticleOverlayCamera.cs deleted file mode 100644 index 15a95f3..0000000 --- a/Scripts/UIParticleOverlayCamera.cs +++ /dev/null @@ -1,170 +0,0 @@ -using UnityEngine; -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR -using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility; -#endif - -namespace Coffee.UIExtensions -{ - /// - /// - [ExecuteInEditMode] - [AddComponentMenu("")] - public class UIParticleOverlayCamera : MonoBehaviour - { - //################################ - // Public/Protected Members. - //################################ - /// - /// Get instance object. - /// If instance does not exist, Find instance in scene, or create new one. - /// - public static UIParticleOverlayCamera instance - { - get - { -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - // If current scene is prefab mode, create OverlayCamera for editor. - var prefabStage = PrefabStageUtility.GetCurrentPrefabStage(); - if (prefabStage != null && prefabStage.scene.isLoaded) - { - if (!s_InstanceForPrefabMode) - { - // This GameObject is not saved in prefab. - // This GameObject is not shown in the hierarchy view. - // When you exit prefab mode, this GameObject is destroyed automatically. - var go = new GameObject(typeof(UIParticleOverlayCamera).Name + "_ForEditor") - { - hideFlags = HideFlags.HideAndDontSave, - tag = "EditorOnly", - }; - UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(go, prefabStage.scene); - s_InstanceForPrefabMode = go.AddComponent(); - } - - return s_InstanceForPrefabMode; - } -#endif - - // Find instance in scene, or create new one. - if (object.ReferenceEquals(s_Instance, null)) - { - s_Instance = FindObjectOfType() ?? - new GameObject(typeof(UIParticleOverlayCamera).Name, typeof(UIParticleOverlayCamera)).GetComponent(); - s_Instance.gameObject.SetActive(true); - s_Instance.enabled = true; - } - - return s_Instance; - } - } - - public static Camera GetCameraForOvrelay(Canvas canvas) - { - var i = instance; - var rt = canvas.rootCanvas.transform as RectTransform; - var cam = i.cameraForOvrelay; - var trans = i.transform; - cam.enabled = false; - - var pos = rt.localPosition; - cam.orthographic = true; - cam.orthographicSize = Mathf.Max(pos.x, pos.y); - cam.nearClipPlane = 0.3f; - cam.farClipPlane = 1000f; - pos.z -= 100; - trans.localPosition = pos; - - return cam; - } - - //################################ - // Private Members. - //################################ - Camera cameraForOvrelay - { - get { return m_Camera ? m_Camera : (m_Camera = GetComponent()) ? m_Camera : (m_Camera = gameObject.AddComponent()); } - } - - Camera m_Camera; - static UIParticleOverlayCamera s_Instance; -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - static UIParticleOverlayCamera s_InstanceForPrefabMode; -#endif - - /// - /// Awake is called when the script instance is being loaded. - /// - void Awake() - { -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - // OverlayCamera for editor. - if (hideFlags == HideFlags.HideAndDontSave || s_InstanceForPrefabMode == this) - { - s_InstanceForPrefabMode = GetComponent(); - return; - } -#endif - - // Hold the instance. - if (s_Instance == null) - { - s_Instance = GetComponent(); - } - // If the instance is duplicated, destroy itself. - else if (s_Instance != this) - { - UnityEngine.Debug.LogWarning("Multiple " + typeof(UIParticleOverlayCamera).Name + " in scene.", this.gameObject); - enabled = false; -#if UNITY_EDITOR - - if (!Application.isPlaying) - { - DestroyImmediate(gameObject); - } - else -#endif - { - Destroy(gameObject); - } - - return; - } - - cameraForOvrelay.enabled = false; - - // Singleton has DontDestroy flag. - if (Application.isPlaying) - { - DontDestroyOnLoad(gameObject); - } - } - - /// - /// This function is called when the MonoBehaviour will be destroyed. - /// - void OnEnable() - { - gameObject.hideFlags = HideFlags.HideAndDontSave; - gameObject.tag = "EditorOnly"; - } - - /// - /// This function is called when the MonoBehaviour will be destroyed. - /// - void OnDestroy() - { -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - if (s_InstanceForPrefabMode == this) - { - s_InstanceForPrefabMode = null; - } -#endif - - // Clear instance on destroy. - if (s_Instance == this) - { - s_Instance = null; - } - } - } -} diff --git a/Scripts/UIParticleUpdater.cs b/Scripts/UIParticleUpdater.cs new file mode 100755 index 0000000..e262bf9 --- /dev/null +++ b/Scripts/UIParticleUpdater.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Profiling; + +namespace Coffee.UIExtensions +{ + public static class UIParticleUpdater + { + static readonly List s_ActiveParticles = new List(); + static MaterialPropertyBlock s_Mpb; + + 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); + } + +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] +#endif + [RuntimeInitializeOnLoadMethod] + private static void InitializeOnLoad() + { + Canvas.willRenderCanvases -= Refresh; + Canvas.willRenderCanvases += Refresh; + } + + private static void Refresh() + { + for (var i = 0; i < s_ActiveParticles.Count; i++) + { + try + { + Refresh(s_ActiveParticles[i]); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + private static void Refresh(UIParticle particle) + { + if (!particle) return; + + Profiler.BeginSample("Modify scale"); + ModifyScale(particle); + Profiler.EndSample(); + + if (!particle.isValid) return; + + Profiler.BeginSample("Update trail particle"); + particle.UpdateTrailParticle(); + Profiler.EndSample(); + + Profiler.BeginSample("Check materials"); + particle.CheckMaterials(); + Profiler.EndSample(); + + Profiler.BeginSample("Make matrix"); + var scaledMatrix = GetScaledMatrix(particle); + Profiler.EndSample(); + + Profiler.BeginSample("Bake mesh"); + BakeMesh(particle, scaledMatrix); + Profiler.EndSample(); + + if (QualitySettings.activeColorSpace == ColorSpace.Linear) + { + Profiler.BeginSample("Modify color space to linear"); + particle.bakedMesh.ModifyColorSpaceToLinear(); + Profiler.EndSample(); + } + + Profiler.BeginSample("Set mesh and texture to CanvasRenderer"); + UpdateMeshAndTexture(particle); + Profiler.EndSample(); + + Profiler.BeginSample("Update Animatable Material Properties"); + UpdateAnimatableMaterialProperties(particle); + Profiler.EndSample(); + } + + private static void ModifyScale(UIParticle particle) + { + if (particle.isTrailParticle) return; + + var modifiedScale = particle.m_Scale3D; + + // Ignore Canvas scaling. + if (particle.ignoreCanvasScaler && particle.canvas) + { + var s = particle.canvas.rootCanvas.transform.localScale; + var sInv = new Vector3( + Mathf.Approximately(s.x, 0) ? 1 : 1 / s.x, + Mathf.Approximately(s.y, 0) ? 1 : 1 / s.y, + Mathf.Approximately(s.z, 0) ? 1 : 1 / s.z); + modifiedScale = Vector3.Scale(modifiedScale, sInv); + } + + // Scale is already modified. + var tr = particle.transform; + if (Mathf.Approximately((tr.localScale - modifiedScale).sqrMagnitude, 0)) return; + + tr.localScale = modifiedScale; + } + + private static void UpdateMeshAndTexture(UIParticle particle) + { + // Update mesh. + particle.canvasRenderer.SetMesh(particle.bakedMesh); + + // Non sprite mode: external texture is not used. + if (!particle.isSpritesMode || particle.isTrailParticle) + { + particle.canvasRenderer.SetTexture(null); + return; + } + + // Sprite mode: get sprite's texture. + Texture tex = null; + Profiler.BeginSample("Check TextureSheetAnimation module"); + var tsaModule = particle.cachedParticleSystem.textureSheetAnimation; + if (tsaModule.enabled && tsaModule.mode == ParticleSystemAnimationMode.Sprites) + { + var count = tsaModule.spriteCount; + for (var i = 0; i < count; i++) + { + var sprite = tsaModule.GetSprite(i); + if (!sprite) continue; + + tex = sprite.GetActualTexture(); + break; + } + } + + Profiler.EndSample(); + + particle.canvasRenderer.SetTexture(tex); + } + + private static Matrix4x4 GetScaledMatrix(UIParticle particle) + { + var transform = particle.transform; + var tr = particle.isTrailParticle ? transform.parent : transform; + var main = particle.mainModule; + var space = main.simulationSpace; + if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) + space = ParticleSystemSimulationSpace.Local; + + switch (space) + { + case ParticleSystemSimulationSpace.Local: + var canvasTr = particle.canvas.rootCanvas.transform; + return Matrix4x4.Rotate(tr.localRotation).inverse + * Matrix4x4.Rotate(canvasTr.localRotation).inverse + * Matrix4x4.Scale(tr.localScale).inverse + * Matrix4x4.Scale(canvasTr.localScale).inverse; + case ParticleSystemSimulationSpace.World: + return transform.worldToLocalMatrix; + case ParticleSystemSimulationSpace.Custom: + // #78: Support custom simulation space. + return transform.worldToLocalMatrix + * Matrix4x4.Translate(main.customSimulationSpace.position); + default: + return Matrix4x4.identity; + } + } + + private static void BakeMesh(UIParticle particle, Matrix4x4 scaledMatrix) + { + // Clear mesh before bake. + MeshHelper.Clear(); + particle.bakedMesh.Clear(); + + // No particle to render. + if (particle.cachedParticleSystem.particleCount <= 0) return; + + // Get camera for baking mesh. + var cam = BakingCamera.GetCamera(particle.canvas); + var renderer = particle.cachedRenderer; + var trail = particle.trailModule; + + Profiler.BeginSample("Bake mesh"); + if (!particle.isSpritesMode) // Non sprite mode: bake main particle and trail particle. + { + if (CanBakeMesh(renderer)) + renderer.BakeMesh(MeshHelper.GetTemporaryMesh(), cam, true); + + if (trail.enabled) + renderer.BakeTrailsMesh(MeshHelper.GetTemporaryMesh(), cam, true); + } + else if (particle.isTrailParticle) // Sprite mode (trail): bake trail particle. + { + if (trail.enabled) + renderer.BakeTrailsMesh(MeshHelper.GetTemporaryMesh(), cam, true); + } + else // Sprite mode (main): bake main particle. + { + if (CanBakeMesh(renderer)) + renderer.BakeMesh(MeshHelper.GetTemporaryMesh(), cam, true); + } + + Profiler.EndSample(); + + Profiler.BeginSample("Apply matrix to position"); + MeshHelper.CombineMesh(particle.bakedMesh, scaledMatrix); + Profiler.EndSample(); + } + + private static bool CanBakeMesh(ParticleSystemRenderer renderer) + { + // #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh` + if (renderer.renderMode == ParticleSystemRenderMode.Mesh && !renderer.mesh) return false; + + // #61: When `ParticleSystem.RenderMode = None`, an error occurs + if (renderer.renderMode == ParticleSystemRenderMode.None) return false; + + return true; + } + + /// + /// Copy the value from MaterialPropertyBlock to CanvasRenderer + /// + private static void UpdateAnimatableMaterialProperties(UIParticle particle) + { +#if UNITY_EDITOR + if (!Application.isPlaying) return; +#endif + if (0 == particle.m_AnimatableProperties.Length) return; + if (0 == particle.canvasRenderer.materialCount) return; + + var mat = particle.canvasRenderer.GetMaterial(0); + if (!mat) return; + + // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer + if (s_Mpb == null) + s_Mpb = new MaterialPropertyBlock(); + particle.cachedRenderer.GetPropertyBlock(s_Mpb); + foreach (var ap in particle.m_AnimatableProperties) + { + ap.UpdateMaterialProperties(mat, s_Mpb); + } + + s_Mpb.Clear(); + } + } +} diff --git a/Scripts/UIParticleUpdater.cs.meta b/Scripts/UIParticleUpdater.cs.meta new file mode 100644 index 0000000..f00ae75 --- /dev/null +++ b/Scripts/UIParticleUpdater.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a0e708dc2e3034ba9a5c51db4252c7e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: -100 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 145dba3..257ee8f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.coffee.ui-particle", "displayName": "UI Particle", "description": "This plugin provide a component to render particle effect for uGUI.\nThe particle rendering is maskable and sortable, without Camera, RenderTexture or Canvas.", - "version": "3.0.0-preview.17", + "version": "3.0.0-preview.18", "unity": "2018.2", "license": "MIT", "repository": {