refactor: refactor

vr
mob-sakai 2020-06-04 11:51:57 +09:00
parent 9961c90914
commit a5abcdaa20
7 changed files with 170 additions and 234 deletions

View File

@ -5,7 +5,6 @@ using UnityEditor;
using UnityEditor.SceneManagement; using UnityEditor.SceneManagement;
using UnityEngine; using UnityEngine;
namespace Coffee.UISoftMask namespace Coffee.UISoftMask
{ {
internal static class EditorUtils internal static class EditorUtils
@ -36,7 +35,7 @@ namespace Coffee.UISoftMask
var so = new SerializedObject(target); var so = new SerializedObject(target);
so.Update(); so.Update();
bool oldEnable = target.enabled; var oldEnable = target.enabled;
target.enabled = false; target.enabled = false;
// Find MonoScript of the specified component. // Find MonoScript of the specified component.

View File

@ -14,9 +14,9 @@ namespace Coffee.UISoftMask
[CanEditMultipleObjects] [CanEditMultipleObjects]
public class SoftMaskEditor : Editor public class SoftMaskEditor : Editor
{ {
const string k_PrefsPreview = "SoftMaskEditor_Preview"; private const string k_PrefsPreview = "SoftMaskEditor_Preview";
static readonly List<Graphic> s_Graphics = new List<Graphic>(); private static readonly List<Graphic> s_Graphics = new List<Graphic>();
static bool s_Preview; private static bool s_Preview;
private void OnEnable() private void OnEnable()
{ {
@ -35,9 +35,7 @@ namespace Coffee.UISoftMask
if (0 < fixTargets.Count) if (0 < fixTargets.Count)
{ {
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox("There are child Graphics that does not have a SoftMaskable component.\nAdd SoftMaskable component to them.", MessageType.Warning);
"There are child Graphics that does not have a SoftMaskable component.\nAdd SoftMaskable component to them.",
MessageType.Warning);
GUILayout.BeginVertical(); GUILayout.BeginVertical();
if (GUILayout.Button("Fix")) if (GUILayout.Button("Fix"))
{ {
@ -69,8 +67,8 @@ namespace Coffee.UISoftMask
if (s_Preview) if (s_Preview)
{ {
var tex = current.softMaskBuffer; var tex = current.softMaskBuffer;
var width = tex.width * 64 / tex.height; var width = tex.width * 128 / tex.height;
EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(width, 64), tex, null, ScaleMode.ScaleToFit); EditorGUI.DrawPreviewTexture(GUILayoutUtility.GetRect(width, 128), tex, null, ScaleMode.ScaleToFit);
Repaint(); Repaint();
} }
@ -81,25 +79,25 @@ namespace Coffee.UISoftMask
//%%%% Context menu for editor %%%% //%%%% Context menu for editor %%%%
[MenuItem("CONTEXT/Mask/Convert To SoftMask", true)] [MenuItem("CONTEXT/Mask/Convert To SoftMask", true)]
static bool _ConvertToSoftMask(MenuCommand command) private static bool _ConvertToSoftMask(MenuCommand command)
{ {
return EditorUtils.CanConvertTo<SoftMask>(command.context); return EditorUtils.CanConvertTo<SoftMask>(command.context);
} }
[MenuItem("CONTEXT/Mask/Convert To SoftMask", false)] [MenuItem("CONTEXT/Mask/Convert To SoftMask", false)]
static void ConvertToSoftMask(MenuCommand command) private static void ConvertToSoftMask(MenuCommand command)
{ {
EditorUtils.ConvertTo<SoftMask>(command.context); EditorUtils.ConvertTo<SoftMask>(command.context);
} }
[MenuItem("CONTEXT/Mask/Convert To Mask", true)] [MenuItem("CONTEXT/Mask/Convert To Mask", true)]
static bool _ConvertToMask(MenuCommand command) private static bool _ConvertToMask(MenuCommand command)
{ {
return EditorUtils.CanConvertTo<Mask>(command.context); return EditorUtils.CanConvertTo<Mask>(command.context);
} }
[MenuItem("CONTEXT/Mask/Convert To Mask", false)] [MenuItem("CONTEXT/Mask/Convert To Mask", false)]
static void ConvertToMask(MenuCommand command) private static void ConvertToMask(MenuCommand command)
{ {
EditorUtils.ConvertTo<Mask>(command.context); EditorUtils.ConvertTo<Mask>(command.context);
} }

View File

@ -3,14 +3,17 @@ using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEditor; using UnityEditor;
using System.Linq; using System.Linq;
using System;
using System.Reflection;
using Object = UnityEngine.Object;
using MaskIntr = UnityEngine.SpriteMaskInteraction; using MaskIntr = UnityEngine.SpriteMaskInteraction;
using System.IO;
namespace Coffee.UISoftMask namespace Coffee.UISoftMask
{ {
internal enum MaskInteraction : int
{
VisibleInsideMask = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6),
VisibleOutsideMask = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6),
Custom = -1,
}
/// <summary> /// <summary>
/// SoftMaskable editor. /// SoftMaskable editor.
/// </summary> /// </summary>
@ -18,18 +21,16 @@ namespace Coffee.UISoftMask
[CanEditMultipleObjects] [CanEditMultipleObjects]
public class SoftMaskableEditor : Editor public class SoftMaskableEditor : Editor
{ {
public enum MaskInteraction : int private static readonly List<Mask> s_TmpMasks = new List<Mask>();
{ private static GUIContent s_MaskWarning;
VisibleInsideMask = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6), private SerializedProperty _spMaskInteraction;
VisibleOutsideMask = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6), private bool _custom;
Custom = -1,
}
MaskInteraction maskInteraction private MaskInteraction maskInteraction
{ {
get get
{ {
int value = _spMaskInteraction.intValue; var value = _spMaskInteraction.intValue;
return _custom return _custom
? MaskInteraction.Custom ? MaskInteraction.Custom
: System.Enum.IsDefined(typeof(MaskInteraction), value) : System.Enum.IsDefined(typeof(MaskInteraction), value)
@ -46,31 +47,26 @@ namespace Coffee.UISoftMask
} }
} }
bool _custom = false;
static readonly List<Graphic> s_Graphics = new List<Graphic>();
SerializedProperty _spMaskInteraction;
List<Mask> tmpMasks = new List<Mask>();
static GUIContent s_MaskWarning;
private void OnEnable() private void OnEnable()
{ {
_spMaskInteraction = serializedObject.FindProperty("m_MaskInteraction"); _spMaskInteraction = serializedObject.FindProperty("m_MaskInteraction");
_custom = (maskInteraction == MaskInteraction.Custom); _custom = (maskInteraction == MaskInteraction.Custom);
s_MaskWarning = new GUIContent(EditorGUIUtility.FindTexture("console.warnicon.sml"),
"This is not a SoftMask component.");
}
if (s_MaskWarning == null)
{
s_MaskWarning = new GUIContent(EditorGUIUtility.FindTexture("console.warnicon.sml"), "This is not a SoftMask component.");
}
}
private void DrawMaskInteractions() private void DrawMaskInteractions()
{ {
var softMaskable = target as SoftMaskable; var softMaskable = target as SoftMaskable;
if (softMaskable == null) return; if (softMaskable == null) return;
softMaskable.GetComponentsInParent<Mask>(true, tmpMasks); softMaskable.GetComponentsInParent<Mask>(true, s_TmpMasks);
tmpMasks.RemoveAll(x => !x.enabled); s_TmpMasks.RemoveAll(x => !x.enabled);
tmpMasks.Reverse(); s_TmpMasks.Reverse();
maskInteraction = (MaskInteraction) EditorGUILayout.EnumPopup("Mask Interaction", maskInteraction); maskInteraction = (MaskInteraction) EditorGUILayout.EnumPopup("Mask Interaction", maskInteraction);
if (!_custom) return; if (!_custom) return;
@ -80,10 +76,10 @@ namespace Coffee.UISoftMask
using (var ccs = new EditorGUI.ChangeCheckScope()) using (var ccs = new EditorGUI.ChangeCheckScope())
{ {
int intr0 = DrawMaskInteraction(0); var intr0 = DrawMaskInteraction(0);
int intr1 = DrawMaskInteraction(1); var intr1 = DrawMaskInteraction(1);
int intr2 = DrawMaskInteraction(2); var intr2 = DrawMaskInteraction(2);
int intr3 = DrawMaskInteraction(3); var intr3 = DrawMaskInteraction(3);
if (ccs.changed) if (ccs.changed)
{ {
@ -94,24 +90,24 @@ namespace Coffee.UISoftMask
EditorGUIUtility.labelWidth = l; EditorGUIUtility.labelWidth = l;
} }
private int DrawMaskInteraction(int layer) private int DrawMaskInteraction(int layer)
{ {
Mask mask = layer < tmpMasks.Count ? tmpMasks[layer] : null; var mask = layer < s_TmpMasks.Count ? s_TmpMasks[layer] : null;
MaskIntr intr = (MaskIntr) ((_spMaskInteraction.intValue >> layer * 2) & 0x3); var intr = (MaskIntr) ((_spMaskInteraction.intValue >> layer * 2) & 0x3);
if (!mask) if (!mask)
{ {
return (int) intr; return (int) intr;
} }
using (new EditorGUILayout.HorizontalScope()) GUILayout.BeginHorizontal();
{
EditorGUILayout.LabelField(mask is SoftMask ? GUIContent.none : s_MaskWarning, GUILayout.Width(16)); EditorGUILayout.LabelField(mask is SoftMask ? GUIContent.none : s_MaskWarning, GUILayout.Width(16));
GUILayout.Space(-5); GUILayout.Space(-5);
EditorGUILayout.ObjectField("Mask " + layer, mask, typeof(Mask), false); EditorGUILayout.ObjectField("Mask " + layer, mask, typeof(Mask), false);
GUILayout.Space(-15); GUILayout.Space(-15);
return (int) (MaskIntr) EditorGUILayout.EnumPopup(intr); intr = (MaskIntr) EditorGUILayout.EnumPopup(intr);
} GUILayout.EndHorizontal();
return (int) intr;
} }
public override void OnInspectorGUI() public override void OnInspectorGUI()
@ -124,61 +120,20 @@ namespace Coffee.UISoftMask
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
var current = target as SoftMaskable; var current = target as SoftMaskable;
if (current == null) return;
var mask = current.softMask;
if (mask) return;
current.GetComponentsInChildren<Graphic>(true, s_Graphics);
var fixTargets = s_Graphics.Where(x =>
x.gameObject != current.gameObject && !x.GetComponent<SoftMaskable>() &&
(!x.GetComponent<Mask>() || x.GetComponent<Mask>().showMaskGraphic)).ToList();
if (0 < fixTargets.Count)
{
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox("This is unnecessary SoftMaskable.\nCan't find any SoftMask components above.", MessageType.Warning);
"There are child Graphics that does not have a SoftMaskable component.\nAdd SoftMaskable component to them.",
MessageType.Warning);
GUILayout.BeginVertical();
if (GUILayout.Button("Fix"))
{
foreach (var p in fixTargets)
{
p.gameObject.AddComponent<SoftMaskable>();
}
}
if (GUILayout.Button("Ping"))
{
EditorGUIUtility.PingObject(fixTargets[0]);
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
if (!DetectMask(current.transform.parent))
{
GUILayout.BeginHorizontal();
EditorGUILayout.HelpBox("This is unnecessary SoftMaskable.\nCan't find any SoftMask components above.",
MessageType.Warning);
if (GUILayout.Button("Remove", GUILayout.Height(40))) if (GUILayout.Button("Remove", GUILayout.Height(40)))
{ {
DestroyImmediate(current); DestroyImmediate(current);
EditorUtils.MarkPrefabDirty(); EditorUtils.MarkPrefabDirty();
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
} }
static bool DetectMask(Transform transform)
{
while (transform)
{
if (transform.GetComponent<SoftMask>()) return true;
transform = transform.parent;
}
return false;
}
}
} }

View File

@ -17,9 +17,20 @@ namespace Coffee.UISoftMask
GraphicConnector.FindConnector(graphic).SetMaterialDirty(graphic); GraphicConnector.FindConnector(graphic).SetMaterialDirty(graphic);
} }
public static Shader FindEffectShader(this Graphic graphic) public static T GetComponentInParentEx<T>(this Component component, bool includeInactive = false) where T : MonoBehaviour
{ {
return GraphicConnector.FindConnector(graphic).FindEffectShader(graphic); if (!component) return null;
var trans = component.transform;
while (trans)
{
var c = trans.GetComponent<T>();
if (c && (includeInactive || c.isActiveAndEnabled)) return c;
trans = trans.parent;
}
return null;
} }
} }
@ -27,10 +38,7 @@ namespace Coffee.UISoftMask
public class GraphicConnector public class GraphicConnector
{ {
private static readonly List<GraphicConnector> s_Connectors = new List<GraphicConnector>(); private static readonly List<GraphicConnector> s_Connectors = new List<GraphicConnector>();
private static readonly Dictionary<Type, GraphicConnector> s_ConnectorMap = new Dictionary<Type, GraphicConnector>();
private static readonly Dictionary<Type, GraphicConnector> s_ConnectorMap =
new Dictionary<Type, GraphicConnector>();
private static readonly GraphicConnector s_EmptyConnector = new GraphicConnector(); private static readonly GraphicConnector s_EmptyConnector = new GraphicConnector();
#if UNITY_EDITOR #if UNITY_EDITOR
@ -75,15 +83,6 @@ namespace Coffee.UISoftMask
get { return -1; } get { return -1; }
} }
/// <summary>
/// Find effect shader.
/// </summary>
public virtual Shader FindEffectShader(Graphic graphic)
{
return Shader.Find("Hidden/UI/SoftMaskable");
}
/// <summary> /// <summary>
/// The connector is valid for the component. /// The connector is valid for the component.
/// </summary> /// </summary>

View File

@ -1,17 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using UnityEngine; using UnityEngine;
using UnityEngine.UI;
namespace Coffee.UISoftMask namespace Coffee.UISoftMask
{ {
internal class MaterialCache internal class MaterialEntry
{
public delegate void ModifyAction(Material material, Graphic graphic);
static Dictionary<Hash128, MaterialEntry> materialMap = new Dictionary<Hash128, MaterialEntry>();
private class MaterialEntry
{ {
public Material material; public Material material;
public int referenceCount; public int referenceCount;
@ -20,23 +13,32 @@ namespace Coffee.UISoftMask
{ {
if (material) if (material)
{ {
#if UNITY_EDITOR
if (!Application.isPlaying)
UnityEngine.Object.DestroyImmediate(material, false); UnityEngine.Object.DestroyImmediate(material, false);
else
#endif
UnityEngine.Object.Destroy(material);
} }
material = null; material = null;
} }
} }
internal static class MaterialCache
{
static readonly Dictionary<Hash128, MaterialEntry> s_MaterialMap = new Dictionary<Hash128, MaterialEntry>();
#if UNITY_EDITOR #if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod] [UnityEditor.InitializeOnLoadMethod]
private static void ClearCache() private static void ClearCache()
{ {
foreach (var entry in materialMap.Values) foreach (var entry in s_MaterialMap.Values)
{ {
entry.Release(); entry.Release();
} }
materialMap.Clear(); s_MaterialMap.Clear();
} }
#endif #endif
@ -45,7 +47,7 @@ namespace Coffee.UISoftMask
if (!hash.isValid) return null; if (!hash.isValid) return null;
MaterialEntry entry; MaterialEntry entry;
if (!materialMap.TryGetValue(hash, out entry)) if (!s_MaterialMap.TryGetValue(hash, out entry))
{ {
entry = new MaterialEntry() entry = new MaterialEntry()
{ {
@ -56,7 +58,7 @@ namespace Coffee.UISoftMask
}; };
onModify(entry.material); onModify(entry.material);
materialMap.Add(hash, entry); s_MaterialMap.Add(hash, entry);
} }
entry.referenceCount++; entry.referenceCount++;
@ -67,13 +69,13 @@ namespace Coffee.UISoftMask
public static void Unregister(Hash128 hash) public static void Unregister(Hash128 hash)
{ {
MaterialEntry entry; MaterialEntry entry;
if (!hash.isValid || !materialMap.TryGetValue(hash, out entry)) return; if (!hash.isValid || !s_MaterialMap.TryGetValue(hash, out entry)) return;
//Debug.LogFormat("Unregister: {0}, {1}", hash, entry.referenceCount -1); //Debug.LogFormat("Unregister: {0}, {1}", hash, entry.referenceCount -1);
if (--entry.referenceCount > 0) return; if (--entry.referenceCount > 0) return;
entry.Release(); entry.Release();
materialMap.Remove(hash); s_MaterialMap.Remove(hash);
//Debug.LogFormat("Unregister: Release Emtry: {0}, {1} (Total: {2})", hash, entry.referenceCount, materialMap.Count); //Debug.LogFormat("Unregister: Release Emtry: {0}, {1} (Total: {2})", hash, entry.referenceCount, materialMap.Count);
} }
} }

View File

@ -25,7 +25,7 @@ namespace Coffee.UISoftMask
x8 = 8, x8 = 8,
} }
static readonly List<SoftMask>[] s_TmpSoftMasks = new List<SoftMask>[] private static readonly List<SoftMask>[] s_TmpSoftMasks = new List<SoftMask>[]
{ {
new List<SoftMask>(), new List<SoftMask>(),
new List<SoftMask>(), new List<SoftMask>(),
@ -33,7 +33,7 @@ namespace Coffee.UISoftMask
new List<SoftMask>(), new List<SoftMask>(),
}; };
static readonly Color[] s_ClearColors = new Color[] private static readonly Color[] s_ClearColors = new Color[]
{ {
new Color(0, 0, 0, 0), new Color(0, 0, 0, 0),
new Color(1, 0, 0, 0), new Color(1, 0, 0, 0),
@ -41,50 +41,46 @@ namespace Coffee.UISoftMask
new Color(1, 1, 1, 0), new Color(1, 1, 1, 0),
}; };
static bool s_UVStartsAtTop; private static bool s_UVStartsAtTop;
private static Shader s_SoftMaskShader;
static Shader s_SoftMaskShader; private static Texture2D s_ReadTexture;
static Texture2D s_ReadTexture; private static readonly List<SoftMask> s_ActiveSoftMasks = new List<SoftMask>();
static readonly List<SoftMask> s_ActiveSoftMasks = new List<SoftMask>(); private static readonly List<SoftMask> s_TempRelatables = new List<SoftMask>();
static readonly List<SoftMask> s_TempRelatables = new List<SoftMask>(); private static readonly Dictionary<int, Matrix4x4> s_PreviousViewProjectionMatrices = new Dictionary<int, Matrix4x4>();
static readonly Dictionary<int, Matrix4x4> s_previousViewProjectionMatrices = new Dictionary<int, Matrix4x4>(); private static readonly Dictionary<int, Matrix4x4> s_NowViewProjectionMatrices = new Dictionary<int, Matrix4x4>();
static readonly Dictionary<int, Matrix4x4> s_nowViewProjectionMatrices = new Dictionary<int, Matrix4x4>(); private static int s_StencilCompId;
static int s_StencilCompId; private static int s_ColorMaskId;
static int s_ColorMaskId; private static int s_MainTexId;
static int s_MainTexId; private static int s_SoftnessId;
static int s_SoftnessId; private static int s_GameVPId;
static int s_GameVPId; private static int s_GameTVPId;
static int s_GameTVPId; private static int s_Alpha;
static int s_Alpha; private MaterialPropertyBlock _mpb;
MaterialPropertyBlock _mpb; private CommandBuffer _cb;
CommandBuffer _cb; private Material _material;
Material _material; private RenderTexture _softMaskBuffer;
RenderTexture _softMaskBuffer; private int _stencilDepth;
int _stencilDepth; private Mesh _mesh;
Mesh _mesh; private SoftMask _parent;
SoftMask _parent; private readonly List<SoftMask> _children = new List<SoftMask>();
readonly List<SoftMask> _children = new List<SoftMask>(); private bool _hasChanged = false;
bool _hasChanged = false; private bool _hasStencilStateChanged = false;
bool _hasStencilStateChanged = false;
[Tooltip("The desampling rate for soft mask buffer.")] [SerializeField] [SerializeField, Tooltip("The desampling rate for soft mask buffer.")]
DesamplingRate m_DesamplingRate = DesamplingRate.None; private DesamplingRate m_DesamplingRate = DesamplingRate.None;
[Tooltip( [SerializeField, Range(0.01f, 1), Tooltip("The value used by the soft mask to select the area of influence defined over the soft mask's graphic.")]
"The value used by the soft mask to select the area of influence defined over the soft mask's graphic.")] private float m_Softness = 1;
[SerializeField]
[Range(0.01f, 1)]
float m_Softness = 1;
[Tooltip("The transparency of the whole masked graphic.")] [SerializeField] [Range(0f, 1f)] [SerializeField, Range(0f, 1f), Tooltip("The transparency of the whole masked graphic.")]
float m_Alpha = 1; private float m_Alpha = 1;
[Tooltip("Should the soft mask ignore parent soft masks?")] [SerializeField] [SerializeField, Tooltip("Should the soft mask ignore parent soft masks?")]
bool m_IgnoreParent = false; private bool m_IgnoreParent = false;
[Tooltip("Is the soft mask a part of parent soft mask?")] [SerializeField] [SerializeField, Tooltip("Is the soft mask a part of parent soft mask?")]
bool m_PartOfParent = false; private bool m_PartOfParent = false;
/// <summary> /// <summary>
@ -336,7 +332,7 @@ namespace Coffee.UISoftMask
_mpb = new MaterialPropertyBlock(); _mpb = new MaterialPropertyBlock();
_cb = new CommandBuffer(); _cb = new CommandBuffer();
graphic.SetVerticesDirty(); graphic.SetVerticesDirtyEx();
base.OnEnable(); base.OnEnable();
_hasStencilStateChanged = false; _hasStencilStateChanged = false;
@ -411,7 +407,7 @@ namespace Coffee.UISoftMask
/// </summary> /// </summary>
protected override void OnValidate() protected override void OnValidate()
{ {
graphic.SetMaterialDirty(); graphic.SetMaterialDirtyEx();
OnTransformParentChanged(); OnTransformParentChanged();
base.OnValidate(); base.OnValidate();
_hasStencilStateChanged = false; _hasStencilStateChanged = false;
@ -441,8 +437,8 @@ namespace Coffee.UISoftMask
var nowVP = cam.projectionMatrix * cam.worldToCameraMatrix; var nowVP = cam.projectionMatrix * cam.worldToCameraMatrix;
var previousVP = default(Matrix4x4); var previousVP = default(Matrix4x4);
var id = cam.GetInstanceID(); var id = cam.GetInstanceID();
s_previousViewProjectionMatrices.TryGetValue(id, out previousVP); s_PreviousViewProjectionMatrices.TryGetValue(id, out previousVP);
s_nowViewProjectionMatrices[id] = nowVP; s_NowViewProjectionMatrices[id] = nowVP;
if (previousVP != nowVP) if (previousVP != nowVP)
{ {
@ -478,13 +474,13 @@ namespace Coffee.UISoftMask
MaskUtilities.NotifyStencilStateChanged(sm); MaskUtilities.NotifyStencilStateChanged(sm);
} }
s_previousViewProjectionMatrices.Clear(); s_PreviousViewProjectionMatrices.Clear();
foreach (var id in s_nowViewProjectionMatrices.Keys) foreach (var id in s_NowViewProjectionMatrices.Keys)
{ {
s_previousViewProjectionMatrices[id] = s_nowViewProjectionMatrices[id]; s_PreviousViewProjectionMatrices[id] = s_NowViewProjectionMatrices[id];
} }
s_nowViewProjectionMatrices.Clear(); s_NowViewProjectionMatrices.Clear();
} }
/// <summary> /// <summary>

View File

@ -15,40 +15,37 @@ namespace Coffee.UISoftMask
#else #else
[ExecuteInEditMode] [ExecuteInEditMode]
# endif # endif
[RequireComponent(typeof(Graphic))]
public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter public class SoftMaskable : MonoBehaviour, IMaterialModifier, ICanvasRaycastFilter
#if UNITY_EDITOR #if UNITY_EDITOR
, ISerializationCallbackReceiver , ISerializationCallbackReceiver
# endif # endif
{ {
const int kVisibleInside = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6); private const int kVisibleInside = (1 << 0) + (1 << 2) + (1 << 4) + (1 << 6);
const int kVisibleOutside = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6); private const int kVisibleOutside = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6);
static readonly Hash128 k_InvalidHash = new Hash128(); private static readonly Hash128 k_InvalidHash = new Hash128();
static int s_SoftMaskTexId; private static int s_SoftMaskTexId;
static int s_StencilCompId; private static int s_StencilCompId;
static int s_MaskInteractionId; private static int s_MaskInteractionId;
static List<SoftMaskable> s_ActiveSoftMaskables; private static List<SoftMaskable> s_ActiveSoftMaskables;
static int[] s_Interactions = new int[4]; private static int[] s_Interactions = new int[4];
[Tooltip("The graphic will be visible only in areas where no mask is present.")] [SerializeField, HideInInspector, System.Obsolete]
[System.Obsolete] private bool m_Inverse;
[HideInInspector]
[SerializeField]
bool m_Inverse = false;
[Tooltip("The interaction for each masks.")] [HideInInspector] [SerializeField] [SerializeField, Tooltip("The interaction for each masks."), HideInInspector]
int m_MaskInteraction = kVisibleInside; private int m_MaskInteraction = kVisibleInside;
[Tooltip("Use stencil to mask.")] [SerializeField] [SerializeField, Tooltip("Use stencil to mask.")]
bool m_UseStencil = false; private bool m_UseStencil;
[Tooltip("Use soft-masked raycast target.\n\nNote: This option is expensive.")] [SerializeField] [SerializeField, Tooltip("Use soft-masked raycast target.\n\nNote: This option is expensive.")]
bool m_RaycastFilter = false; private bool m_RaycastFilter;
Graphic _graphic = null; private Graphic _graphic;
SoftMask _softMask = null; private SoftMask _softMask;
Material _maskMaterial = null; private Hash128 _effectMaterialHash;
Hash128 _effectMaterialHash;
/// <summary> /// <summary>
/// The graphic will be visible only in areas where no mask is present. /// The graphic will be visible only in areas where no mask is present.
@ -82,6 +79,11 @@ namespace Coffee.UISoftMask
get { return _graphic ? _graphic : _graphic = GetComponent<Graphic>(); } get { return _graphic ? _graphic : _graphic = GetComponent<Graphic>(); }
} }
public SoftMask softMask
{
get { return _softMask ? _softMask : _softMask = this.GetComponentInParentEx<SoftMask>(); }
}
/// <summary> /// <summary>
/// Perform material modification in this function. /// Perform material modification in this function.
/// </summary> /// </summary>
@ -98,27 +100,13 @@ namespace Coffee.UISoftMask
// If this component is disabled, the material is returned as is. // If this component is disabled, the material is returned as is.
if (!isActiveAndEnabled) return baseMaterial; if (!isActiveAndEnabled) return baseMaterial;
// Find the nearest parent softmask.
var parentTransform = transform.parent;
while (parentTransform)
{
var sm = parentTransform.GetComponent<SoftMask>();
if (sm && sm.enabled)
{
_softMask = sm;
break;
}
parentTransform = parentTransform.parent;
}
// If the parents do not have a soft mask component, the material is returned as is. // If the parents do not have a soft mask component, the material is returned as is.
if (!_softMask) return baseMaterial; if (!softMask) return baseMaterial;
// Generate soft maskable material. // Generate soft maskable material.
_effectMaterialHash = new Hash128( _effectMaterialHash = new Hash128(
(uint) baseMaterial.GetInstanceID(), (uint) baseMaterial.GetInstanceID(),
(uint) _softMask.GetInstanceID(), (uint) softMask.GetInstanceID(),
(uint) m_MaskInteraction, (uint) m_MaskInteraction,
(uint) (m_UseStencil ? 1 : 0) (uint) (m_UseStencil ? 1 : 0)
); );
@ -130,7 +118,7 @@ namespace Coffee.UISoftMask
#if UNITY_EDITOR #if UNITY_EDITOR
mat.EnableKeyword("SOFTMASK_EDITOR"); mat.EnableKeyword("SOFTMASK_EDITOR");
#endif #endif
mat.SetTexture(s_SoftMaskTexId, _softMask.softMaskBuffer); mat.SetTexture(s_SoftMaskTexId, softMask.softMaskBuffer);
mat.SetInt(s_StencilCompId, mat.SetInt(s_StencilCompId,
m_UseStencil ? (int) CompareFunction.Equal : (int) CompareFunction.Always); m_UseStencil ? (int) CompareFunction.Equal : (int) CompareFunction.Always);
mat.SetVector(s_MaskInteractionId, new Vector4( mat.SetVector(s_MaskInteractionId, new Vector4(
@ -140,7 +128,6 @@ namespace Coffee.UISoftMask
((m_MaskInteraction >> 6) & 0x3) ((m_MaskInteraction >> 6) & 0x3)
)); ));
}); });
_maskMaterial = modifiedMaterial;
return modifiedMaterial; return modifiedMaterial;
} }
@ -153,21 +140,21 @@ namespace Coffee.UISoftMask
/// <param name="eventCamera">Raycast camera.</param> /// <param name="eventCamera">Raycast camera.</param>
bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 sp, Camera eventCamera) bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{ {
if (!isActiveAndEnabled || !_softMask) if (!isActiveAndEnabled || !softMask)
return true; return true;
if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera)) if (!RectTransformUtility.RectangleContainsScreenPoint(transform as RectTransform, sp, eventCamera))
return false; return false;
if (!m_RaycastFilter) if (!m_RaycastFilter)
return true; return true;
var sm = _softMask; var sm = softMask;
for (var i = 0; i < 4; i++) for (var i = 0; i < 4; i++)
{ {
s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0; s_Interactions[i] = sm ? ((m_MaskInteraction >> i * 2) & 0x3) : 0;
sm = sm ? sm.parent : null; sm = sm ? sm.parent : null;
} }
return _softMask.IsRaycastLocationValid(sp, eventCamera, graphic, s_Interactions); return softMask.IsRaycastLocationValid(sp, eventCamera, graphic, s_Interactions);
} }
/// <summary> /// <summary>
@ -243,7 +230,7 @@ namespace Coffee.UISoftMask
if (m_Inverse) if (m_Inverse)
{ {
m_Inverse = false; m_Inverse = false;
m_MaskInteraction = (2 << 0) + (2 << 2) + (2 << 4) + (2 << 6); m_MaskInteraction = kVisibleOutside;
} }
#pragma warning restore 0612 #pragma warning restore 0612