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](pull/120/head50e749c183
)) * fix camera for baking mesh ([6395a4f](6395a4fa74
)) * support .Net Framework 3.5 (again) ([23fcb06](23fcb06bf9
)) ### Features * 3.0.0 updater ([f99292b](f99292b9a1
)) * add menu to create UIParticle ([14f1c78](14f1c782ff
)) * Combine baked meshes to improve performance ([633d058](633d058756
)) * improve performance ([77c056a](77c056ad5f
)) * optimization for vertices transforms and adding node for trails ([e070e8d](e070e8d5ee
)), closes [#75](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/75) * option to ignoring canvas scaling ([fe85fed](fe85fed3c0
)) * support 3d scaling ([42a84bc](42a84bc5e1
)) * support custom simulation space ([a83e647](a83e64761c
)), closes [#78](https://github.com/mob-sakai/ParticleEffectForUGUI/issues/78) * support for particle systems including trail only ([f389d39](f389d39953
)), 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.
parent
785709fa02
commit
eccb3ecb5e
27
CHANGELOG.md
27
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)
|
# [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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 0defeb6d13f61475eacce2b101cb2248
|
guid: c434da72184404a0cbdf8e7a529a41bf
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
|
@ -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<BakingCamera>() ?? 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<BakingCamera>();
|
||||||
|
inst._camera = gameObject.AddComponent<Camera>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: f69dfb25b53b14addbd71dbebdbaa132
|
guid: 999f0ea10cb5f48ed89190a0ca83dd53
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
|
@ -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<string> s_ActiveNames = new List<string>();
|
||||||
|
static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 60feb9aeb134e4ec789c0e63b017997f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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 = "<color=#c34062><b>[CustomCompiler]</b></color> ";
|
|
||||||
|
|
||||||
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 = "<b><color=#9a4089>[AsmdefEx]</color></b> ";
|
|
||||||
|
|
||||||
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<string> 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<string>())
|
|
||||||
.Except(remove ?? Enumerable.Empty<string>())
|
|
||||||
.Union(ignoreAccessChecks ? new[] {"IGNORE_ACCESS_CHECKS"} : Enumerable.Empty<string>())
|
|
||||||
.Union(new[] {"ASMDEF_EX"})
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ModifyFiles(IEnumerable<string> 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<Match>()
|
|
||||||
.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<Match>()
|
|
||||||
.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<object>().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: <b>{0} should be recompiled.</b>", 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("<b><color=#9a4089>[AsmdefEx ({0})]</color></b> ", 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("<b>Request to recompile: {0} ({1})</b>", assemblyName, asmdefPath);
|
|
||||||
|
|
||||||
AssetDatabase.ImportAsset(asmdefPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -2,6 +2,6 @@ fileFormatVersion: 2
|
||||||
guid: 69deabfe8eed7495886c9daa69c9826a
|
guid: 69deabfe8eed7495886c9daa69c9826a
|
||||||
AssemblyDefinitionImporter:
|
AssemblyDefinitionImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
userData: '{"IgnoreAccessChecks":true,"ModifySymbols":""}'
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
|
|
@ -1,129 +1,32 @@
|
||||||
#if IGNORE_ACCESS_CHECKS // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY.
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEditor.UI;
|
using UnityEditor.UI;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System;
|
|
||||||
using ShaderPropertyType = Coffee.UIExtensions.UIParticle.AnimatableProperty.ShaderPropertyType;
|
|
||||||
|
|
||||||
namespace Coffee.UIExtensions
|
namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
[CustomEditor(typeof(UIParticle))]
|
[CustomEditor(typeof(UIParticle))]
|
||||||
[CanEditMultipleObjects]
|
[CanEditMultipleObjects]
|
||||||
public class UIParticleEditor : GraphicEditor
|
internal class UIParticleEditor : GraphicEditor
|
||||||
{
|
{
|
||||||
class AnimatedPropertiesEditor
|
|
||||||
{
|
|
||||||
static readonly List<string> s_ActiveNames = new List<string>();
|
|
||||||
static readonly System.Text.StringBuilder s_Sb = new System.Text.StringBuilder();
|
|
||||||
|
|
||||||
public string name;
|
|
||||||
public ShaderPropertyType type;
|
|
||||||
|
|
||||||
static string CollectActiveNames(SerializedProperty sp, List<string> 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.
|
// Constant or Static Members.
|
||||||
//################################
|
//################################
|
||||||
static readonly GUIContent s_ContentParticleMaterial = new GUIContent("Particle Material", "The material for rendering particles");
|
private 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");
|
private static readonly GUIContent s_ContentTrailMaterial = new GUIContent("Trail Material", "The material for rendering particle trails");
|
||||||
static readonly List<ParticleSystem> s_ParticleSystems = new List<ParticleSystem>();
|
private static readonly GUIContent s_ContentAdvancedOptions = new GUIContent("Advanced Options");
|
||||||
static readonly Color s_GizmoColor = new Color(1f, 0.7f, 0.7f, 0.9f);
|
private static readonly GUIContent s_Content3D = new GUIContent("3D");
|
||||||
|
private static readonly GUIContent s_ContentScale = new GUIContent("Scale");
|
||||||
|
private static readonly List<ParticleSystem> s_ParticleSystems = new List<ParticleSystem>();
|
||||||
|
|
||||||
static readonly List<string> s_MaskablePropertyNames = new List<string>()
|
private SerializedProperty _spParticleSystem;
|
||||||
|
private SerializedProperty _spScale3D;
|
||||||
|
private SerializedProperty _spIgnoreCanvasScaler;
|
||||||
|
private SerializedProperty _spAnimatableProperties;
|
||||||
|
private bool _xyzMode;
|
||||||
|
|
||||||
|
private static readonly List<string> s_MaskablePropertyNames = new List<string>
|
||||||
{
|
{
|
||||||
"_Stencil",
|
"_Stencil",
|
||||||
"_StencilComp",
|
"_StencilComp",
|
||||||
|
@ -133,6 +36,7 @@ namespace Coffee.UIExtensions
|
||||||
"_ColorMask",
|
"_ColorMask",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//################################
|
//################################
|
||||||
// Public/Protected Members.
|
// Public/Protected Members.
|
||||||
//################################
|
//################################
|
||||||
|
@ -143,16 +47,9 @@ namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
base.OnEnable();
|
base.OnEnable();
|
||||||
_spParticleSystem = serializedObject.FindProperty("m_ParticleSystem");
|
_spParticleSystem = serializedObject.FindProperty("m_ParticleSystem");
|
||||||
_spTrailParticle = serializedObject.FindProperty("m_TrailParticle");
|
_spScale3D = serializedObject.FindProperty("m_Scale3D");
|
||||||
_spScale = serializedObject.FindProperty("m_Scale");
|
_spIgnoreCanvasScaler = serializedObject.FindProperty("m_IgnoreCanvasScaler");
|
||||||
_spIgnoreParent = serializedObject.FindProperty("m_IgnoreParent");
|
|
||||||
_spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
|
_spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties");
|
||||||
_particles = targets.Cast<UIParticle>().ToArray();
|
|
||||||
_shapeModuleUIs = null;
|
|
||||||
|
|
||||||
var targetsGos = targets.Cast<UIParticle>().Select(x => x.gameObject).ToArray();
|
|
||||||
_inspector = Resources.FindObjectsOfTypeAll<ParticleSystemInspector>()
|
|
||||||
.FirstOrDefault(x => x.targets.Cast<ParticleSystem>().Select(x => x.gameObject).SequenceEqual(targetsGos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -160,15 +57,19 @@ namespace Coffee.UIExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
|
var current = target as UIParticle;
|
||||||
|
if (current == null) return;
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(true);
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
EditorGUILayout.PropertyField(_spParticleSystem);
|
EditorGUILayout.PropertyField(_spParticleSystem);
|
||||||
EditorGUI.EndDisabledGroup();
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
|
// Draw materials.
|
||||||
EditorGUI.indentLevel++;
|
EditorGUI.indentLevel++;
|
||||||
var ps = _spParticleSystem.objectReferenceValue as ParticleSystem;
|
var ps = _spParticleSystem.objectReferenceValue as ParticleSystem;
|
||||||
if (ps)
|
if (ps != null)
|
||||||
{
|
{
|
||||||
var pr = ps.GetComponent<ParticleSystemRenderer>();
|
var pr = ps.GetComponent<ParticleSystemRenderer>();
|
||||||
var sp = new SerializedObject(pr).FindProperty("m_Materials");
|
var sp = new SerializedObject(pr).FindProperty("m_Materials");
|
||||||
|
@ -194,22 +95,19 @@ namespace Coffee.UIExtensions
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(true);
|
// Advanced Options
|
||||||
EditorGUILayout.PropertyField(_spTrailParticle);
|
EditorGUILayout.Space();
|
||||||
EditorGUI.EndDisabledGroup();
|
EditorGUILayout.LabelField(s_ContentAdvancedOptions, EditorStyles.boldLabel);
|
||||||
|
|
||||||
var current = target as UIParticle;
|
_xyzMode = DrawFloatOrVector3Field(_spScale3D, _xyzMode);
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(_spIgnoreParent);
|
EditorGUILayout.PropertyField(_spIgnoreCanvasScaler);
|
||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(!current.isRoot);
|
|
||||||
EditorGUILayout.PropertyField(_spScale);
|
|
||||||
EditorGUI.EndDisabledGroup();
|
|
||||||
|
|
||||||
// AnimatableProperties
|
// AnimatableProperties
|
||||||
AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, current.material);
|
AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, current.material);
|
||||||
|
|
||||||
current.GetComponentsInChildren<ParticleSystem>(true, s_ParticleSystems);
|
// Fix
|
||||||
|
current.GetComponentsInChildren(true, s_ParticleSystems);
|
||||||
if (s_ParticleSystems.Any(x => x.GetComponent<UIParticle>() == null))
|
if (s_ParticleSystems.Any(x => x.GetComponent<UIParticle>() == null))
|
||||||
{
|
{
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
|
@ -229,75 +127,54 @@ namespace Coffee.UIExtensions
|
||||||
|
|
||||||
s_ParticleSystems.Clear();
|
s_ParticleSystems.Clear();
|
||||||
|
|
||||||
|
// Does the shader support UI masks?
|
||||||
if (current.maskable && current.material && current.material.shader)
|
if (current.maskable && current.material && current.material.shader)
|
||||||
{
|
{
|
||||||
var mat = current.material;
|
var mat = current.material;
|
||||||
var shader = mat.shader;
|
var shader = mat.shader;
|
||||||
foreach (var propName in s_MaskablePropertyNames)
|
foreach (var propName in s_MaskablePropertyNames)
|
||||||
{
|
{
|
||||||
if (!mat.HasProperty(propName))
|
if (mat.HasProperty(propName)) continue;
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox(string.Format("Shader {0} doesn't have '{1}' property. This graphic is not maskable.", shader.name, propName), MessageType.Warning);
|
EditorGUILayout.HelpBox(string.Format("Shader '{0}' doesn't have '{1}' property. This graphic cannot be masked.", shader.name, propName), MessageType.Warning);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool DrawFloatOrVector3Field(SerializedProperty sp, bool showXyz)
|
||||||
//################################
|
|
||||||
// Private Members.
|
|
||||||
//################################
|
|
||||||
SerializedProperty _spParticleSystem;
|
|
||||||
SerializedProperty _spTrailParticle;
|
|
||||||
SerializedProperty _spScale;
|
|
||||||
SerializedProperty _spIgnoreParent;
|
|
||||||
SerializedProperty _spAnimatableProperties;
|
|
||||||
UIParticle[] _particles;
|
|
||||||
ShapeModuleUI[] _shapeModuleUIs;
|
|
||||||
ParticleSystemInspector _inspector;
|
|
||||||
|
|
||||||
void OnSceneGUI()
|
|
||||||
{
|
{
|
||||||
_shapeModuleUIs = _shapeModuleUIs ?? _inspector?.m_ParticleEffectUI?.m_Emitters?.SelectMany(x => x.m_Modules).OfType<ShapeModuleUI>()?.ToArray();
|
var x = sp.FindPropertyRelative("x");
|
||||||
if (_shapeModuleUIs == null || _shapeModuleUIs.Length == 0 || _shapeModuleUIs[0].GetParticleSystem() != (target as UIParticle).cachedParticleSystem)
|
var y = sp.FindPropertyRelative("y");
|
||||||
return;
|
var z = sp.FindPropertyRelative("z");
|
||||||
|
|
||||||
Action postAction = () => { };
|
showXyz |= !Mathf.Approximately(x.floatValue, y.floatValue) ||
|
||||||
Color origin = ShapeModuleUI.s_GizmoColor.m_Color;
|
!Mathf.Approximately(y.floatValue, z.floatValue) ||
|
||||||
Color originDark = ShapeModuleUI.s_GizmoColor.m_Color;
|
y.hasMultipleDifferentValues ||
|
||||||
ShapeModuleUI.s_GizmoColor.m_Color = s_GizmoColor;
|
z.hasMultipleDifferentValues;
|
||||||
ShapeModuleUI.s_GizmoColor.m_OptionalDarkColor = s_GizmoColor;
|
|
||||||
|
|
||||||
_particles
|
EditorGUILayout.BeginHorizontal();
|
||||||
.Distinct()
|
if (showXyz)
|
||||||
.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
|
|
||||||
{
|
{
|
||||||
foreach (var ui in _shapeModuleUIs)
|
EditorGUILayout.PropertyField(sp);
|
||||||
ui.OnSceneViewGUI();
|
|
||||||
}
|
}
|
||||||
catch
|
else
|
||||||
{
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
EditorGUILayout.PropertyField(x, s_ContentScale);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
z.floatValue = y.floatValue = x.floatValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
postAction();
|
EditorGUI.BeginChangeCheck();
|
||||||
ShapeModuleUI.s_GizmoColor.m_Color = origin;
|
showXyz = GUILayout.Toggle(showXyz, s_Content3D, EditorStyles.miniButton, GUILayout.Width(30));
|
||||||
ShapeModuleUI.s_GizmoColor.m_OptionalDarkColor = originDark;
|
if (EditorGUI.EndChangeCheck() && !showXyz)
|
||||||
|
z.floatValue = y.floatValue = x.floatValue;
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
return showXyz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY.
|
|
||||||
|
|
|
@ -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.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Coffee.UIExtensions
|
namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
public class UIParticleMenu
|
public class UIParticleMenu
|
||||||
{
|
{
|
||||||
|
#if !UNITY_2019_1_OR_NEWER
|
||||||
static string GetPreviousSamplePath(string displayName, string sampleName)
|
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);
|
var sampleRootInfo = new DirectoryInfo(sampleRoot);
|
||||||
if (!sampleRootInfo.Exists) return null;
|
if (!sampleRootInfo.Exists) return null;
|
||||||
|
|
||||||
|
@ -27,12 +27,12 @@ namespace Coffee.UIExtensions
|
||||||
|
|
||||||
static void ImportSample(string packageName, string sampleName)
|
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 json = File.ReadAllText(jsonPath);
|
||||||
string version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
|
string version = Regex.Match(json, "\"version\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
|
||||||
string displayName = Regex.Match(json, "\"displayName\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
|
string displayName = Regex.Match(json, "\"displayName\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
|
||||||
string src = $"{Path.GetDirectoryName(jsonPath)}/Samples~/{sampleName}";
|
string src = string.Format("{0}/Samples~/{1}", Path.GetDirectoryName(jsonPath), sampleName);
|
||||||
string dst = $"Assets/Samples/{displayName}/{version}/{sampleName}";
|
string dst = string.Format("Assets/Samples/{0}/{1}/{2}", displayName, version, sampleName);
|
||||||
string previous = GetPreviousSamplePath(displayName, sampleName);
|
string previous = GetPreviousSamplePath(displayName, sampleName);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(previous))
|
if (!string.IsNullOrEmpty(previous))
|
||||||
|
@ -43,11 +43,19 @@ namespace Coffee.UIExtensions
|
||||||
if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel"))
|
if (!EditorUtility.DisplayDialog("Sample Importer", msg, "OK", "Cancel"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileUtil.DeleteFileOrDirectory(previous);
|
|
||||||
FileUtil.DeleteFileOrDirectory(previous + ".meta");
|
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);
|
AssetDatabase.ImportAsset(dst, ImportAssetOptions.ImportRecursive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +64,45 @@ namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
ImportSample("com.coffee.ui-particle", "Demo");
|
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<ParticleSystemRenderer>();
|
||||||
|
var defaultMat = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Particle.mat");
|
||||||
|
renderer.material = defaultMat ? defaultMat : renderer.material;
|
||||||
|
|
||||||
|
// Set to hierarchy mode
|
||||||
|
var particleSystem = ps.GetComponent<ParticleSystem>();
|
||||||
|
var main = particleSystem.main;
|
||||||
|
main.scalingMode = ParticleSystemScalingMode.Hierarchy;
|
||||||
|
|
||||||
|
// Add UIParticle.
|
||||||
|
var uiParticle = ps.AddComponent<UIParticle>();
|
||||||
|
uiParticle.ignoreCanvasScaler = true;
|
||||||
|
uiParticle.scale = 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#endif // [ASMDEFEX] DO NOT REMOVE THIS LINE MANUALLY.
|
|
||||||
|
|
|
@ -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<Color32> s_Colors = new List<Color32>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f689ea5a2e9f140288c8874127aa9ee0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d188d31b140094ebc84a9caafbc7ac71
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -1,12 +1,6 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Profiling;
|
using UnityEngine.Rendering;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using ShaderPropertyType = Coffee.UIExtensions.UIParticle.AnimatableProperty.ShaderPropertyType;
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Coffee.UIExtensions
|
namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
|
@ -17,16 +11,6 @@ namespace Coffee.UIExtensions
|
||||||
[RequireComponent(typeof(CanvasRenderer))]
|
[RequireComponent(typeof(CanvasRenderer))]
|
||||||
public class UIParticle : MaskableGraphic
|
public class UIParticle : MaskableGraphic
|
||||||
{
|
{
|
||||||
//################################
|
|
||||||
// Constant or Readonly Static Members.
|
|
||||||
//################################
|
|
||||||
static readonly int s_IdMainTex = Shader.PropertyToID("_MainTex");
|
|
||||||
static readonly List<Vector3> s_Vertices = new List<Vector3>();
|
|
||||||
static readonly List<Color32> s_Colors = new List<Color32>();
|
|
||||||
static readonly List<UIParticle> s_TempRelatables = new List<UIParticle>();
|
|
||||||
static readonly List<UIParticle> s_ActiveParticles = new List<UIParticle>();
|
|
||||||
|
|
||||||
|
|
||||||
//################################
|
//################################
|
||||||
// Serialize Members.
|
// Serialize Members.
|
||||||
//################################
|
//################################
|
||||||
|
@ -34,153 +18,36 @@ namespace Coffee.UIExtensions
|
||||||
ParticleSystem m_ParticleSystem;
|
ParticleSystem m_ParticleSystem;
|
||||||
|
|
||||||
[Tooltip("The UIParticle to render trail effect")] [SerializeField]
|
[Tooltip("The UIParticle to render trail effect")] [SerializeField]
|
||||||
UIParticle m_TrailParticle;
|
internal UIParticle m_TrailParticle;
|
||||||
|
|
||||||
[HideInInspector] [SerializeField] bool m_IsTrail = false;
|
[HideInInspector] [SerializeField] bool m_IsTrail = false;
|
||||||
|
|
||||||
[Tooltip("Particle effect scale")] [SerializeField]
|
[Tooltip("Ignore canvas scaler")] [SerializeField]
|
||||||
float m_Scale = 1;
|
bool m_IgnoreCanvasScaler = false;
|
||||||
|
|
||||||
[Tooltip("Ignore parent scale")] [SerializeField]
|
[Tooltip("Ignore parent scale")] [SerializeField]
|
||||||
bool m_IgnoreParent = false;
|
bool m_IgnoreParent = false;
|
||||||
|
|
||||||
[Tooltip("Animatable material properties. AnimationでParticleSystemのマテリアルプロパティを変更する場合、有効にしてください。")] [SerializeField]
|
[Tooltip("Particle effect scale")] [SerializeField]
|
||||||
AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0];
|
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]
|
[Tooltip("Particle effect scale")] [SerializeField]
|
||||||
public class AnimatableProperty : ISerializationCallbackReceiver
|
internal Vector3 m_Scale3D = Vector3.one;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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/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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Particle effect scale.
|
|
||||||
/// </summary>
|
|
||||||
public float scale
|
|
||||||
{
|
|
||||||
get { return _parent ? _parent.scale : m_Scale; }
|
|
||||||
set { m_Scale = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Should the soft mask ignore parent soft masks?
|
|
||||||
/// </summary>
|
|
||||||
/// <value>If set to true the soft mask will ignore any parent soft mask settings.</value>
|
|
||||||
public bool ignoreParent
|
|
||||||
{
|
|
||||||
get { return m_IgnoreParent; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (m_IgnoreParent != value)
|
|
||||||
{
|
|
||||||
m_IgnoreParent = value;
|
|
||||||
OnTransformParentChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is this the root UIParticle?
|
|
||||||
/// </summary>
|
|
||||||
public bool isRoot
|
|
||||||
{
|
|
||||||
get { return !_parent; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should this graphic be considered a target for raycasting?
|
/// Should this graphic be considered a target for raycasting?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -191,7 +58,7 @@ namespace Coffee.UIExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ParticleSystem.
|
/// Cached ParticleSystem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ParticleSystem cachedParticleSystem
|
public ParticleSystem cachedParticleSystem
|
||||||
{
|
{
|
||||||
|
@ -199,21 +66,134 @@ namespace Coffee.UIExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform material modification in this function.
|
/// Cached ParticleSystem.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Modified material.</returns>
|
internal ParticleSystemRenderer cachedRenderer
|
||||||
/// <param name="baseMaterial">Configured Material.</param>
|
|
||||||
public override Material GetModifiedMaterial(Material baseMaterial)
|
|
||||||
{
|
{
|
||||||
Material mat = null;
|
get { return _renderer; }
|
||||||
if (!_renderer)
|
}
|
||||||
mat = baseMaterial;
|
|
||||||
else if (m_AnimatableProperties.Length == 0)
|
|
||||||
mat = _renderer.sharedMaterial;
|
|
||||||
else
|
|
||||||
mat = new Material(material);
|
|
||||||
|
|
||||||
return base.GetModifiedMaterial(mat);
|
public bool ignoreCanvasScaler
|
||||||
|
{
|
||||||
|
get { return m_IgnoreCanvasScaler; }
|
||||||
|
set { m_IgnoreCanvasScaler = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Particle effect scale.
|
||||||
|
/// </summary>
|
||||||
|
public float scale
|
||||||
|
{
|
||||||
|
get { return m_Scale3D.x; }
|
||||||
|
set { m_Scale3D.Set(value, value, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Particle effect scale.
|
||||||
|
/// </summary>
|
||||||
|
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<Mask>();
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -221,41 +201,24 @@ namespace Coffee.UIExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnEnable()
|
protected override void OnEnable()
|
||||||
{
|
{
|
||||||
// Register.
|
UpdateVersionIfNeeded();
|
||||||
if (s_ActiveParticles.Count == 0)
|
|
||||||
{
|
|
||||||
Canvas.willRenderCanvases += UpdateMeshes;
|
|
||||||
s_Mpb = new MaterialPropertyBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
s_ActiveParticles.Add(this);
|
_tracker.Add(this, rectTransform, DrivenTransformProperties.Scale);
|
||||||
|
|
||||||
// Reset the parent-child relation.
|
|
||||||
GetComponentsInChildren<UIParticle>(false, s_TempRelatables);
|
|
||||||
for (int i = s_TempRelatables.Count - 1; 0 <= i; i--)
|
|
||||||
{
|
|
||||||
s_TempRelatables[i].OnTransformParentChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
s_TempRelatables.Clear();
|
|
||||||
|
|
||||||
|
// Initialize.
|
||||||
_renderer = cachedParticleSystem ? cachedParticleSystem.GetComponent<ParticleSystemRenderer>() : null;
|
_renderer = cachedParticleSystem ? cachedParticleSystem.GetComponent<ParticleSystemRenderer>() : null;
|
||||||
if (_renderer && Application.isPlaying)
|
if (_renderer != null)
|
||||||
{
|
|
||||||
_renderer.enabled = false;
|
_renderer.enabled = false;
|
||||||
}
|
|
||||||
|
CheckMaterials();
|
||||||
|
|
||||||
// Create objects.
|
// Create objects.
|
||||||
_mesh = new Mesh();
|
_bakedMesh = new Mesh();
|
||||||
_mesh.MarkDynamic();
|
_bakedMesh.MarkDynamic();
|
||||||
CheckTrail();
|
|
||||||
|
|
||||||
if (cachedParticleSystem)
|
MeshHelper.Register();
|
||||||
{
|
BakingCamera.Register();
|
||||||
_oldPos = cachedParticleSystem.main.scalingMode == ParticleSystemScalingMode.Local
|
UIParticleUpdater.Register(this);
|
||||||
? rectTransform.localPosition
|
|
||||||
: rectTransform.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnEnable();
|
base.OnEnable();
|
||||||
}
|
}
|
||||||
|
@ -265,46 +228,28 @@ namespace Coffee.UIExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnDisable()
|
protected override void OnDisable()
|
||||||
{
|
{
|
||||||
// Unregister.
|
_tracker.Clear();
|
||||||
s_ActiveParticles.Remove(this);
|
|
||||||
if (s_ActiveParticles.Count == 0)
|
// 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();
|
base.OnDisable();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
/// <summary>
|
|
||||||
/// Reset to default values.
|
|
||||||
/// </summary>
|
|
||||||
protected override void Reset()
|
|
||||||
{
|
|
||||||
// Disable ParticleSystemRenderer on reset.
|
|
||||||
if (cachedParticleSystem)
|
|
||||||
{
|
|
||||||
cachedParticleSystem.GetComponent<ParticleSystemRenderer>().enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Reset();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
|
/// Call to update the geometry of the Graphic onto the CanvasRenderer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -317,20 +262,6 @@ namespace Coffee.UIExtensions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void OnTransformParentChanged()
|
protected override void OnTransformParentChanged()
|
||||||
{
|
{
|
||||||
UIParticle newParent = null;
|
|
||||||
if (isActiveAndEnabled && !m_IgnoreParent)
|
|
||||||
{
|
|
||||||
var parentTransform = transform.parent;
|
|
||||||
while (parentTransform && (!newParent || !newParent.enabled))
|
|
||||||
{
|
|
||||||
newParent = parentTransform.GetComponent<UIParticle>();
|
|
||||||
parentTransform = parentTransform.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetParent(newParent);
|
|
||||||
|
|
||||||
base.OnTransformParentChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -340,327 +271,75 @@ namespace Coffee.UIExtensions
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
/// <summary>
|
|
||||||
/// This function is called when the script is loaded or a value is changed in the inspector(Called in the editor only).
|
|
||||||
/// </summary>
|
|
||||||
protected override void OnValidate()
|
|
||||||
{
|
|
||||||
OnTransformParentChanged();
|
|
||||||
base.OnValidate();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
//################################
|
//################################
|
||||||
// Private Members.
|
// Private Members.
|
||||||
//################################
|
//################################
|
||||||
Mesh _mesh;
|
private static bool HasMaterialChanged(Material material, ref int current)
|
||||||
ParticleSystemRenderer _renderer;
|
|
||||||
UIParticle _parent;
|
|
||||||
List<UIParticle> _children = new List<UIParticle>();
|
|
||||||
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];
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update meshes.
|
|
||||||
/// </summary>
|
|
||||||
static void UpdateMeshes()
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < s_ActiveParticles.Count; i++)
|
var old = current;
|
||||||
{
|
current = material ? material.GetInstanceID() : 0;
|
||||||
if (s_ActiveParticles[i])
|
return current != old;
|
||||||
{
|
|
||||||
s_ActiveParticles[i].UpdateMesh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal void UpdateTrailParticle()
|
||||||
/// Update meshe.
|
|
||||||
/// </summary>
|
|
||||||
void UpdateMesh()
|
|
||||||
{
|
{
|
||||||
try
|
// Should have a UIParticle for trail particle?
|
||||||
{
|
if (isActiveAndEnabled && isValid && !isTrailParticle && isSpritesModeAndHasTrail)
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks the trail.
|
|
||||||
/// </summary>
|
|
||||||
void CheckTrail()
|
|
||||||
{
|
|
||||||
if (isActiveAndEnabled && !m_IsTrail && m_ParticleSystem && m_ParticleSystem.trails.enabled)
|
|
||||||
{
|
{
|
||||||
if (!m_TrailParticle)
|
if (!m_TrailParticle)
|
||||||
{
|
{
|
||||||
|
// Create new UIParticle for trail particle
|
||||||
m_TrailParticle = new GameObject("[UIParticle] Trail").AddComponent<UIParticle>();
|
m_TrailParticle = new GameObject("[UIParticle] Trail").AddComponent<UIParticle>();
|
||||||
var trans = m_TrailParticle.transform;
|
var trans = m_TrailParticle.transform;
|
||||||
trans.SetParent(transform);
|
trans.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||||
trans.localPosition = Vector3.zero;
|
|
||||||
trans.localRotation = Quaternion.identity;
|
|
||||||
trans.localScale = Vector3.one;
|
trans.localScale = Vector3.one;
|
||||||
|
trans.SetParent(transform, false);
|
||||||
|
|
||||||
m_TrailParticle._renderer = GetComponent<ParticleSystemRenderer>();
|
m_TrailParticle._renderer = _renderer;
|
||||||
m_TrailParticle.m_ParticleSystem = GetComponent<ParticleSystem>();
|
m_TrailParticle.m_ParticleSystem = m_ParticleSystem;
|
||||||
m_TrailParticle.m_IsTrail = true;
|
m_TrailParticle.m_IsTrail = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_TrailParticle.enabled = true;
|
m_TrailParticle.gameObject.hideFlags = HideFlags.DontSave;
|
||||||
}
|
}
|
||||||
else if (m_TrailParticle)
|
else if (m_TrailParticle)
|
||||||
{
|
{
|
||||||
m_TrailParticle.enabled = false;
|
// Destroy a UIParticle for trail particle.
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the parent of the soft mask.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="newParent">The parent soft mask to use.</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copy the value from MaterialPropertyBlock to CanvasRenderer (#41)
|
|
||||||
/// </summary>
|
|
||||||
void UpdateAnimatableMaterialProperties()
|
|
||||||
{
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
return;
|
DestroyImmediate(m_TrailParticle.gameObject);
|
||||||
|
else
|
||||||
#endif
|
#endif
|
||||||
if (0 == m_AnimatableProperties.Length)
|
Destroy(m_TrailParticle.gameObject);
|
||||||
return;
|
|
||||||
|
|
||||||
_renderer.GetPropertyBlock(s_Mpb);
|
m_TrailParticle = null;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
internal void CheckMaterials()
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (!sprite) return null;
|
if (!_renderer) return;
|
||||||
|
|
||||||
if (Application.isPlaying) return sprite.texture;
|
var matChanged = HasMaterialChanged(_renderer.sharedMaterial, ref _cachedSharedMaterialId);
|
||||||
var ret = miGetActiveAtlasTexture.Invoke(null, new[] {sprite}) as Texture2D;
|
var matChanged2 = HasMaterialChanged(_renderer.trailMaterial, ref _cachedTrailMaterialId);
|
||||||
return ret ? ret : sprite.texture;
|
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<UIParticle>();
|
||||||
|
if (m_IgnoreParent || !parent)
|
||||||
|
scale3D = m_Scale * transform.localScale;
|
||||||
|
else
|
||||||
|
scale3D = transform.localScale;
|
||||||
|
m_Scale = 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
using UnityEngine;
|
|
||||||
#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
|
|
||||||
using PrefabStageUtility = UnityEditor.Experimental.SceneManagement.PrefabStageUtility;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Coffee.UIExtensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
[ExecuteInEditMode]
|
|
||||||
[AddComponentMenu("")]
|
|
||||||
public class UIParticleOverlayCamera : MonoBehaviour
|
|
||||||
{
|
|
||||||
//################################
|
|
||||||
// Public/Protected Members.
|
|
||||||
//################################
|
|
||||||
/// <summary>
|
|
||||||
/// Get instance object.
|
|
||||||
/// If instance does not exist, Find instance in scene, or create new one.
|
|
||||||
/// </summary>
|
|
||||||
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<UIParticleOverlayCamera>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return s_InstanceForPrefabMode;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Find instance in scene, or create new one.
|
|
||||||
if (object.ReferenceEquals(s_Instance, null))
|
|
||||||
{
|
|
||||||
s_Instance = FindObjectOfType<UIParticleOverlayCamera>() ??
|
|
||||||
new GameObject(typeof(UIParticleOverlayCamera).Name, typeof(UIParticleOverlayCamera)).GetComponent<UIParticleOverlayCamera>();
|
|
||||||
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<Camera>()) ? m_Camera : (m_Camera = gameObject.AddComponent<Camera>()); }
|
|
||||||
}
|
|
||||||
|
|
||||||
Camera m_Camera;
|
|
||||||
static UIParticleOverlayCamera s_Instance;
|
|
||||||
#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
|
|
||||||
static UIParticleOverlayCamera s_InstanceForPrefabMode;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Awake is called when the script instance is being loaded.
|
|
||||||
/// </summary>
|
|
||||||
void Awake()
|
|
||||||
{
|
|
||||||
#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR
|
|
||||||
// OverlayCamera for editor.
|
|
||||||
if (hideFlags == HideFlags.HideAndDontSave || s_InstanceForPrefabMode == this)
|
|
||||||
{
|
|
||||||
s_InstanceForPrefabMode = GetComponent<UIParticleOverlayCamera>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Hold the instance.
|
|
||||||
if (s_Instance == null)
|
|
||||||
{
|
|
||||||
s_Instance = GetComponent<UIParticleOverlayCamera>();
|
|
||||||
}
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is called when the MonoBehaviour will be destroyed.
|
|
||||||
/// </summary>
|
|
||||||
void OnEnable()
|
|
||||||
{
|
|
||||||
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
|
||||||
gameObject.tag = "EditorOnly";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This function is called when the MonoBehaviour will be destroyed.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<UIParticle> s_ActiveParticles = new List<UIParticle>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy the value from MaterialPropertyBlock to CanvasRenderer
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a0e708dc2e3034ba9a5c51db4252c7e0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: -100
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "com.coffee.ui-particle",
|
"name": "com.coffee.ui-particle",
|
||||||
"displayName": "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.",
|
"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",
|
"unity": "2018.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
Loading…
Reference in New Issue