SkeletonRenderer.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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. #if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
  30. #define NEW_PREFAB_SYSTEM
  31. #endif
  32. #if UNITY_2018_1_OR_NEWER
  33. #define PER_MATERIAL_PROPERTY_BLOCKS
  34. #endif
  35. #if UNITY_2017_1_OR_NEWER
  36. #define BUILT_IN_SPRITE_MASK_COMPONENT
  37. #endif
  38. #if UNITY_2019_3_OR_NEWER
  39. #define CONFIGURABLE_ENTER_PLAY_MODE
  40. #endif
  41. #define SPINE_OPTIONAL_RENDEROVERRIDE
  42. #define SPINE_OPTIONAL_MATERIALOVERRIDE
  43. using System.Collections.Generic;
  44. using UnityEngine;
  45. namespace Spine.Unity {
  46. /// <summary>Base class of animated Spine skeleton components. This component manages and renders a skeleton.</summary>
  47. #if NEW_PREFAB_SYSTEM
  48. [ExecuteAlways]
  49. #else
  50. [ExecuteInEditMode]
  51. #endif
  52. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
  53. [HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderer-Component")]
  54. public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
  55. public SkeletonDataAsset skeletonDataAsset;
  56. #region Initialization settings
  57. /// <summary>Skin name to use when the Skeleton is initialized.</summary>
  58. [SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
  59. /// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
  60. /// Otherwise any changes will be overwritten by the next inspector update.</summary>
  61. #if UNITY_EDITOR
  62. public bool EditorSkipSkinSync {
  63. get { return editorSkipSkinSync; }
  64. set { editorSkipSkinSync = value; }
  65. }
  66. protected bool editorSkipSkinSync = false;
  67. #endif
  68. /// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
  69. public bool initialFlipX, initialFlipY;
  70. #endregion
  71. #region Advanced Render Settings
  72. /// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
  73. public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
  74. protected UpdateMode updateMode = UpdateMode.FullUpdate;
  75. /// <summary>Update mode used when the MeshRenderer becomes invisible
  76. /// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
  77. /// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
  78. public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
  79. // Submesh Separation
  80. /// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
  81. [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")][SerializeField][SpineSlot] protected string[] separatorSlotNames = new string[0];
  82. /// <summary>Slots that determine where the render is split. This is used by components such as SkeletonRenderSeparator so that the skeleton can be rendered by two separate renderers on different GameObjects.</summary>
  83. [System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
  84. // Render Settings
  85. [Range(-0.1f, 0f)] public float zSpacing;
  86. /// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
  87. public bool useClipping = false;
  88. /// <summary>If true, triangles will not be updated. Enable this as an optimization if the skeleton does not make use of attachment swapping or hiding, or draw order keys. Otherwise, setting this to false may cause errors in rendering.</summary>
  89. public bool immutableTriangles = false;
  90. /// <summary>Multiply vertex color RGB with vertex color alpha. Set this to true if the shader used for rendering is a premultiplied alpha shader. Setting this to false disables single-batch additive slots.</summary>
  91. public bool pmaVertexColors = true;
  92. /// <summary>Clears the state of the render and skeleton when this component or its GameObject is disabled. This prevents previous state from being retained when it is enabled again. When pooling your skeleton, setting this to true can be helpful.</summary>
  93. public bool clearStateOnDisable = false;
  94. /// <summary>If true, second colors on slots will be added to the output Mesh as UV2 and UV3. A special "tint black" shader that interprets UV2 and UV3 as black point colors is required to render this properly.</summary>
  95. public bool tintBlack = false;
  96. /// <summary>If true, the renderer assumes the skeleton only requires one Material and one submesh to render. This allows the MeshGenerator to skip checking for changes in Materials. Enable this as an optimization if the skeleton only uses one Material.</summary>
  97. /// <remarks>This disables SkeletonRenderSeparator functionality.</remarks>
  98. public bool singleSubmesh = false;
  99. #if PER_MATERIAL_PROPERTY_BLOCKS
  100. /// <summary> Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. "A B A").
  101. /// If true, GPU instancing is disabled at all materials and MaterialPropertyBlocks are assigned at each
  102. /// material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect
  103. /// draw order (e.g. "A1 B A2" changed to "A1A2 B").
  104. /// You can disable this parameter when everything is drawn correctly to save the additional performance cost.
  105. /// </summary>
  106. public bool fixDrawOrder = false;
  107. #endif
  108. /// <summary>If true, the mesh generator adds normals to the output mesh. For better performance and reduced memory requirements, use a shader that assumes the desired normal.</summary>
  109. [UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] public bool addNormals = false;
  110. /// <summary>If true, tangents are calculated every frame and added to the Mesh. Enable this when using a shader that uses lighting that requires tangents.</summary>
  111. public bool calculateTangents = false;
  112. #if BUILT_IN_SPRITE_MASK_COMPONENT
  113. /// <summary>This enum controls the mode under which the sprite will interact with the masking system.</summary>
  114. /// <remarks>Interaction modes with <see cref="UnityEngine.SpriteMask"/> components are identical to Unity's <see cref="UnityEngine.SpriteRenderer"/>,
  115. /// see https://docs.unity3d.com/ScriptReference/SpriteMaskInteraction.html. </remarks>
  116. public SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
  117. [System.Serializable]
  118. public class SpriteMaskInteractionMaterials {
  119. public bool AnyMaterialCreated {
  120. get {
  121. return materialsMaskDisabled.Length > 0 ||
  122. materialsInsideMask.Length > 0 ||
  123. materialsOutsideMask.Length > 0;
  124. }
  125. }
  126. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.None"/>.</summary>
  127. public Material[] materialsMaskDisabled = new Material[0];
  128. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  129. public Material[] materialsInsideMask = new Material[0];
  130. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  131. public Material[] materialsOutsideMask = new Material[0];
  132. }
  133. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes.</summary>
  134. public SpriteMaskInteractionMaterials maskMaterials = new SpriteMaskInteractionMaterials();
  135. /// <summary>Shader property ID used for the Stencil comparison function.</summary>
  136. public static readonly int STENCIL_COMP_PARAM_ID = Shader.PropertyToID("_StencilComp");
  137. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.None"/>.</summary>
  138. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_NONE = UnityEngine.Rendering.CompareFunction.Always;
  139. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  140. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE = UnityEngine.Rendering.CompareFunction.LessEqual;
  141. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  142. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE = UnityEngine.Rendering.CompareFunction.Greater;
  143. #if UNITY_EDITOR
  144. private static bool haveStencilParametersBeenFixed = false;
  145. #endif
  146. #endif // #if BUILT_IN_SPRITE_MASK_COMPONENT
  147. #endregion
  148. #region Overrides
  149. #if SPINE_OPTIONAL_RENDEROVERRIDE
  150. // These are API for anything that wants to take over rendering for a SkeletonRenderer.
  151. public bool disableRenderingOnOverride = true;
  152. public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
  153. event InstructionDelegate generateMeshOverride;
  154. /// <summary>Allows separate code to take over rendering for this SkeletonRenderer component. The subscriber is passed a SkeletonRendererInstruction argument to determine how to render a skeleton.</summary>
  155. public event InstructionDelegate GenerateMeshOverride {
  156. add {
  157. generateMeshOverride += value;
  158. if (disableRenderingOnOverride && generateMeshOverride != null) {
  159. Initialize(false);
  160. if (meshRenderer)
  161. meshRenderer.enabled = false;
  162. }
  163. }
  164. remove {
  165. generateMeshOverride -= value;
  166. if (disableRenderingOnOverride && generateMeshOverride == null) {
  167. Initialize(false);
  168. if (meshRenderer)
  169. meshRenderer.enabled = true;
  170. }
  171. }
  172. }
  173. /// <summary> Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.</summary>
  174. public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
  175. #endif
  176. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  177. [System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
  178. /// <summary>Use this Dictionary to override a Material with a different Material.</summary>
  179. public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
  180. #endif
  181. [System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
  182. /// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
  183. public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
  184. #endregion
  185. #region Mesh Generator
  186. [System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
  187. readonly MeshGenerator meshGenerator = new MeshGenerator();
  188. [System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
  189. #endregion
  190. #region Cached component references
  191. MeshRenderer meshRenderer;
  192. MeshFilter meshFilter;
  193. #endregion
  194. #region Skeleton
  195. [System.NonSerialized] public bool valid;
  196. [System.NonSerialized] public Skeleton skeleton;
  197. public Skeleton Skeleton {
  198. get {
  199. Initialize(false);
  200. return skeleton;
  201. }
  202. }
  203. #endregion
  204. public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
  205. /// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
  206. public event SkeletonRendererDelegate OnRebuild;
  207. /// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
  208. /// all materials have been updated.</summary>
  209. public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
  210. public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
  211. #region Runtime Instantiation
  212. public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
  213. return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset, quiet);
  214. }
  215. /// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
  216. /// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
  217. public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
  218. var c = gameObject.AddComponent<T>();
  219. if (skeletonDataAsset != null) {
  220. c.skeletonDataAsset = skeletonDataAsset;
  221. c.Initialize(false, quiet);
  222. }
  223. return c;
  224. }
  225. /// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
  226. public void SetMeshSettings (MeshGenerator.Settings settings) {
  227. this.calculateTangents = settings.calculateTangents;
  228. this.immutableTriangles = settings.immutableTriangles;
  229. this.pmaVertexColors = settings.pmaVertexColors;
  230. this.tintBlack = settings.tintBlack;
  231. this.useClipping = settings.useClipping;
  232. this.zSpacing = settings.zSpacing;
  233. this.meshGenerator.settings = settings;
  234. }
  235. #endregion
  236. public virtual void Awake () {
  237. Initialize(false);
  238. updateMode = updateWhenInvisible;
  239. }
  240. #if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE
  241. public virtual void Start () {
  242. Initialize(false);
  243. }
  244. #endif
  245. void OnDisable () {
  246. if (clearStateOnDisable && valid)
  247. ClearState();
  248. }
  249. void OnDestroy () {
  250. rendererBuffers.Dispose();
  251. valid = false;
  252. }
  253. /// <summary>
  254. /// Clears the previously generated mesh and resets the skeleton's pose.</summary>
  255. public virtual void ClearState () {
  256. var meshFilter = GetComponent<MeshFilter>();
  257. if (meshFilter != null) meshFilter.sharedMesh = null;
  258. currentInstructions.Clear();
  259. if (skeleton != null) skeleton.SetToSetupPose();
  260. }
  261. /// <summary>
  262. /// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
  263. /// </summary>
  264. public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
  265. meshGenerator.EnsureVertexCapacity(minimumVertexCount);
  266. }
  267. /// <summary>
  268. /// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
  269. /// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>
  270. public virtual void Initialize (bool overwrite, bool quiet = false) {
  271. if (valid && !overwrite)
  272. return;
  273. // Clear
  274. {
  275. // Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
  276. // otherwise constant reloading will be triggered at prefabs.
  277. currentInstructions.Clear();
  278. rendererBuffers.Clear();
  279. meshGenerator.Begin();
  280. skeleton = null;
  281. valid = false;
  282. }
  283. if (skeletonDataAsset == null)
  284. return;
  285. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
  286. if (skeletonData == null) return;
  287. valid = true;
  288. meshFilter = GetComponent<MeshFilter>();
  289. meshRenderer = GetComponent<MeshRenderer>();
  290. rendererBuffers.Initialize();
  291. skeleton = new Skeleton(skeletonData) {
  292. ScaleX = initialFlipX ? -1 : 1,
  293. ScaleY = initialFlipY ? -1 : 1
  294. };
  295. if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
  296. skeleton.SetSkin(initialSkinName);
  297. separatorSlots.Clear();
  298. for (int i = 0; i < separatorSlotNames.Length; i++)
  299. separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
  300. LateUpdate(); // Generate mesh for the first frame it exists.
  301. if (OnRebuild != null)
  302. OnRebuild(this);
  303. #if UNITY_EDITOR
  304. if (!Application.isPlaying) {
  305. string errorMessage = null;
  306. if (!quiet && MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage))
  307. Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage);
  308. }
  309. #endif
  310. }
  311. /// <summary>
  312. /// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
  313. public virtual void LateUpdate () {
  314. if (!valid) return;
  315. #if UNITY_EDITOR && NEW_PREFAB_SYSTEM
  316. // Don't store mesh or material at the prefab, otherwise it will permanently reload
  317. var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
  318. if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
  319. (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
  320. return;
  321. }
  322. #endif
  323. if (updateMode != UpdateMode.FullUpdate) return;
  324. #if SPINE_OPTIONAL_RENDEROVERRIDE
  325. bool doMeshOverride = generateMeshOverride != null;
  326. if ((!meshRenderer.enabled) && !doMeshOverride) return;
  327. #else
  328. const bool doMeshOverride = false;
  329. if (!meshRenderer.enabled) return;
  330. #endif
  331. var currentInstructions = this.currentInstructions;
  332. var workingSubmeshInstructions = currentInstructions.submeshInstructions;
  333. var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
  334. bool updateTriangles;
  335. if (this.singleSubmesh) {
  336. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  337. MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
  338. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  339. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  340. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  341. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  342. #endif
  343. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  344. meshGenerator.settings = new MeshGenerator.Settings {
  345. pmaVertexColors = this.pmaVertexColors,
  346. zSpacing = this.zSpacing,
  347. useClipping = this.useClipping,
  348. tintBlack = this.tintBlack,
  349. calculateTangents = this.calculateTangents,
  350. addNormals = this.addNormals
  351. };
  352. meshGenerator.Begin();
  353. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  354. if (currentInstructions.hasActiveClipping) {
  355. meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
  356. } else {
  357. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  358. }
  359. } else {
  360. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  361. MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
  362. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  363. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  364. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  365. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  366. #endif
  367. #if SPINE_OPTIONAL_RENDEROVERRIDE
  368. if (doMeshOverride) {
  369. this.generateMeshOverride(currentInstructions);
  370. if (disableRenderingOnOverride) return;
  371. }
  372. #endif
  373. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  374. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  375. meshGenerator.settings = new MeshGenerator.Settings {
  376. pmaVertexColors = this.pmaVertexColors,
  377. zSpacing = this.zSpacing,
  378. useClipping = this.useClipping,
  379. tintBlack = this.tintBlack,
  380. calculateTangents = this.calculateTangents,
  381. addNormals = this.addNormals
  382. };
  383. meshGenerator.Begin();
  384. if (currentInstructions.hasActiveClipping)
  385. meshGenerator.BuildMesh(currentInstructions, updateTriangles);
  386. else
  387. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  388. }
  389. if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
  390. // STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
  391. var currentMesh = currentSmartMesh.mesh;
  392. meshGenerator.FillVertexData(currentMesh);
  393. rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
  394. bool materialsChanged = rendererBuffers.MaterialsChangedInLastUpdate();
  395. if (updateTriangles) { // Check if the triangles should also be updated.
  396. meshGenerator.FillTriangles(currentMesh);
  397. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  398. } else if (materialsChanged) {
  399. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  400. }
  401. if (materialsChanged && (this.maskMaterials.AnyMaterialCreated)) {
  402. this.maskMaterials = new SpriteMaskInteractionMaterials();
  403. }
  404. meshGenerator.FillLateVertexData(currentMesh);
  405. // STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
  406. meshFilter.sharedMesh = currentMesh;
  407. currentSmartMesh.instructionUsed.Set(currentInstructions);
  408. #if BUILT_IN_SPRITE_MASK_COMPONENT
  409. if (meshRenderer != null) {
  410. AssignSpriteMaskMaterials();
  411. }
  412. #endif
  413. #if PER_MATERIAL_PROPERTY_BLOCKS
  414. if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
  415. SetMaterialSettingsToFixDrawOrder();
  416. }
  417. #endif
  418. if (OnMeshAndMaterialsUpdated != null)
  419. OnMeshAndMaterialsUpdated(this);
  420. }
  421. public void OnBecameVisible () {
  422. UpdateMode previousUpdateMode = updateMode;
  423. updateMode = UpdateMode.FullUpdate;
  424. if (previousUpdateMode != UpdateMode.FullUpdate)
  425. LateUpdate(); // OnBecameVisible is called after LateUpdate()
  426. }
  427. public void OnBecameInvisible () {
  428. updateMode = updateWhenInvisible;
  429. }
  430. public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
  431. if (string.IsNullOrEmpty(startsWith)) return;
  432. FindAndApplySeparatorSlots(
  433. (slotName) => slotName.StartsWith(startsWith),
  434. clearExistingSeparators,
  435. updateStringArray
  436. );
  437. }
  438. public void FindAndApplySeparatorSlots (System.Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false) {
  439. if (slotNamePredicate == null) return;
  440. if (!valid) return;
  441. if (clearExistingSeparators)
  442. separatorSlots.Clear();
  443. var slots = skeleton.slots;
  444. foreach (var slot in slots) {
  445. if (slotNamePredicate.Invoke(slot.data.name))
  446. separatorSlots.Add(slot);
  447. }
  448. if (updateStringArray) {
  449. var detectedSeparatorNames = new List<string>();
  450. foreach (var slot in skeleton.slots) {
  451. string slotName = slot.data.name;
  452. if (slotNamePredicate.Invoke(slotName))
  453. detectedSeparatorNames.Add(slotName);
  454. }
  455. if (!clearExistingSeparators) {
  456. string[] originalNames = this.separatorSlotNames;
  457. foreach (string originalName in originalNames)
  458. detectedSeparatorNames.Add(originalName);
  459. }
  460. this.separatorSlotNames = detectedSeparatorNames.ToArray();
  461. }
  462. }
  463. public void ReapplySeparatorSlotNames () {
  464. if (!valid)
  465. return;
  466. separatorSlots.Clear();
  467. for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
  468. var slot = skeleton.FindSlot(separatorSlotNames[i]);
  469. if (slot != null) {
  470. separatorSlots.Add(slot);
  471. }
  472. #if UNITY_EDITOR
  473. else if (!string.IsNullOrEmpty(separatorSlotNames[i]))
  474. {
  475. Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
  476. }
  477. #endif
  478. }
  479. }
  480. #if BUILT_IN_SPRITE_MASK_COMPONENT
  481. private void AssignSpriteMaskMaterials()
  482. {
  483. #if UNITY_EDITOR
  484. if (!Application.isPlaying && !UnityEditor.EditorApplication.isUpdating) {
  485. EditorFixStencilCompParameters();
  486. }
  487. #endif
  488. if (Application.isPlaying) {
  489. if (maskInteraction != SpriteMaskInteraction.None && maskMaterials.materialsMaskDisabled.Length == 0)
  490. maskMaterials.materialsMaskDisabled = meshRenderer.sharedMaterials;
  491. }
  492. if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
  493. maskInteraction == SpriteMaskInteraction.None) {
  494. this.meshRenderer.materials = maskMaterials.materialsMaskDisabled;
  495. }
  496. else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
  497. if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null) {
  498. if (!InitSpriteMaskMaterialsInsideMask())
  499. return;
  500. }
  501. this.meshRenderer.materials = maskMaterials.materialsInsideMask;
  502. }
  503. else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
  504. if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null) {
  505. if (!InitSpriteMaskMaterialsOutsideMask())
  506. return;
  507. }
  508. this.meshRenderer.materials = maskMaterials.materialsOutsideMask;
  509. }
  510. }
  511. private bool InitSpriteMaskMaterialsInsideMask()
  512. {
  513. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE, ref maskMaterials.materialsInsideMask);
  514. }
  515. private bool InitSpriteMaskMaterialsOutsideMask()
  516. {
  517. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE, ref maskMaterials.materialsOutsideMask);
  518. }
  519. private bool InitSpriteMaskMaterialsForMaskType(UnityEngine.Rendering.CompareFunction maskFunction, ref Material[] materialsToFill)
  520. {
  521. #if UNITY_EDITOR
  522. if (!Application.isPlaying) {
  523. return false;
  524. }
  525. #endif
  526. var originalMaterials = maskMaterials.materialsMaskDisabled;
  527. materialsToFill = new Material[originalMaterials.Length];
  528. for (int i = 0; i < originalMaterials.Length; i++) {
  529. Material newMaterial = new Material(originalMaterials[i]);
  530. newMaterial.SetFloat(STENCIL_COMP_PARAM_ID, (int)maskFunction);
  531. materialsToFill[i] = newMaterial;
  532. }
  533. return true;
  534. }
  535. #if UNITY_EDITOR
  536. private void EditorFixStencilCompParameters() {
  537. if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
  538. haveStencilParametersBeenFixed = true;
  539. FixAllProjectMaterialsStencilCompParameters();
  540. }
  541. }
  542. private void FixAllProjectMaterialsStencilCompParameters() {
  543. string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
  544. foreach (var guid in materialGUIDS) {
  545. string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
  546. if (!string.IsNullOrEmpty(path)) {
  547. var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
  548. if (mat.HasProperty(STENCIL_COMP_PARAM_ID) && mat.GetFloat(STENCIL_COMP_PARAM_ID) == 0) {
  549. mat.SetFloat(STENCIL_COMP_PARAM_ID, (int)STENCIL_COMP_MASKINTERACTION_NONE);
  550. }
  551. }
  552. }
  553. UnityEditor.AssetDatabase.Refresh();
  554. UnityEditor.AssetDatabase.SaveAssets();
  555. }
  556. private bool HasAnyStencilComp0Material() {
  557. if (meshRenderer == null)
  558. return false;
  559. foreach (var mat in meshRenderer.sharedMaterials) {
  560. if (mat != null && mat.HasProperty(STENCIL_COMP_PARAM_ID)) {
  561. float currentCompValue = mat.GetFloat(STENCIL_COMP_PARAM_ID);
  562. if (currentCompValue == 0)
  563. return true;
  564. }
  565. }
  566. return false;
  567. }
  568. #endif // UNITY_EDITOR
  569. #endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
  570. #if PER_MATERIAL_PROPERTY_BLOCKS
  571. private MaterialPropertyBlock reusedPropertyBlock;
  572. public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
  573. /// <summary>
  574. /// This method was introduced as a workaround for too aggressive submesh draw call batching,
  575. /// leading to incorrect draw order when 3+ materials are used at submeshes in alternating order.
  576. /// Otherwise, e.g. when using Lightweight Render Pipeline, deliberately separated draw calls
  577. /// "A1 B A2" are reordered to "A1A2 B", regardless of batching-related project settings.
  578. /// </summary>
  579. private void SetMaterialSettingsToFixDrawOrder() {
  580. if (reusedPropertyBlock == null) reusedPropertyBlock = new MaterialPropertyBlock();
  581. bool hasPerRendererBlock = meshRenderer.HasPropertyBlock();
  582. if (hasPerRendererBlock) {
  583. meshRenderer.GetPropertyBlock(reusedPropertyBlock);
  584. }
  585. for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) {
  586. if (!meshRenderer.sharedMaterials[i])
  587. continue;
  588. if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i);
  589. // Note: this parameter shall not exist at any shader, then Unity will create separate
  590. // material instances (not in terms of memory cost or leakage).
  591. reusedPropertyBlock.SetFloat(SUBMESH_DUMMY_PARAM_ID, i);
  592. meshRenderer.SetPropertyBlock(reusedPropertyBlock, i);
  593. meshRenderer.sharedMaterials[i].enableInstancing = false;
  594. }
  595. }
  596. #endif
  597. }
  598. }