SkeletonRenderer.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  1. /******************************************************************************
  2. * Spine Runtimes License Agreement
  3. * Last updated July 28, 2023. Replaces all prior versions.
  4. *
  5. * Copyright (c) 2013-2023, 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 or
  13. * 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 THE
  27. * 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. #if UNITY_2020_1_OR_NEWER
  42. #define REVERT_HAS_OVERLOADS
  43. #endif
  44. #define SPINE_OPTIONAL_RENDEROVERRIDE
  45. #define SPINE_OPTIONAL_MATERIALOVERRIDE
  46. #define SPINE_OPTIONAL_ON_DEMAND_LOADING
  47. using System.Collections.Generic;
  48. using UnityEngine;
  49. #if UNITY_EDITOR
  50. using UnityEditor.SceneManagement;
  51. #endif
  52. namespace Spine.Unity {
  53. /// <summary>Base class of animated Spine skeleton components. This component manages and renders a skeleton.</summary>
  54. #if NEW_PREFAB_SYSTEM
  55. [ExecuteAlways]
  56. #else
  57. [ExecuteInEditMode]
  58. #endif
  59. [RequireComponent(typeof(MeshRenderer)), DisallowMultipleComponent]
  60. [HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderer-Component")]
  61. public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
  62. public SkeletonDataAsset skeletonDataAsset;
  63. #region Initialization settings
  64. /// <summary>Skin name to use when the Skeleton is initialized.</summary>
  65. [SpineSkin(defaultAsEmptyString: true)] public string initialSkinName;
  66. /// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
  67. /// Otherwise any changes will be overwritten by the next inspector update.</summary>
  68. #if UNITY_EDITOR
  69. public bool EditorSkipSkinSync {
  70. get { return editorSkipSkinSync; }
  71. set { editorSkipSkinSync = value; }
  72. }
  73. protected bool editorSkipSkinSync = false;
  74. /// <summary>Sets the MeshFilter's hide flags to DontSaveInEditor which fixes the prefab
  75. /// always being marked as changed, but at the cost of references to the MeshFilter by other
  76. /// components being lost.</summary>
  77. public SettingsTriState fixPrefabOverrideViaMeshFilter = SettingsTriState.UseGlobalSetting;
  78. public static bool fixPrefabOverrideViaMeshFilterGlobal = false;
  79. public void EditorUpdateMeshFilterHideFlags () {
  80. if (!meshFilter) {
  81. meshFilter = GetComponent<MeshFilter>();
  82. if (meshFilter == null)
  83. meshFilter = gameObject.AddComponent<MeshFilter>();
  84. }
  85. bool dontSaveInEditor = false;
  86. if (fixPrefabOverrideViaMeshFilter == SettingsTriState.Enable ||
  87. (fixPrefabOverrideViaMeshFilter == SettingsTriState.UseGlobalSetting &&
  88. fixPrefabOverrideViaMeshFilterGlobal))
  89. dontSaveInEditor = true;
  90. if (dontSaveInEditor) {
  91. #if NEW_PREFAB_SYSTEM
  92. if (UnityEditor.PrefabUtility.IsPartOfAnyPrefab(meshFilter)) {
  93. GameObject instanceRoot = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(meshFilter);
  94. if (instanceRoot != null) {
  95. List<ObjectOverride> objectOverrides = UnityEditor.PrefabUtility.GetObjectOverrides(instanceRoot);
  96. foreach (ObjectOverride objectOverride in objectOverrides) {
  97. if (objectOverride.instanceObject == meshFilter) {
  98. #if REVERT_HAS_OVERLOADS
  99. objectOverride.Revert(UnityEditor.InteractionMode.AutomatedAction);
  100. #else
  101. objectOverride.Revert();
  102. #endif
  103. break;
  104. }
  105. }
  106. }
  107. }
  108. #endif
  109. meshFilter.hideFlags = HideFlags.DontSaveInEditor;
  110. } else {
  111. meshFilter.hideFlags = HideFlags.None;
  112. }
  113. }
  114. #endif
  115. /// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
  116. public bool initialFlipX, initialFlipY;
  117. #endregion
  118. #region Advanced Render Settings
  119. /// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
  120. public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
  121. protected UpdateMode updateMode = UpdateMode.FullUpdate;
  122. /// <summary>Update mode used when the MeshRenderer becomes invisible
  123. /// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
  124. /// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
  125. public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
  126. // Submesh Separation
  127. /// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
  128. [UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")] [SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0];
  129. /// <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>
  130. [System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
  131. // Render Settings
  132. [Range(-0.1f, 0f)] public float zSpacing;
  133. /// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
  134. public bool useClipping = true;
  135. /// <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>
  136. public bool immutableTriangles = false;
  137. /// <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>
  138. public bool pmaVertexColors = true;
  139. /// <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>
  140. public bool clearStateOnDisable = false;
  141. /// <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>
  142. public bool tintBlack = false;
  143. /// <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>
  144. /// <remarks>This disables SkeletonRenderSeparator functionality.</remarks>
  145. public bool singleSubmesh = false;
  146. #if PER_MATERIAL_PROPERTY_BLOCKS
  147. /// <summary> Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. "A B A").
  148. /// If true, GPU instancing is disabled at all materials and MaterialPropertyBlocks are assigned at each
  149. /// material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect
  150. /// draw order (e.g. "A1 B A2" changed to "A1A2 B").
  151. /// You can disable this parameter when everything is drawn correctly to save the additional performance cost.
  152. /// </summary>
  153. public bool fixDrawOrder = false;
  154. #endif
  155. /// <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>
  156. [UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] public bool addNormals = false;
  157. /// <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>
  158. public bool calculateTangents = false;
  159. #if BUILT_IN_SPRITE_MASK_COMPONENT
  160. /// <summary>This enum controls the mode under which the sprite will interact with the masking system.</summary>
  161. /// <remarks>Interaction modes with <see cref="UnityEngine.SpriteMask"/> components are identical to Unity's <see cref="UnityEngine.SpriteRenderer"/>,
  162. /// see https://docs.unity3d.com/ScriptReference/SpriteMaskInteraction.html. </remarks>
  163. public SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
  164. [System.Serializable]
  165. public class SpriteMaskInteractionMaterials {
  166. public bool AnyMaterialCreated {
  167. get {
  168. return materialsMaskDisabled.Length > 0 ||
  169. materialsInsideMask.Length > 0 ||
  170. materialsOutsideMask.Length > 0;
  171. }
  172. }
  173. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.None"/>.</summary>
  174. public Material[] materialsMaskDisabled = new Material[0];
  175. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  176. public Material[] materialsInsideMask = new Material[0];
  177. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  178. public Material[] materialsOutsideMask = new Material[0];
  179. }
  180. /// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes.</summary>
  181. public SpriteMaskInteractionMaterials maskMaterials = new SpriteMaskInteractionMaterials();
  182. /// <summary>Shader property ID used for the Stencil comparison function.</summary>
  183. public static readonly int STENCIL_COMP_PARAM_ID = Shader.PropertyToID("_StencilComp");
  184. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.None"/>.</summary>
  185. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_NONE = UnityEngine.Rendering.CompareFunction.Always;
  186. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
  187. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE = UnityEngine.Rendering.CompareFunction.LessEqual;
  188. /// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
  189. public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE = UnityEngine.Rendering.CompareFunction.Greater;
  190. #if UNITY_EDITOR
  191. private static bool haveStencilParametersBeenFixed = false;
  192. #endif
  193. #endif // #if BUILT_IN_SPRITE_MASK_COMPONENT
  194. #endregion
  195. #region Overrides
  196. #if SPINE_OPTIONAL_RENDEROVERRIDE
  197. // These are API for anything that wants to take over rendering for a SkeletonRenderer.
  198. public bool disableRenderingOnOverride = true;
  199. public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
  200. event InstructionDelegate generateMeshOverride;
  201. /// <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>
  202. public event InstructionDelegate GenerateMeshOverride {
  203. add {
  204. generateMeshOverride += value;
  205. if (disableRenderingOnOverride && generateMeshOverride != null) {
  206. Initialize(false);
  207. if (meshRenderer)
  208. meshRenderer.enabled = false;
  209. updateMode = UpdateMode.FullUpdate;
  210. }
  211. }
  212. remove {
  213. generateMeshOverride -= value;
  214. if (disableRenderingOnOverride && generateMeshOverride == null) {
  215. Initialize(false);
  216. if (meshRenderer)
  217. meshRenderer.enabled = true;
  218. }
  219. }
  220. }
  221. /// <summary> Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.</summary>
  222. public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
  223. #endif
  224. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  225. [System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
  226. /// <summary>Use this Dictionary to override a Material with a different Material.</summary>
  227. public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
  228. #endif
  229. [System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
  230. /// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
  231. public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
  232. #endregion
  233. #region Mesh Generator
  234. [System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
  235. readonly MeshGenerator meshGenerator = new MeshGenerator();
  236. [System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
  237. /// <summary>Returns the <see cref="SkeletonClipping"/> used by this renderer for use with e.g.
  238. /// <see cref="Skeleton.GetBounds(out float, out float, out float, out float, ref float[], SkeletonClipping)"/>
  239. /// </summary>
  240. public SkeletonClipping SkeletonClipping { get { return meshGenerator.SkeletonClipping; } }
  241. #endregion
  242. #region Cached component references
  243. MeshRenderer meshRenderer;
  244. MeshFilter meshFilter;
  245. #endregion
  246. #region Skeleton
  247. [System.NonSerialized] public bool valid;
  248. [System.NonSerialized] public Skeleton skeleton;
  249. public Skeleton Skeleton {
  250. get {
  251. Initialize(false);
  252. return skeleton;
  253. }
  254. }
  255. #endregion
  256. #region Physics
  257. /// <seealso cref="PhysicsPositionInheritanceFactor"/>
  258. [SerializeField] protected Vector2 physicsPositionInheritanceFactor = Vector2.one;
  259. /// <seealso cref="PhysicsRotationInheritanceFactor"/>
  260. [SerializeField] protected float physicsRotationInheritanceFactor = 1.0f;
  261. /// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
  262. [SerializeField] protected Transform physicsMovementRelativeTo = null;
  263. /// <summary>Used for applying Transform translation to skeleton PhysicsConstraints.</summary>
  264. protected Vector2 lastPosition;
  265. /// <summary>Used for applying Transform rotation to skeleton PhysicsConstraints.</summary>
  266. protected float lastRotation;
  267. /// <summary>When set to non-zero, Transform position movement in X and Y direction
  268. /// is applied to skeleton PhysicsConstraints, multiplied by this scale factor.
  269. /// Typical values are <c>Vector2.one</c> to apply XY movement 1:1,
  270. /// <c>Vector2(2f, 2f)</c> to apply movement with double intensity,
  271. /// <c>Vector2(1f, 0f)</c> to apply only horizontal movement, or
  272. /// <c>Vector2.zero</c> to not apply any Transform position movement at all.</summary>
  273. public Vector2 PhysicsPositionInheritanceFactor {
  274. get {
  275. return physicsPositionInheritanceFactor;
  276. }
  277. set {
  278. if (physicsPositionInheritanceFactor == Vector2.zero && value != Vector2.zero) ResetLastPosition();
  279. physicsPositionInheritanceFactor = value;
  280. }
  281. }
  282. /// <summary>When set to non-zero, Transform rotation movement is applied to skeleton PhysicsConstraints,
  283. /// multiplied by this scale factor. Typical values are <c>1</c> to apply movement 1:1,
  284. /// <c>2</c> to apply movement with double intensity, or
  285. /// <c>0</c> to not apply any Transform rotation movement at all.</summary>
  286. public float PhysicsRotationInheritanceFactor {
  287. get {
  288. return physicsRotationInheritanceFactor;
  289. }
  290. set {
  291. if (physicsRotationInheritanceFactor == 0f && value != 0f) ResetLastRotation();
  292. physicsRotationInheritanceFactor = value;
  293. }
  294. }
  295. /// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
  296. public Transform PhysicsMovementRelativeTo {
  297. get {
  298. return physicsMovementRelativeTo;
  299. }
  300. set {
  301. physicsMovementRelativeTo = value;
  302. if (physicsPositionInheritanceFactor != Vector2.zero) ResetLastPosition();
  303. if (physicsRotationInheritanceFactor != 0f) ResetLastRotation();
  304. }
  305. }
  306. public void ResetLastPosition () {
  307. lastPosition = GetPhysicsTransformPosition();
  308. }
  309. public void ResetLastRotation () {
  310. lastRotation = GetPhysicsTransformRotation();
  311. }
  312. public void ResetLastPositionAndRotation () {
  313. lastPosition = GetPhysicsTransformPosition();
  314. lastRotation = GetPhysicsTransformRotation();
  315. }
  316. #endregion
  317. public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
  318. /// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
  319. public event SkeletonRendererDelegate OnRebuild;
  320. /// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
  321. /// all materials have been updated.</summary>
  322. public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
  323. public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
  324. #region Runtime Instantiation
  325. public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
  326. return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset, quiet);
  327. }
  328. /// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
  329. /// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
  330. public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
  331. T c = gameObject.AddComponent<T>();
  332. if (skeletonDataAsset != null) {
  333. c.skeletonDataAsset = skeletonDataAsset;
  334. c.Initialize(false, quiet);
  335. }
  336. return c;
  337. }
  338. /// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
  339. public void SetMeshSettings (MeshGenerator.Settings settings) {
  340. this.calculateTangents = settings.calculateTangents;
  341. this.immutableTriangles = settings.immutableTriangles;
  342. this.pmaVertexColors = settings.pmaVertexColors;
  343. this.tintBlack = settings.tintBlack;
  344. this.useClipping = settings.useClipping;
  345. this.zSpacing = settings.zSpacing;
  346. this.meshGenerator.settings = settings;
  347. }
  348. #endregion
  349. public virtual void Awake () {
  350. Initialize(false);
  351. if (generateMeshOverride == null || !disableRenderingOnOverride)
  352. updateMode = updateWhenInvisible;
  353. }
  354. #if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE
  355. public virtual void Start () {
  356. Initialize(false);
  357. }
  358. #endif
  359. #if UNITY_EDITOR
  360. void OnEnable () {
  361. if (!Application.isPlaying)
  362. LateUpdate();
  363. }
  364. #endif
  365. void OnDisable () {
  366. if (clearStateOnDisable && valid)
  367. ClearState();
  368. }
  369. void OnDestroy () {
  370. rendererBuffers.Dispose();
  371. valid = false;
  372. }
  373. /// <summary>
  374. /// Clears the previously generated mesh and resets the skeleton's pose.</summary>
  375. public virtual void ClearState () {
  376. MeshFilter meshFilter = GetComponent<MeshFilter>();
  377. if (meshFilter != null) meshFilter.sharedMesh = null;
  378. currentInstructions.Clear();
  379. if (skeleton != null) skeleton.SetToSetupPose();
  380. }
  381. /// <summary>
  382. /// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
  383. /// </summary>
  384. public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
  385. meshGenerator.EnsureVertexCapacity(minimumVertexCount);
  386. }
  387. /// <summary>
  388. /// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
  389. /// <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>
  390. public virtual void Initialize (bool overwrite, bool quiet = false) {
  391. if (valid && !overwrite)
  392. return;
  393. #if UNITY_EDITOR
  394. if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing)
  395. return;
  396. #endif
  397. // Clear
  398. {
  399. // Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
  400. // otherwise constant reloading will be triggered at prefabs.
  401. currentInstructions.Clear();
  402. rendererBuffers.Clear();
  403. meshGenerator.Begin();
  404. skeleton = null;
  405. valid = false;
  406. }
  407. if (skeletonDataAsset == null)
  408. return;
  409. SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(quiet);
  410. if (skeletonData == null) return;
  411. valid = true;
  412. meshFilter = GetComponent<MeshFilter>();
  413. if (meshFilter == null)
  414. meshFilter = gameObject.AddComponent<MeshFilter>();
  415. meshRenderer = GetComponent<MeshRenderer>();
  416. rendererBuffers.Initialize();
  417. skeleton = new Skeleton(skeletonData) {
  418. ScaleX = initialFlipX ? -1 : 1,
  419. ScaleY = initialFlipY ? -1 : 1
  420. };
  421. ResetLastPositionAndRotation();
  422. if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
  423. skeleton.SetSkin(initialSkinName);
  424. separatorSlots.Clear();
  425. for (int i = 0; i < separatorSlotNames.Length; i++)
  426. separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
  427. // Generate mesh once, required to update mesh bounds for visibility
  428. UpdateMode updateModeSaved = updateMode;
  429. updateMode = UpdateMode.FullUpdate;
  430. UpdateWorldTransform(Skeleton.Physics.Update);
  431. LateUpdate();
  432. updateMode = updateModeSaved;
  433. if (OnRebuild != null)
  434. OnRebuild(this);
  435. #if UNITY_EDITOR
  436. if (!Application.isPlaying) {
  437. string errorMessage = null;
  438. if (!quiet && MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage))
  439. Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage);
  440. }
  441. #endif
  442. }
  443. public virtual void ApplyTransformMovementToPhysics () {
  444. if (Application.isPlaying) {
  445. if (physicsPositionInheritanceFactor != Vector2.zero) {
  446. Vector2 position = GetPhysicsTransformPosition();
  447. Vector2 positionDelta = position - lastPosition;
  448. positionDelta = transform.InverseTransformVector(positionDelta);
  449. if (physicsMovementRelativeTo != null) {
  450. positionDelta = physicsMovementRelativeTo.TransformVector(positionDelta);
  451. }
  452. positionDelta.x *= physicsPositionInheritanceFactor.x;
  453. positionDelta.y *= physicsPositionInheritanceFactor.y;
  454. skeleton.PhysicsTranslate(positionDelta.x, positionDelta.y);
  455. lastPosition = position;
  456. }
  457. if (physicsRotationInheritanceFactor != 0f) {
  458. float rotation = GetPhysicsTransformRotation();
  459. skeleton.PhysicsRotate(0, 0, physicsRotationInheritanceFactor * (rotation - lastRotation));
  460. lastRotation = rotation;
  461. }
  462. }
  463. }
  464. protected Vector2 GetPhysicsTransformPosition () {
  465. if (physicsMovementRelativeTo == null) {
  466. return transform.position;
  467. } else {
  468. if (physicsMovementRelativeTo == transform.parent)
  469. return transform.localPosition;
  470. else
  471. return physicsMovementRelativeTo.InverseTransformPoint(transform.position);
  472. }
  473. }
  474. protected float GetPhysicsTransformRotation () {
  475. if (physicsMovementRelativeTo == null) {
  476. return this.transform.rotation.eulerAngles.z;
  477. } else {
  478. if (physicsMovementRelativeTo == this.transform.parent)
  479. return this.transform.localRotation.eulerAngles.z;
  480. else {
  481. Quaternion relative = Quaternion.Inverse(physicsMovementRelativeTo.rotation) * this.transform.rotation;
  482. return relative.eulerAngles.z;
  483. }
  484. }
  485. }
  486. protected virtual void UpdateWorldTransform (Skeleton.Physics physics) {
  487. skeleton.UpdateWorldTransform(physics);
  488. }
  489. /// <summary>
  490. /// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
  491. public virtual void LateUpdate () {
  492. if (!valid) return;
  493. #if UNITY_EDITOR && NEW_PREFAB_SYSTEM
  494. // Don't store mesh or material at the prefab, otherwise it will permanently reload
  495. UnityEditor.PrefabAssetType prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
  496. if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
  497. (prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
  498. return;
  499. }
  500. EditorUpdateMeshFilterHideFlags();
  501. #endif
  502. if (updateMode != UpdateMode.FullUpdate) return;
  503. LateUpdateMesh();
  504. }
  505. public virtual void LateUpdateMesh () {
  506. #if SPINE_OPTIONAL_RENDEROVERRIDE
  507. bool doMeshOverride = generateMeshOverride != null;
  508. if ((!meshRenderer || !meshRenderer.enabled) && !doMeshOverride) return;
  509. #else
  510. const bool doMeshOverride = false;
  511. if (!meshRenderer.enabled) return;
  512. #endif
  513. SkeletonRendererInstruction currentInstructions = this.currentInstructions;
  514. ExposedList<SubmeshInstruction> workingSubmeshInstructions = currentInstructions.submeshInstructions;
  515. MeshRendererBuffers.SmartMesh currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
  516. bool updateTriangles;
  517. if (this.singleSubmesh) {
  518. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  519. MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
  520. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  521. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  522. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  523. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  524. #endif
  525. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  526. meshGenerator.settings = new MeshGenerator.Settings {
  527. pmaVertexColors = this.pmaVertexColors,
  528. zSpacing = this.zSpacing,
  529. useClipping = this.useClipping,
  530. tintBlack = this.tintBlack,
  531. calculateTangents = this.calculateTangents,
  532. addNormals = this.addNormals
  533. };
  534. meshGenerator.Begin();
  535. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  536. if (currentInstructions.hasActiveClipping) {
  537. meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
  538. } else {
  539. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  540. }
  541. } else {
  542. // STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
  543. MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
  544. // STEP 1.9. Post-process workingInstructions. ==================================================================================
  545. #if SPINE_OPTIONAL_MATERIALOVERRIDE
  546. if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
  547. MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
  548. #endif
  549. #if SPINE_OPTIONAL_RENDEROVERRIDE
  550. if (doMeshOverride) {
  551. this.generateMeshOverride(currentInstructions);
  552. if (disableRenderingOnOverride) return;
  553. }
  554. #endif
  555. updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
  556. // STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
  557. meshGenerator.settings = new MeshGenerator.Settings {
  558. pmaVertexColors = this.pmaVertexColors,
  559. zSpacing = this.zSpacing,
  560. useClipping = this.useClipping,
  561. tintBlack = this.tintBlack,
  562. calculateTangents = this.calculateTangents,
  563. addNormals = this.addNormals
  564. };
  565. meshGenerator.Begin();
  566. if (currentInstructions.hasActiveClipping)
  567. meshGenerator.BuildMesh(currentInstructions, updateTriangles);
  568. else
  569. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  570. }
  571. if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
  572. // STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
  573. Mesh currentMesh = currentSmartMesh.mesh;
  574. meshGenerator.FillVertexData(currentMesh);
  575. rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
  576. bool materialsChanged = rendererBuffers.MaterialsChangedInLastUpdate();
  577. if (updateTriangles) { // Check if the triangles should also be updated.
  578. meshGenerator.FillTriangles(currentMesh);
  579. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  580. } else if (materialsChanged) {
  581. meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
  582. }
  583. if (materialsChanged && (this.maskMaterials.AnyMaterialCreated)) {
  584. this.maskMaterials = new SpriteMaskInteractionMaterials();
  585. }
  586. meshGenerator.FillLateVertexData(currentMesh);
  587. // STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
  588. if (meshFilter)
  589. meshFilter.sharedMesh = currentMesh;
  590. currentSmartMesh.instructionUsed.Set(currentInstructions);
  591. #if BUILT_IN_SPRITE_MASK_COMPONENT
  592. if (meshRenderer != null) {
  593. AssignSpriteMaskMaterials();
  594. }
  595. #endif
  596. #if SPINE_OPTIONAL_ON_DEMAND_LOADING
  597. if (Application.isPlaying)
  598. HandleOnDemandLoading();
  599. #endif
  600. #if PER_MATERIAL_PROPERTY_BLOCKS
  601. if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
  602. SetMaterialSettingsToFixDrawOrder();
  603. }
  604. #endif
  605. if (OnMeshAndMaterialsUpdated != null)
  606. OnMeshAndMaterialsUpdated(this);
  607. }
  608. public virtual void OnBecameVisible () {
  609. UpdateMode previousUpdateMode = updateMode;
  610. updateMode = UpdateMode.FullUpdate;
  611. // OnBecameVisible is called after LateUpdate()
  612. if (previousUpdateMode != UpdateMode.FullUpdate)
  613. LateUpdate();
  614. }
  615. public void OnBecameInvisible () {
  616. updateMode = updateWhenInvisible;
  617. }
  618. public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
  619. if (string.IsNullOrEmpty(startsWith)) return;
  620. FindAndApplySeparatorSlots(
  621. (slotName) => slotName.StartsWith(startsWith),
  622. clearExistingSeparators,
  623. updateStringArray
  624. );
  625. }
  626. public void FindAndApplySeparatorSlots (System.Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false) {
  627. if (slotNamePredicate == null) return;
  628. if (!valid) return;
  629. if (clearExistingSeparators)
  630. separatorSlots.Clear();
  631. ExposedList<Slot> slots = skeleton.Slots;
  632. foreach (Slot slot in slots) {
  633. if (slotNamePredicate.Invoke(slot.Data.Name))
  634. separatorSlots.Add(slot);
  635. }
  636. if (updateStringArray) {
  637. List<string> detectedSeparatorNames = new List<string>();
  638. foreach (Slot slot in skeleton.Slots) {
  639. string slotName = slot.Data.Name;
  640. if (slotNamePredicate.Invoke(slotName))
  641. detectedSeparatorNames.Add(slotName);
  642. }
  643. if (!clearExistingSeparators) {
  644. string[] originalNames = this.separatorSlotNames;
  645. foreach (string originalName in originalNames)
  646. detectedSeparatorNames.Add(originalName);
  647. }
  648. this.separatorSlotNames = detectedSeparatorNames.ToArray();
  649. }
  650. }
  651. public void ReapplySeparatorSlotNames () {
  652. if (!valid)
  653. return;
  654. separatorSlots.Clear();
  655. for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
  656. Slot slot = skeleton.FindSlot(separatorSlotNames[i]);
  657. if (slot != null) {
  658. separatorSlots.Add(slot);
  659. }
  660. #if UNITY_EDITOR
  661. else if (!string.IsNullOrEmpty(separatorSlotNames[i])) {
  662. Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
  663. }
  664. #endif
  665. }
  666. }
  667. #if BUILT_IN_SPRITE_MASK_COMPONENT
  668. private void AssignSpriteMaskMaterials () {
  669. #if UNITY_EDITOR
  670. if (!Application.isPlaying && !UnityEditor.EditorApplication.isUpdating) {
  671. EditorFixStencilCompParameters();
  672. }
  673. #endif
  674. if (Application.isPlaying) {
  675. if (maskInteraction != SpriteMaskInteraction.None && maskMaterials.materialsMaskDisabled.Length == 0)
  676. maskMaterials.materialsMaskDisabled = meshRenderer.sharedMaterials;
  677. }
  678. if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
  679. maskInteraction == SpriteMaskInteraction.None) {
  680. this.meshRenderer.materials = maskMaterials.materialsMaskDisabled;
  681. } else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
  682. if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null) {
  683. if (!InitSpriteMaskMaterialsInsideMask())
  684. return;
  685. }
  686. this.meshRenderer.materials = maskMaterials.materialsInsideMask;
  687. } else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
  688. if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null) {
  689. if (!InitSpriteMaskMaterialsOutsideMask())
  690. return;
  691. }
  692. this.meshRenderer.materials = maskMaterials.materialsOutsideMask;
  693. }
  694. }
  695. private bool InitSpriteMaskMaterialsInsideMask () {
  696. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE, ref maskMaterials.materialsInsideMask);
  697. }
  698. private bool InitSpriteMaskMaterialsOutsideMask () {
  699. return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE, ref maskMaterials.materialsOutsideMask);
  700. }
  701. private bool InitSpriteMaskMaterialsForMaskType (UnityEngine.Rendering.CompareFunction maskFunction, ref Material[] materialsToFill) {
  702. #if UNITY_EDITOR
  703. if (!Application.isPlaying) {
  704. return false;
  705. }
  706. #endif
  707. Material[] originalMaterials = maskMaterials.materialsMaskDisabled;
  708. materialsToFill = new Material[originalMaterials.Length];
  709. for (int i = 0; i < originalMaterials.Length; i++) {
  710. Material originalMaterial = originalMaterials[i];
  711. if (originalMaterial == null) {
  712. materialsToFill[i] = null;
  713. continue;
  714. }
  715. Material newMaterial = new Material(originalMaterial);
  716. newMaterial.SetFloat(STENCIL_COMP_PARAM_ID, (int)maskFunction);
  717. materialsToFill[i] = newMaterial;
  718. }
  719. return true;
  720. }
  721. #if UNITY_EDITOR
  722. private void EditorFixStencilCompParameters () {
  723. if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
  724. haveStencilParametersBeenFixed = true;
  725. FixAllProjectMaterialsStencilCompParameters();
  726. }
  727. }
  728. private void FixAllProjectMaterialsStencilCompParameters () {
  729. string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
  730. foreach (string guid in materialGUIDS) {
  731. string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
  732. if (!string.IsNullOrEmpty(path)) {
  733. Material material = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
  734. if (material.HasProperty(STENCIL_COMP_PARAM_ID) && material.GetFloat(STENCIL_COMP_PARAM_ID) == 0) {
  735. material.SetFloat(STENCIL_COMP_PARAM_ID, (int)STENCIL_COMP_MASKINTERACTION_NONE);
  736. }
  737. }
  738. }
  739. UnityEditor.AssetDatabase.Refresh();
  740. UnityEditor.AssetDatabase.SaveAssets();
  741. }
  742. private bool HasAnyStencilComp0Material () {
  743. if (meshRenderer == null)
  744. return false;
  745. foreach (Material material in meshRenderer.sharedMaterials) {
  746. if (material != null && material.HasProperty(STENCIL_COMP_PARAM_ID)) {
  747. float currentCompValue = material.GetFloat(STENCIL_COMP_PARAM_ID);
  748. if (currentCompValue == 0)
  749. return true;
  750. }
  751. }
  752. return false;
  753. }
  754. #endif // UNITY_EDITOR
  755. #endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
  756. #if SPINE_OPTIONAL_ON_DEMAND_LOADING
  757. void HandleOnDemandLoading () {
  758. foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) {
  759. if (atlasAsset.TextureLoadingMode != AtlasAssetBase.LoadingMode.Normal) {
  760. atlasAsset.BeginCustomTextureLoading();
  761. for (int i = 0, count = meshRenderer.sharedMaterials.Length; i < count; ++i) {
  762. Material overrideMaterial = null;
  763. atlasAsset.RequireTexturesLoaded(meshRenderer.sharedMaterials[i], ref overrideMaterial);
  764. if (overrideMaterial != null)
  765. meshRenderer.sharedMaterials[i] = overrideMaterial;
  766. }
  767. atlasAsset.EndCustomTextureLoading();
  768. }
  769. }
  770. }
  771. #endif
  772. #if PER_MATERIAL_PROPERTY_BLOCKS
  773. private MaterialPropertyBlock reusedPropertyBlock;
  774. public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
  775. /// <summary>
  776. /// This method was introduced as a workaround for too aggressive submesh draw call batching,
  777. /// leading to incorrect draw order when 3+ materials are used at submeshes in alternating order.
  778. /// Otherwise, e.g. when using Lightweight Render Pipeline, deliberately separated draw calls
  779. /// "A1 B A2" are reordered to "A1A2 B", regardless of batching-related project settings.
  780. /// </summary>
  781. private void SetMaterialSettingsToFixDrawOrder () {
  782. if (reusedPropertyBlock == null) reusedPropertyBlock = new MaterialPropertyBlock();
  783. bool hasPerRendererBlock = meshRenderer.HasPropertyBlock();
  784. if (hasPerRendererBlock) {
  785. meshRenderer.GetPropertyBlock(reusedPropertyBlock);
  786. }
  787. for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) {
  788. if (!meshRenderer.sharedMaterials[i])
  789. continue;
  790. if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i);
  791. // Note: this parameter shall not exist at any shader, then Unity will create separate
  792. // material instances (not in terms of memory cost or leakage).
  793. reusedPropertyBlock.SetFloat(SUBMESH_DUMMY_PARAM_ID, i);
  794. meshRenderer.SetPropertyBlock(reusedPropertyBlock, i);
  795. meshRenderer.sharedMaterials[i].enableInstancing = false;
  796. }
  797. }
  798. #endif
  799. }
  800. }