SoftMaskable.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.EventSystems;
  4. using UnityEngine.UI;
  5. using SoftMasking.Extensions;
  6. namespace SoftMasking {
  7. [ExecuteInEditMode]
  8. [DisallowMultipleComponent]
  9. [AddComponentMenu("")]
  10. public class SoftMaskable : UIBehaviour, IMaterialModifier {
  11. ISoftMask _mask;
  12. Graphic _graphic;
  13. Material _replacement;
  14. bool _affectedByMask;
  15. bool _destroyed;
  16. public bool shaderIsNotSupported { get; private set; }
  17. public bool isMaskingEnabled {
  18. get {
  19. return mask != null
  20. && mask.isAlive
  21. && mask.isMaskingEnabled
  22. && _affectedByMask;
  23. }
  24. }
  25. public ISoftMask mask {
  26. get { return _mask; }
  27. private set {
  28. if (_mask != value) {
  29. if (_mask != null)
  30. replacement = null;
  31. _mask = (value != null && value.isAlive) ? value : null;
  32. Invalidate();
  33. }
  34. }
  35. }
  36. public Material GetModifiedMaterial(Material baseMaterial) {
  37. if (isMaskingEnabled) {
  38. // First, get a new material then release the old one. It allows us to reuse
  39. // the old material if it's still actual.
  40. var newMat = mask.GetReplacement(baseMaterial);
  41. replacement = newMat;
  42. if (replacement) {
  43. shaderIsNotSupported = false;
  44. return replacement;
  45. }
  46. // Warn only if material has non-default UI shader. Otherwise, it seems that
  47. // replacement is null because SoftMask.defaultShader isn't set. If so, it's
  48. // SoftMask's business.
  49. if (!baseMaterial.HasDefaultUIShader())
  50. SetShaderNotSupported(baseMaterial);
  51. } else {
  52. shaderIsNotSupported = false;
  53. replacement = null;
  54. }
  55. return baseMaterial;
  56. }
  57. // Called when replacement material might be changed, so the material should be refreshed.
  58. public void Invalidate() {
  59. if (graphic)
  60. graphic.SetMaterialDirty();
  61. }
  62. // Called when an active mask might be changed, so the mask should be searched again.
  63. public void MaskMightChanged() {
  64. if (FindMaskOrDie())
  65. Invalidate();
  66. }
  67. protected override void Awake() {
  68. base.Awake();
  69. hideFlags = HideFlags.HideInInspector;
  70. }
  71. protected override void OnEnable() {
  72. base.OnEnable();
  73. if (FindMaskOrDie())
  74. RequestChildTransformUpdate();
  75. }
  76. protected override void OnDisable() {
  77. base.OnDisable();
  78. mask = null; // To invalidate the Graphic and free the material
  79. }
  80. protected override void OnDestroy() {
  81. base.OnDestroy();
  82. _destroyed = true;
  83. }
  84. protected override void OnTransformParentChanged() {
  85. base.OnTransformParentChanged();
  86. FindMaskOrDie();
  87. }
  88. protected override void OnCanvasHierarchyChanged() {
  89. base.OnCanvasHierarchyChanged();
  90. // A change of override sorting might change the mask instance that's masking us
  91. FindMaskOrDie();
  92. }
  93. void OnTransformChildrenChanged() {
  94. RequestChildTransformUpdate();
  95. }
  96. void RequestChildTransformUpdate() {
  97. if (mask != null)
  98. mask.UpdateTransformChildren(transform);
  99. }
  100. Graphic graphic { get { return _graphic ? _graphic : (_graphic = GetComponent<Graphic>()); } }
  101. Material replacement {
  102. get { return _replacement; }
  103. set {
  104. if (_replacement != value) {
  105. if (_replacement != null && mask != null)
  106. mask.ReleaseReplacement(_replacement);
  107. _replacement = value;
  108. }
  109. }
  110. }
  111. bool FindMaskOrDie() {
  112. if (_destroyed)
  113. return false;
  114. mask = NearestMask(transform, out _affectedByMask)
  115. ?? NearestMask(transform, out _affectedByMask, enabledOnly: false);
  116. if (mask == null) {
  117. _destroyed = true;
  118. DestroyImmediate(this);
  119. return false;
  120. }
  121. return true;
  122. }
  123. static ISoftMask NearestMask(Transform transform, out bool processedByThisMask, bool enabledOnly = true) {
  124. processedByThisMask = true;
  125. var current = transform;
  126. while (true) {
  127. if (!current)
  128. return null;
  129. if (current != transform) { // Masks do not mask themselves
  130. var mask = GetISoftMask(current, shouldBeEnabled: enabledOnly);
  131. if (mask != null)
  132. return mask;
  133. }
  134. if (IsOverridingSortingCanvas(current))
  135. processedByThisMask = false;
  136. current = current.parent;
  137. }
  138. }
  139. static List<ISoftMask> s_softMasks = new List<ISoftMask>();
  140. static List<Canvas> s_canvases = new List<Canvas>();
  141. static ISoftMask GetISoftMask(Transform current, bool shouldBeEnabled = true) {
  142. var mask = GetComponent(current, s_softMasks);
  143. if (mask != null && mask.isAlive && (!shouldBeEnabled || mask.isMaskingEnabled))
  144. return mask;
  145. return null;
  146. }
  147. static bool IsOverridingSortingCanvas(Transform transform) {
  148. var canvas = GetComponent(transform, s_canvases);
  149. if (canvas && canvas.overrideSorting)
  150. return true;
  151. return false;
  152. }
  153. static T GetComponent<T>(Component component, List<T> cachedList) where T : class {
  154. component.GetComponents(cachedList);
  155. using (new ClearListAtExit<T>(cachedList))
  156. return cachedList.Count > 0 ? cachedList[0] : null;
  157. }
  158. void SetShaderNotSupported(Material material) {
  159. if (!shaderIsNotSupported) {
  160. Debug.LogWarningFormat(
  161. gameObject,
  162. "SoftMask will not work on {0} because material {1} doesn't support masking. " +
  163. "Add masking support to your material or set Graphic's material to None to use " +
  164. "a default one.",
  165. graphic,
  166. material);
  167. shaderIsNotSupported = true;
  168. }
  169. }
  170. }
  171. }