SpineMaskUtilities.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated January 1, 2020. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2020, Esoteric Software LLC
  6. *
  7. * Integration of the Spine Runtimes into software or otherwise creating
  8. * derivative works of the Spine Runtimes is permitted under the terms and
  9. * conditions of Section 2 of the Spine Editor License Agreement:
  10. * http://esotericsoftware.com/spine-editor-license
  11. *
  12. * Otherwise, it is permitted to integrate the Spine Runtimes into software
  13. * or otherwise create derivative works of the Spine Runtimes (collectively,
  14. * "Products"), provided that each user of the Products must obtain their own
  15. * Spine Editor license and redistribution of the Products in any form must
  16. * include this license and copyright notice.
  17. *
  18. * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
  19. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
  24. * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  27. * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *****************************************************************************/
  29. #pragma warning disable 0219
  30. #define SPINE_SKELETONMECANIM
  31. #if UNITY_2017_2_OR_NEWER
  32. #define NEWPLAYMODECALLBACKS
  33. #endif
  34. #if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
  35. #define NEW_PREFAB_SYSTEM
  36. #endif
  37. #if UNITY_2018 || UNITY_2019 || UNITY_2018_3_OR_NEWER
  38. #define NEWHIERARCHYWINDOWCALLBACKS
  39. #endif
  40. #if UNITY_2017_1_OR_NEWER
  41. #define BUILT_IN_SPRITE_MASK_COMPONENT
  42. #endif
  43. #if BUILT_IN_SPRITE_MASK_COMPONENT
  44. using UnityEngine;
  45. using UnityEditor;
  46. using System.Collections.Generic;
  47. using System.IO;
  48. using System.Text;
  49. using System.Linq;
  50. using System.Reflection;
  51. namespace Spine.Unity.Editor {
  52. public class SpineMaskUtilities {
  53. private const string MATERIAL_FILENAME_SUFFIX_INSIDE_MASK = "_InsideMask";
  54. private const string MATERIAL_FILENAME_SUFFIX_OUTSIDE_MASK = "_OutsideMask";
  55. public static void EditorAssignSpriteMaskMaterials(SkeletonRenderer skeleton) {
  56. var maskMaterials = skeleton.maskMaterials;
  57. var maskInteraction = skeleton.maskInteraction;
  58. var meshRenderer = skeleton.GetComponent<MeshRenderer>();
  59. if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
  60. maskInteraction == SpriteMaskInteraction.None) {
  61. meshRenderer.materials = maskMaterials.materialsMaskDisabled;
  62. }
  63. else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
  64. if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null)
  65. EditorInitSpriteMaskMaterialsInsideMask(skeleton);
  66. meshRenderer.materials = maskMaterials.materialsInsideMask;
  67. }
  68. else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
  69. if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null)
  70. EditorInitSpriteMaskMaterialsOutsideMask(skeleton);
  71. meshRenderer.materials = maskMaterials.materialsOutsideMask;
  72. }
  73. }
  74. public static bool AreMaskMaterialsMissing(SkeletonRenderer skeleton) {
  75. var maskMaterials = skeleton.maskMaterials;
  76. var maskInteraction = skeleton.maskInteraction;
  77. if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
  78. return (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null);
  79. }
  80. else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
  81. return (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null);
  82. }
  83. return false;
  84. }
  85. public static void EditorInitMaskMaterials(SkeletonRenderer skeleton, SkeletonRenderer.SpriteMaskInteractionMaterials maskMaterials, SpriteMaskInteraction maskType) {
  86. if (maskType == SpriteMaskInteraction.None) {
  87. EditorConfirmDisabledMaskMaterialsInit(skeleton);
  88. }
  89. else if (maskType == SpriteMaskInteraction.VisibleInsideMask) {
  90. EditorInitSpriteMaskMaterialsInsideMask(skeleton);
  91. }
  92. else if (maskType == SpriteMaskInteraction.VisibleOutsideMask) {
  93. EditorInitSpriteMaskMaterialsOutsideMask(skeleton);
  94. }
  95. }
  96. public static void EditorDeleteMaskMaterials(SkeletonRenderer.SpriteMaskInteractionMaterials maskMaterials, SpriteMaskInteraction maskType) {
  97. Material[] targetMaterials;
  98. if (maskType == SpriteMaskInteraction.VisibleInsideMask) {
  99. targetMaterials = maskMaterials.materialsInsideMask;
  100. }
  101. else if (maskType == SpriteMaskInteraction.VisibleOutsideMask) {
  102. targetMaterials = maskMaterials.materialsOutsideMask;
  103. }
  104. else {
  105. Debug.LogWarning("EditorDeleteMaskMaterials: Normal materials are kept as a reference and shall never be deleted.");
  106. return;
  107. }
  108. for (int i = 0; i < targetMaterials.Length; ++i) {
  109. var material = targetMaterials[i];
  110. if (material != null) {
  111. string materialPath = UnityEditor.AssetDatabase.GetAssetPath(material);
  112. UnityEditor.AssetDatabase.DeleteAsset(materialPath);
  113. Debug.Log(string.Concat("Deleted material '", materialPath, "'"));
  114. }
  115. }
  116. if (maskType == SpriteMaskInteraction.VisibleInsideMask) {
  117. maskMaterials.materialsInsideMask = new Material[0];
  118. }
  119. else if (maskType == SpriteMaskInteraction.VisibleOutsideMask) {
  120. maskMaterials.materialsOutsideMask = new Material[0];
  121. }
  122. }
  123. private static void EditorInitSpriteMaskMaterialsInsideMask(SkeletonRenderer skeleton) {
  124. var maskMaterials = skeleton.maskMaterials;
  125. EditorInitSpriteMaskMaterialsForMaskType(skeleton, SkeletonRenderer.STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE,
  126. ref maskMaterials.materialsInsideMask);
  127. }
  128. private static void EditorInitSpriteMaskMaterialsOutsideMask(SkeletonRenderer skeleton) {
  129. var maskMaterials = skeleton.maskMaterials;
  130. EditorInitSpriteMaskMaterialsForMaskType(skeleton, SkeletonRenderer.STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE,
  131. ref maskMaterials.materialsOutsideMask);
  132. }
  133. private static void EditorInitSpriteMaskMaterialsForMaskType(SkeletonRenderer skeleton, UnityEngine.Rendering.CompareFunction maskFunction,
  134. ref Material[] materialsToFill) {
  135. if (!EditorConfirmDisabledMaskMaterialsInit(skeleton))
  136. return;
  137. var maskMaterials = skeleton.maskMaterials;
  138. var originalMaterials = maskMaterials.materialsMaskDisabled;
  139. materialsToFill = new Material[originalMaterials.Length];
  140. for (int i = 0; i < originalMaterials.Length; i++) {
  141. Material newMaterial = null;
  142. if (!Application.isPlaying) {
  143. newMaterial = EditorCreateOrLoadMaskMaterialAsset(maskMaterials, maskFunction, originalMaterials[i]);
  144. }
  145. if (newMaterial == null) {
  146. newMaterial = new Material(originalMaterials[i]);
  147. newMaterial.SetFloat(SkeletonRenderer.STENCIL_COMP_PARAM_ID, (int)maskFunction);
  148. }
  149. materialsToFill[i] = newMaterial;
  150. }
  151. }
  152. private static bool EditorConfirmDisabledMaskMaterialsInit(SkeletonRenderer skeleton) {
  153. var maskMaterials = skeleton.maskMaterials;
  154. if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null) {
  155. return true;
  156. }
  157. var meshRenderer = skeleton.GetComponent<MeshRenderer>();
  158. Material[] currentMaterials = meshRenderer.sharedMaterials;
  159. if (currentMaterials.Length == 0 || currentMaterials[0] == null) {
  160. Debug.LogWarning("No materials found assigned at " + skeleton.name);
  161. return false;
  162. }
  163. // We have to be sure that there has not been a recompilation or similar events that led to
  164. // inside- or outside-mask materials being assigned to meshRenderer.sharedMaterials.
  165. string firstMaterialPath = UnityEditor.AssetDatabase.GetAssetPath(currentMaterials[0]);
  166. if (firstMaterialPath.Contains(MATERIAL_FILENAME_SUFFIX_INSIDE_MASK) ||
  167. firstMaterialPath.Contains(MATERIAL_FILENAME_SUFFIX_OUTSIDE_MASK)) {
  168. maskMaterials.materialsMaskDisabled = new Material[currentMaterials.Length];
  169. for (int i = 0; i < currentMaterials.Length; ++i) {
  170. string path = UnityEditor.AssetDatabase.GetAssetPath(currentMaterials[i]);
  171. string correctPath = null;
  172. if (path.Contains(MATERIAL_FILENAME_SUFFIX_INSIDE_MASK)) {
  173. correctPath = path.Replace(MATERIAL_FILENAME_SUFFIX_INSIDE_MASK, "");
  174. }
  175. else if (path.Contains(MATERIAL_FILENAME_SUFFIX_OUTSIDE_MASK)) {
  176. correctPath = path.Replace(MATERIAL_FILENAME_SUFFIX_OUTSIDE_MASK, "");
  177. }
  178. if (correctPath != null) {
  179. Material material = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(correctPath);
  180. if (material == null)
  181. Debug.LogWarning("No original ignore-mask material found for path " + correctPath);
  182. maskMaterials.materialsMaskDisabled[i] = material;
  183. }
  184. }
  185. }
  186. else {
  187. maskMaterials.materialsMaskDisabled = currentMaterials;
  188. }
  189. return true;
  190. }
  191. public static Material EditorCreateOrLoadMaskMaterialAsset(SkeletonRenderer.SpriteMaskInteractionMaterials maskMaterials,
  192. UnityEngine.Rendering.CompareFunction maskFunction, Material originalMaterial) {
  193. string originalMaterialPath = UnityEditor.AssetDatabase.GetAssetPath(originalMaterial);
  194. int posOfExtensionDot = originalMaterialPath.LastIndexOf('.');
  195. string materialPath = (maskFunction == SkeletonRenderer.STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE) ?
  196. originalMaterialPath.Insert(posOfExtensionDot, MATERIAL_FILENAME_SUFFIX_INSIDE_MASK) :
  197. originalMaterialPath.Insert(posOfExtensionDot, MATERIAL_FILENAME_SUFFIX_OUTSIDE_MASK);
  198. Material material = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(materialPath);
  199. if (material != null) {
  200. return material;
  201. }
  202. material = new Material(originalMaterial);
  203. material.SetFloat(SkeletonRenderer.STENCIL_COMP_PARAM_ID, (int)maskFunction);
  204. UnityEditor.AssetDatabase.CreateAsset(material, materialPath);
  205. Debug.Log(string.Concat("Created material '", materialPath, "' for mask interaction based on '", originalMaterialPath, "'."));
  206. UnityEditor.EditorUtility.SetDirty(material);
  207. UnityEditor.AssetDatabase.SaveAssets();
  208. return material;
  209. }
  210. }
  211. }
  212. #endif // BUILT_IN_SPRITE_MASK_COMPONENT