SkeletonGraphic.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  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. using System.Collections.Generic;
  33. using UnityEngine;
  34. using UnityEngine.UI;
  35. namespace Spine.Unity {
  36. #if NEW_PREFAB_SYSTEM
  37. [ExecuteAlways]
  38. #else
  39. [ExecuteInEditMode]
  40. #endif
  41. [RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]
  42. [AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")]
  43. public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation, IHasSkeletonDataAsset {
  44. #region Inspector
  45. public SkeletonDataAsset skeletonDataAsset;
  46. public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
  47. [SpineSkin(dataField:"skeletonDataAsset", defaultAsEmptyString:true)]
  48. public string initialSkinName;
  49. public bool initialFlipX, initialFlipY;
  50. [SpineAnimation(dataField:"skeletonDataAsset")]
  51. public string startingAnimation;
  52. public bool startingLoop;
  53. public float timeScale = 1f;
  54. public bool freeze;
  55. public bool unscaledTime;
  56. public bool allowMultipleCanvasRenderers = false;
  57. public List<CanvasRenderer> canvasRenderers = new List<CanvasRenderer>();
  58. // Submesh Separation
  59. public const string SeparatorPartGameObjectName = "Part";
  60. /// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
  61. [SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0];
  62. /// <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>
  63. [System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
  64. public bool enableSeparatorSlots = false;
  65. [SerializeField] protected List<Transform> separatorParts = new List<Transform>();
  66. public List<Transform> SeparatorParts { get { return separatorParts; } }
  67. public bool updateSeparatorPartLocation = true;
  68. private bool wasUpdatedAfterInit = true;
  69. private Texture baseTexture = null;
  70. #if UNITY_EDITOR
  71. protected override void OnValidate () {
  72. // This handles Scene View preview.
  73. base.OnValidate ();
  74. if (this.IsValid) {
  75. if (skeletonDataAsset == null) {
  76. Clear();
  77. } else if (skeletonDataAsset.skeletonJSON == null) {
  78. Clear();
  79. } else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
  80. Clear();
  81. Initialize(true);
  82. if (!allowMultipleCanvasRenderers && (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].MaterialCount > 1))
  83. Debug.LogError("Unity UI does not support multiple textures per Renderer. Please enable 'Advanced - Multiple CanvasRenderers' to generate the required CanvasRenderer GameObjects. Otherwise your skeleton will not be rendered correctly.", this);
  84. } else {
  85. if (freeze) return;
  86. if (!string.IsNullOrEmpty(initialSkinName)) {
  87. var skin = skeleton.data.FindSkin(initialSkinName);
  88. if (skin != null) {
  89. if (skin == skeleton.data.defaultSkin)
  90. skeleton.SetSkin((Skin)null);
  91. else
  92. skeleton.SetSkin(skin);
  93. }
  94. }
  95. // Only provide visual feedback to inspector changes in Unity Editor Edit mode.
  96. if (!Application.isPlaying) {
  97. skeleton.ScaleX = this.initialFlipX ? -1 : 1;
  98. skeleton.ScaleY = this.initialFlipY ? -1 : 1;
  99. state.ClearTrack(0);
  100. skeleton.SetToSetupPose();
  101. if (!string.IsNullOrEmpty(startingAnimation)) {
  102. state.SetAnimation(0, startingAnimation, startingLoop);
  103. Update(0f);
  104. }
  105. }
  106. }
  107. } else {
  108. // Under some circumstances (e.g. sometimes on the first import) OnValidate is called
  109. // before SpineEditorUtilities.ImportSpineContent, causing an unnecessary exception.
  110. // The (skeletonDataAsset.skeletonJSON != null) condition serves to prevent this exception.
  111. if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON != null)
  112. Initialize(true);
  113. }
  114. }
  115. protected override void Reset () {
  116. base.Reset();
  117. if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic"))
  118. Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material.");
  119. }
  120. #endif
  121. #endregion
  122. #region Runtime Instantiation
  123. /// <summary>Create a new GameObject with a SkeletonGraphic component.</summary>
  124. /// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
  125. public static SkeletonGraphic NewSkeletonGraphicGameObject (SkeletonDataAsset skeletonDataAsset, Transform parent, Material material) {
  126. var sg = SkeletonGraphic.AddSkeletonGraphicComponent(new GameObject("New Spine GameObject"), skeletonDataAsset, material);
  127. if (parent != null) sg.transform.SetParent(parent, false);
  128. return sg;
  129. }
  130. /// <summary>Add a SkeletonGraphic component to a GameObject.</summary>
  131. /// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
  132. public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) {
  133. var c = gameObject.AddComponent<SkeletonGraphic>();
  134. if (skeletonDataAsset != null) {
  135. c.material = material;
  136. c.skeletonDataAsset = skeletonDataAsset;
  137. c.Initialize(false);
  138. }
  139. return c;
  140. }
  141. #endregion
  142. #region Overrides
  143. [System.NonSerialized] readonly Dictionary<Texture, Texture> customTextureOverride = new Dictionary<Texture, Texture>();
  144. /// <summary>Use this Dictionary to override a Texture with a different Texture.</summary>
  145. public Dictionary<Texture, Texture> CustomTextureOverride { get { return customTextureOverride; } }
  146. [System.NonSerialized] readonly Dictionary<Texture, Material> customMaterialOverride = new Dictionary<Texture, Material>();
  147. /// <summary>Use this Dictionary to override the Material where the Texture was used at the original atlas.</summary>
  148. public Dictionary<Texture, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
  149. // This is used by the UI system to determine what to put in the MaterialPropertyBlock.
  150. Texture overrideTexture;
  151. public Texture OverrideTexture {
  152. get { return overrideTexture; }
  153. set {
  154. overrideTexture = value;
  155. canvasRenderer.SetTexture(this.mainTexture); // Refresh canvasRenderer's texture. Make sure it handles null.
  156. }
  157. }
  158. #endregion
  159. #region Internals
  160. public override Texture mainTexture {
  161. get {
  162. if (overrideTexture != null) return overrideTexture;
  163. return baseTexture;
  164. }
  165. }
  166. protected override void Awake () {
  167. base.Awake ();
  168. if (!this.IsValid) {
  169. #if UNITY_EDITOR
  170. // workaround for special import case of open scene where OnValidate and Awake are
  171. // called in wrong order, before setup of Spine assets.
  172. if (!Application.isPlaying) {
  173. if (this.skeletonDataAsset != null && this.skeletonDataAsset.skeletonJSON == null)
  174. return;
  175. }
  176. #endif
  177. Initialize(false);
  178. Rebuild(CanvasUpdate.PreRender);
  179. }
  180. }
  181. public override void Rebuild (CanvasUpdate update) {
  182. base.Rebuild(update);
  183. if (canvasRenderer.cull) return;
  184. if (update == CanvasUpdate.PreRender) UpdateMesh();
  185. if (allowMultipleCanvasRenderers) canvasRenderer.Clear();
  186. }
  187. protected override void OnDisable () {
  188. base.OnDisable();
  189. foreach (var canvasRenderer in canvasRenderers) {
  190. canvasRenderer.Clear();
  191. }
  192. }
  193. public virtual void Update () {
  194. #if UNITY_EDITOR
  195. if (!Application.isPlaying) {
  196. Update(0f);
  197. return;
  198. }
  199. #endif
  200. if (freeze) return;
  201. Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
  202. }
  203. public virtual void Update (float deltaTime) {
  204. if (!this.IsValid) return;
  205. deltaTime *= timeScale;
  206. skeleton.Update(deltaTime);
  207. state.Update(deltaTime);
  208. state.Apply(skeleton);
  209. if (UpdateLocal != null) UpdateLocal(this);
  210. skeleton.UpdateWorldTransform();
  211. if (UpdateWorld != null) {
  212. UpdateWorld(this);
  213. skeleton.UpdateWorldTransform();
  214. }
  215. if (UpdateComplete != null) UpdateComplete(this);
  216. wasUpdatedAfterInit = true;
  217. }
  218. public void LateUpdate () {
  219. // instantiation can happen from Update() after this component, leading to a missing Update() call.
  220. if (!wasUpdatedAfterInit) Update(0);
  221. if (freeze) return;
  222. //this.SetVerticesDirty(); // Which is better?
  223. UpdateMesh();
  224. }
  225. public void ReapplySeparatorSlotNames () {
  226. if (!IsValid)
  227. return;
  228. separatorSlots.Clear();
  229. for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
  230. string slotName = separatorSlotNames[i];
  231. if (slotName == "")
  232. continue;
  233. var slot = skeleton.FindSlot(slotName);
  234. if (slot != null) {
  235. separatorSlots.Add(slot);
  236. }
  237. #if UNITY_EDITOR
  238. else
  239. {
  240. Debug.LogWarning(slotName + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
  241. }
  242. #endif
  243. }
  244. UpdateSeparatorPartParents();
  245. }
  246. #endregion
  247. #region API
  248. protected Skeleton skeleton;
  249. public Skeleton Skeleton { get { return skeleton; } set { skeleton = value; } }
  250. public SkeletonData SkeletonData { get { return skeleton == null ? null : skeleton.data; } }
  251. public bool IsValid { get { return skeleton != null; } }
  252. public delegate void SkeletonRendererDelegate (SkeletonGraphic skeletonGraphic);
  253. /// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
  254. public event SkeletonRendererDelegate OnRebuild;
  255. /// <summary>OnMeshAndMaterialsUpdated is at the end of LateUpdate after the Mesh and
  256. /// all materials have been updated.</summary>
  257. public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
  258. protected Spine.AnimationState state;
  259. public Spine.AnimationState AnimationState { get { return state; } }
  260. [SerializeField] protected Spine.Unity.MeshGenerator meshGenerator = new MeshGenerator();
  261. public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } }
  262. DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
  263. SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
  264. readonly ExposedList<Mesh> meshes = new ExposedList<Mesh>();
  265. public Mesh GetLastMesh () {
  266. return meshBuffers.GetCurrent().mesh;
  267. }
  268. public bool MatchRectTransformWithBounds () {
  269. UpdateMesh();
  270. if (!this.allowMultipleCanvasRenderers)
  271. return MatchRectTransformSingleRenderer();
  272. else
  273. return MatchRectTransformMultipleRenderers();
  274. }
  275. protected bool MatchRectTransformSingleRenderer () {
  276. Mesh mesh = this.GetLastMesh();
  277. if (mesh == null) {
  278. return false;
  279. }
  280. if (mesh.vertexCount == 0) {
  281. this.rectTransform.sizeDelta = new Vector2(50f, 50f);
  282. this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
  283. return false;
  284. }
  285. mesh.RecalculateBounds();
  286. SetRectTransformBounds(mesh.bounds);
  287. return true;
  288. }
  289. protected bool MatchRectTransformMultipleRenderers () {
  290. bool anyBoundsAdded = false;
  291. Bounds combinedBounds = new Bounds();
  292. for (int i = 0; i < canvasRenderers.Count; ++i) {
  293. var canvasRenderer = canvasRenderers[i];
  294. if (!canvasRenderer.gameObject.activeSelf)
  295. continue;
  296. Mesh mesh = meshes.Items[i];
  297. if (mesh == null || mesh.vertexCount == 0)
  298. continue;
  299. mesh.RecalculateBounds();
  300. var bounds = mesh.bounds;
  301. if (anyBoundsAdded)
  302. combinedBounds.Encapsulate(bounds);
  303. else {
  304. anyBoundsAdded = true;
  305. combinedBounds = bounds;
  306. }
  307. }
  308. if (!anyBoundsAdded) {
  309. this.rectTransform.sizeDelta = new Vector2(50f, 50f);
  310. this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
  311. return false;
  312. }
  313. SetRectTransformBounds(combinedBounds);
  314. return true;
  315. }
  316. private void SetRectTransformBounds (Bounds combinedBounds) {
  317. var size = combinedBounds.size;
  318. var center = combinedBounds.center;
  319. var p = new Vector2(
  320. 0.5f - (center.x / size.x),
  321. 0.5f - (center.y / size.y)
  322. );
  323. this.rectTransform.sizeDelta = size;
  324. this.rectTransform.pivot = p;
  325. }
  326. public event UpdateBonesDelegate UpdateLocal;
  327. public event UpdateBonesDelegate UpdateWorld;
  328. public event UpdateBonesDelegate UpdateComplete;
  329. /// <summary> Occurs after the vertex data populated every frame, before the vertices are pushed into the mesh.</summary>
  330. public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
  331. public void Clear () {
  332. skeleton = null;
  333. canvasRenderer.Clear();
  334. for (int i = 0; i < canvasRenderers.Count; ++i)
  335. canvasRenderers[i].Clear();
  336. foreach (var mesh in meshes)
  337. Destroy(mesh);
  338. meshes.Clear();
  339. }
  340. public void TrimRenderers () {
  341. var newList = new List<CanvasRenderer>();
  342. foreach (var canvasRenderer in canvasRenderers) {
  343. if (canvasRenderer.gameObject.activeSelf) {
  344. newList.Add(canvasRenderer);
  345. }
  346. else {
  347. if (Application.isEditor && !Application.isPlaying)
  348. DestroyImmediate(canvasRenderer.gameObject);
  349. else
  350. Destroy(canvasRenderer.gameObject);
  351. }
  352. }
  353. canvasRenderers = newList;
  354. }
  355. public void Initialize (bool overwrite) {
  356. if (this.IsValid && !overwrite) return;
  357. // Make sure none of the stuff is null
  358. if (this.skeletonDataAsset == null) return;
  359. var skeletonData = this.skeletonDataAsset.GetSkeletonData(false);
  360. if (skeletonData == null) return;
  361. if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].MaterialCount <= 0) return;
  362. this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
  363. if (state == null) {
  364. Clear();
  365. return;
  366. }
  367. this.skeleton = new Skeleton(skeletonData) {
  368. ScaleX = this.initialFlipX ? -1 : 1,
  369. ScaleY = this.initialFlipY ? -1 : 1
  370. };
  371. InitMeshBuffers();
  372. baseTexture = skeletonDataAsset.atlasAssets[0].PrimaryMaterial.mainTexture;
  373. canvasRenderer.SetTexture(this.mainTexture); // Needed for overwriting initializations.
  374. // Set the initial Skin and Animation
  375. if (!string.IsNullOrEmpty(initialSkinName))
  376. skeleton.SetSkin(initialSkinName);
  377. separatorSlots.Clear();
  378. for (int i = 0; i < separatorSlotNames.Length; i++)
  379. separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
  380. wasUpdatedAfterInit = false;
  381. if (!string.IsNullOrEmpty(startingAnimation)) {
  382. var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
  383. if (animationObject != null) {
  384. state.SetAnimation(0, animationObject, startingLoop);
  385. #if UNITY_EDITOR
  386. if (!Application.isPlaying)
  387. Update(0f);
  388. #endif
  389. }
  390. }
  391. if (OnRebuild != null)
  392. OnRebuild(this);
  393. }
  394. public void UpdateMesh () {
  395. if (!this.IsValid) return;
  396. skeleton.SetColor(this.color);
  397. var currentInstructions = this.currentInstructions;
  398. if (!this.allowMultipleCanvasRenderers) {
  399. UpdateMeshSingleCanvasRenderer();
  400. }
  401. else {
  402. UpdateMeshMultipleCanvasRenderers(currentInstructions);
  403. }
  404. if (OnMeshAndMaterialsUpdated != null)
  405. OnMeshAndMaterialsUpdated(this);
  406. }
  407. public bool HasMultipleSubmeshInstructions () {
  408. if (!IsValid)
  409. return false;
  410. return MeshGenerator.RequiresMultipleSubmeshesByDrawOrder(skeleton);
  411. }
  412. #endregion
  413. protected void InitMeshBuffers () {
  414. if (meshBuffers != null) {
  415. meshBuffers.GetNext().Clear();
  416. meshBuffers.GetNext().Clear();
  417. }
  418. else {
  419. meshBuffers = new DoubleBuffered<MeshRendererBuffers.SmartMesh>();
  420. }
  421. }
  422. protected void UpdateMeshSingleCanvasRenderer () {
  423. if (canvasRenderers.Count > 0)
  424. DisableUnusedCanvasRenderers(usedCount : 0);
  425. var smartMesh = meshBuffers.GetNext();
  426. MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null);
  427. bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
  428. meshGenerator.Begin();
  429. if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) {
  430. meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles);
  431. }
  432. else {
  433. meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
  434. }
  435. if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
  436. if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
  437. var mesh = smartMesh.mesh;
  438. meshGenerator.FillVertexData(mesh);
  439. if (updateTriangles) meshGenerator.FillTriangles(mesh);
  440. meshGenerator.FillLateVertexData(mesh);
  441. canvasRenderer.SetMesh(mesh);
  442. smartMesh.instructionUsed.Set(currentInstructions);
  443. if (currentInstructions.submeshInstructions.Count > 0) {
  444. var material = currentInstructions.submeshInstructions.Items[0].material;
  445. if (material != null && baseTexture != material.mainTexture) {
  446. baseTexture = material.mainTexture;
  447. if (overrideTexture == null)
  448. canvasRenderer.SetTexture(this.mainTexture);
  449. }
  450. }
  451. //this.UpdateMaterial(); // note: This would allocate memory.
  452. }
  453. protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) {
  454. MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null,
  455. enableSeparatorSlots ? separatorSlots : null,
  456. enableSeparatorSlots ? separatorSlots.Count > 0 : false,
  457. false);
  458. int submeshCount = currentInstructions.submeshInstructions.Count;
  459. EnsureCanvasRendererCount(submeshCount);
  460. EnsureMeshesCount(submeshCount);
  461. EnsureSeparatorPartCount();
  462. var c = canvas;
  463. float scale = (c == null) ? 100 : c.referencePixelsPerUnit;
  464. // Generate meshes.
  465. var meshesItems = meshes.Items;
  466. bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0);
  467. int separatorSlotGroupIndex = 0;
  468. Transform parent = this.separatorSlots.Count == 0 ? this.transform : this.separatorParts[0];
  469. if (updateSeparatorPartLocation) {
  470. for (int p = 0; p < this.separatorParts.Count; ++p) {
  471. separatorParts[p].position = this.transform.position;
  472. separatorParts[p].rotation = this.transform.rotation;
  473. }
  474. }
  475. int targetSiblingIndex = 0;
  476. for (int i = 0; i < submeshCount; i++) {
  477. var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
  478. meshGenerator.Begin();
  479. meshGenerator.AddSubmesh(submeshInstructionItem);
  480. var targetMesh = meshesItems[i];
  481. meshGenerator.ScaleVertexData(scale);
  482. if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
  483. meshGenerator.FillVertexData(targetMesh);
  484. meshGenerator.FillTriangles(targetMesh);
  485. meshGenerator.FillLateVertexData(targetMesh);
  486. var submeshMaterial = submeshInstructionItem.material;
  487. var canvasRenderer = canvasRenderers[i];
  488. canvasRenderer.gameObject.SetActive(true);
  489. canvasRenderer.SetMesh(targetMesh);
  490. canvasRenderer.materialCount = 1;
  491. if (canvasRenderer.transform.parent != parent.transform) {
  492. canvasRenderer.transform.SetParent(parent.transform, false);
  493. canvasRenderer.transform.localPosition = Vector3.zero;
  494. }
  495. canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++);
  496. if (submeshInstructionItem.forceSeparate) {
  497. targetSiblingIndex = 0;
  498. parent = separatorParts[++separatorSlotGroupIndex];
  499. }
  500. if (useOriginalTextureAndMaterial)
  501. canvasRenderer.SetMaterial(this.materialForRendering, submeshMaterial.mainTexture);
  502. else {
  503. var originalTexture = submeshMaterial.mainTexture;
  504. Material usedMaterial;
  505. Texture usedTexture;
  506. if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial))
  507. usedMaterial = material;
  508. if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture))
  509. usedTexture = originalTexture;
  510. canvasRenderer.SetMaterial(usedMaterial, usedTexture);
  511. }
  512. }
  513. DisableUnusedCanvasRenderers(usedCount : submeshCount);
  514. }
  515. protected void EnsureCanvasRendererCount (int targetCount) {
  516. #if UNITY_EDITOR
  517. RemoveNullCanvasRenderers();
  518. #endif
  519. int currentCount = canvasRenderers.Count;
  520. for (int i = currentCount; i < targetCount; ++i) {
  521. var go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform));
  522. go.transform.SetParent(this.transform, false);
  523. go.transform.localPosition = Vector3.zero;
  524. var canvasRenderer = go.AddComponent<CanvasRenderer>();
  525. canvasRenderers.Add(canvasRenderer);
  526. }
  527. }
  528. protected void DisableUnusedCanvasRenderers (int usedCount) {
  529. #if UNITY_EDITOR
  530. RemoveNullCanvasRenderers();
  531. #endif
  532. for (int i = usedCount; i < canvasRenderers.Count; i++) {
  533. canvasRenderers[i].Clear();
  534. canvasRenderers[i].gameObject.SetActive(false);
  535. }
  536. }
  537. #if UNITY_EDITOR
  538. private void RemoveNullCanvasRenderers () {
  539. if (Application.isEditor && !Application.isPlaying) {
  540. for (int i = canvasRenderers.Count - 1; i >= 0; --i) {
  541. if (canvasRenderers[i] == null) {
  542. canvasRenderers.RemoveAt(i);
  543. }
  544. }
  545. }
  546. }
  547. #endif
  548. protected void EnsureMeshesCount (int targetCount) {
  549. int oldCount = meshes.Count;
  550. meshes.EnsureCapacity(targetCount);
  551. var meshesItems = meshes.Items;
  552. for (int i = oldCount; i < targetCount; i++)
  553. if (meshesItems[i] == null) meshesItems[i] = new Mesh();
  554. }
  555. protected void EnsureSeparatorPartCount () {
  556. #if UNITY_EDITOR
  557. RemoveNullSeparatorParts();
  558. #endif
  559. int targetCount = separatorSlots.Count + 1;
  560. if (targetCount == 1)
  561. return;
  562. #if UNITY_EDITOR
  563. if (Application.isEditor && !Application.isPlaying) {
  564. for (int i = separatorParts.Count-1; i >= 0; --i) {
  565. if (separatorParts[i] == null) {
  566. separatorParts.RemoveAt(i);
  567. }
  568. }
  569. }
  570. #endif
  571. int currentCount = separatorParts.Count;
  572. for (int i = currentCount; i < targetCount; ++i) {
  573. var go = new GameObject(string.Format("{0}[{1}]", SeparatorPartGameObjectName, i), typeof(RectTransform));
  574. go.transform.SetParent(this.transform, false);
  575. go.transform.localPosition = Vector3.zero;
  576. separatorParts.Add(go.transform);
  577. }
  578. }
  579. protected void UpdateSeparatorPartParents () {
  580. int usedCount = separatorSlots.Count + 1;
  581. if (usedCount == 1) {
  582. usedCount = 0; // placed directly at the SkeletonGraphic parent
  583. for (int i = 0; i < canvasRenderers.Count; ++i) {
  584. var canvasRenderer = canvasRenderers[i];
  585. if (canvasRenderer.transform.parent.name.Contains(SeparatorPartGameObjectName)) {
  586. canvasRenderer.transform.SetParent(this.transform, false);
  587. canvasRenderer.transform.localPosition = Vector3.zero;
  588. }
  589. }
  590. }
  591. for (int i = 0; i < separatorParts.Count; ++i) {
  592. bool isUsed = i < usedCount;
  593. separatorParts[i].gameObject.SetActive(isUsed);
  594. }
  595. }
  596. #if UNITY_EDITOR
  597. private void RemoveNullSeparatorParts () {
  598. if (Application.isEditor && !Application.isPlaying) {
  599. for (int i = separatorParts.Count - 1; i >= 0; --i) {
  600. if (separatorParts[i] == null) {
  601. separatorParts.RemoveAt(i);
  602. }
  603. }
  604. }
  605. }
  606. #endif
  607. }
  608. }