/****************************************************************************** * Spine Runtimes License Agreement * Last updated September 24, 2021. Replaces all prior versions. * * Copyright (c) 2013-2021, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ #if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER #define NEW_PREFAB_SYSTEM #endif #if UNITY_2018_2_OR_NEWER #define HAS_CULL_TRANSPARENT_MESH #endif using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace Spine.Unity { #if NEW_PREFAB_SYSTEM [ExecuteAlways] #else [ExecuteInEditMode] #endif [RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent] [AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")] [HelpURL("http://esotericsoftware.com/spine-unity#SkeletonGraphic-Component")] public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation, IHasSkeletonDataAsset { #region Inspector public SkeletonDataAsset skeletonDataAsset; public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } public Material additiveMaterial; public Material multiplyMaterial; public Material screenMaterial; [SpineSkin(dataField: "skeletonDataAsset", defaultAsEmptyString: true)] public string initialSkinName; public bool initialFlipX, initialFlipY; [SpineAnimation(dataField: "skeletonDataAsset")] public string startingAnimation; public bool startingLoop; public float timeScale = 1f; public bool freeze; /// Update mode to optionally limit updates to e.g. only apply animations but not update the mesh. public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } } protected UpdateMode updateMode = UpdateMode.FullUpdate; /// Update mode used when the MeshRenderer becomes invisible /// (when OnBecameInvisible() is called). Update mode is automatically /// reset to UpdateMode.FullUpdate when the mesh becomes visible again. public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate; public bool allowMultipleCanvasRenderers = false; public List canvasRenderers = new List(); protected List submeshGraphics = new List(); protected int usedRenderersCount = 0; // Submesh Separation public const string SeparatorPartGameObjectName = "Part"; /// Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing. [SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0]; /// 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. [System.NonSerialized] public readonly List separatorSlots = new List(); public bool enableSeparatorSlots = false; [SerializeField] protected List separatorParts = new List(); public List SeparatorParts { get { return separatorParts; } } public bool updateSeparatorPartLocation = true; private bool wasUpdatedAfterInit = true; private Texture baseTexture = null; #if UNITY_EDITOR protected override void OnValidate () { // This handles Scene View preview. base.OnValidate(); if (this.IsValid) { if (skeletonDataAsset == null) { Clear(); } else if (skeletonDataAsset.skeletonJSON == null) { Clear(); } else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.Data) { Clear(); Initialize(true); if (!allowMultipleCanvasRenderers && (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].MaterialCount > 1)) 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); } else { if (freeze) return; if (!Application.isPlaying) { Initialize(true); return; } if (!string.IsNullOrEmpty(initialSkinName)) { Skin skin = skeleton.Data.FindSkin(initialSkinName); if (skin != null) { if (skin == skeleton.Data.DefaultSkin) skeleton.SetSkin((Skin)null); else skeleton.SetSkin(skin); } } } } else { // Under some circumstances (e.g. sometimes on the first import) OnValidate is called // before SpineEditorUtilities.ImportSpineContent, causing an unnecessary exception. // The (skeletonDataAsset.skeletonJSON != null) condition serves to prevent this exception. if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON != null) Initialize(true); } } protected override void Reset () { base.Reset(); if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic")) Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material."); } #endif #endregion #region Runtime Instantiation /// Create a new GameObject with a SkeletonGraphic component. /// Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work. public static SkeletonGraphic NewSkeletonGraphicGameObject (SkeletonDataAsset skeletonDataAsset, Transform parent, Material material) { SkeletonGraphic sg = SkeletonGraphic.AddSkeletonGraphicComponent(new GameObject("New Spine GameObject"), skeletonDataAsset, material); if (parent != null) sg.transform.SetParent(parent, false); return sg; } /// Add a SkeletonGraphic component to a GameObject. /// Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work. public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) { SkeletonGraphic skeletonGraphic = gameObject.AddComponent(); if (skeletonDataAsset != null) { skeletonGraphic.material = material; skeletonGraphic.skeletonDataAsset = skeletonDataAsset; skeletonGraphic.Initialize(false); } #if HAS_CULL_TRANSPARENT_MESH CanvasRenderer canvasRenderer = gameObject.GetComponent(); if (canvasRenderer) canvasRenderer.cullTransparentMesh = false; #endif return skeletonGraphic; } #endregion #region Overrides // API for taking over rendering. /// When true, no meshes and materials are assigned at CanvasRenderers if the used override /// AssignMeshOverrideSingleRenderer or AssignMeshOverrideMultipleRenderers is non-null. public bool disableMeshAssignmentOnOverride = true; /// Delegate type for overriding mesh and material assignment, /// used when allowMultipleCanvasRenderers is false. /// Mesh normally assigned at the main CanvasRenderer. /// Material normally assigned at the main CanvasRenderer. /// Texture normally assigned at the main CanvasRenderer. public delegate void MeshAssignmentDelegateSingle (Mesh mesh, Material graphicMaterial, Texture texture); /// Number of meshes. Don't use meshes.Length as this might be higher /// due to pre-allocated entries. /// Mesh array where each element is normally assigned to one of the canvasRenderers. /// Material array where each element is normally assigned to one of the canvasRenderers. /// Texture array where each element is normally assigned to one of the canvasRenderers. public delegate void MeshAssignmentDelegateMultiple (int meshCount, Mesh[] meshes, Material[] graphicMaterials, Texture[] textures); event MeshAssignmentDelegateSingle assignMeshOverrideSingle; event MeshAssignmentDelegateMultiple assignMeshOverrideMultiple; /// Allows separate code to take over mesh and material assignment for this SkeletonGraphic component. /// Used when allowMultipleCanvasRenderers is false. public event MeshAssignmentDelegateSingle AssignMeshOverrideSingleRenderer { add { assignMeshOverrideSingle += value; if (disableMeshAssignmentOnOverride && assignMeshOverrideSingle != null) { Initialize(false); } } remove { assignMeshOverrideSingle -= value; if (disableMeshAssignmentOnOverride && assignMeshOverrideSingle == null) { Initialize(false); } } } /// Allows separate code to take over mesh and material assignment for this SkeletonGraphic component. /// Used when allowMultipleCanvasRenderers is true. public event MeshAssignmentDelegateMultiple AssignMeshOverrideMultipleRenderers { add { assignMeshOverrideMultiple += value; if (disableMeshAssignmentOnOverride && assignMeshOverrideMultiple != null) { Initialize(false); } } remove { assignMeshOverrideMultiple -= value; if (disableMeshAssignmentOnOverride && assignMeshOverrideMultiple == null) { Initialize(false); } } } [System.NonSerialized] readonly Dictionary customTextureOverride = new Dictionary(); /// Use this Dictionary to override a Texture with a different Texture. public Dictionary CustomTextureOverride { get { return customTextureOverride; } } [System.NonSerialized] readonly Dictionary customMaterialOverride = new Dictionary(); /// Use this Dictionary to override the Material where the Texture was used at the original atlas. public Dictionary CustomMaterialOverride { get { return customMaterialOverride; } } // This is used by the UI system to determine what to put in the MaterialPropertyBlock. Texture overrideTexture; public Texture OverrideTexture { get { return overrideTexture; } set { overrideTexture = value; canvasRenderer.SetTexture(this.mainTexture); // Refresh canvasRenderer's texture. Make sure it handles null. } } #endregion #region Internals public override Texture mainTexture { get { if (overrideTexture != null) return overrideTexture; return baseTexture; } } protected override void Awake () { base.Awake(); this.onCullStateChanged.AddListener(OnCullStateChanged); SyncSubmeshGraphicsWithCanvasRenderers(); if (!this.IsValid) { #if UNITY_EDITOR // workaround for special import case of open scene where OnValidate and Awake are // called in wrong order, before setup of Spine assets. if (!Application.isPlaying) { if (this.skeletonDataAsset != null && this.skeletonDataAsset.skeletonJSON == null) return; } #endif Initialize(false); Rebuild(CanvasUpdate.PreRender); } } protected override void OnDestroy () { Clear(); base.OnDestroy(); } public override void Rebuild (CanvasUpdate update) { base.Rebuild(update); if (canvasRenderer.cull) return; if (update == CanvasUpdate.PreRender) UpdateMeshToInstructions(); if (allowMultipleCanvasRenderers) canvasRenderer.Clear(); } protected override void OnDisable () { base.OnDisable(); foreach (CanvasRenderer canvasRenderer in canvasRenderers) { canvasRenderer.Clear(); } } public virtual void Update () { #if UNITY_EDITOR if (!Application.isPlaying) { Update(0f); return; } #endif if (freeze || updateTiming != UpdateTiming.InUpdate) return; Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime); } virtual protected void FixedUpdate () { if (freeze || updateTiming != UpdateTiming.InFixedUpdate) return; Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime); } public virtual void Update (float deltaTime) { if (!this.IsValid) return; wasUpdatedAfterInit = true; if (updateMode < UpdateMode.OnlyAnimationStatus) return; UpdateAnimationStatus(deltaTime); if (updateMode == UpdateMode.OnlyAnimationStatus) { state.ApplyEventTimelinesOnly(skeleton, issueEvents: false); return; } ApplyAnimation(); } protected void SyncSubmeshGraphicsWithCanvasRenderers () { submeshGraphics.Clear(); #if UNITY_EDITOR if (!Application.isPlaying) DestroyOldRawImages(); #endif foreach (CanvasRenderer canvasRenderer in canvasRenderers) { SkeletonSubmeshGraphic submeshGraphic = canvasRenderer.GetComponent(); if (submeshGraphic == null) { submeshGraphic = canvasRenderer.gameObject.AddComponent(); submeshGraphic.maskable = this.maskable; submeshGraphic.raycastTarget = false; } submeshGraphics.Add(submeshGraphic); } } protected void UpdateAnimationStatus (float deltaTime) { deltaTime *= timeScale; state.Update(deltaTime); } protected void ApplyAnimation () { if (BeforeApply != null) BeforeApply(this); if (updateMode != UpdateMode.OnlyEventTimelines) state.Apply(skeleton); else state.ApplyEventTimelinesOnly(skeleton, issueEvents: true); if (UpdateLocal != null) UpdateLocal(this); skeleton.UpdateWorldTransform(); if (UpdateWorld != null) { UpdateWorld(this); skeleton.UpdateWorldTransform(); } if (UpdateComplete != null) UpdateComplete(this); } public void LateUpdate () { // instantiation can happen from Update() after this component, leading to a missing Update() call. if (!wasUpdatedAfterInit) Update(0); if (freeze) return; if (updateMode != UpdateMode.FullUpdate) return; PrepareInstructionsAndRenderers(); if (OnInstructionsPrepared != null) OnInstructionsPrepared(this.currentInstructions); SetVerticesDirty(); // triggers Rebuild and avoids potential double-update in a single frame } protected void OnCullStateChanged (bool culled) { if (culled) OnBecameInvisible(); else OnBecameVisible(); } public void OnBecameVisible () { updateMode = UpdateMode.FullUpdate; } public void OnBecameInvisible () { updateMode = updateWhenInvisible; } public void ReapplySeparatorSlotNames () { if (!IsValid) return; separatorSlots.Clear(); for (int i = 0, n = separatorSlotNames.Length; i < n; i++) { string slotName = separatorSlotNames[i]; if (slotName == "") continue; Slot slot = skeleton.FindSlot(slotName); if (slot != null) { separatorSlots.Add(slot); } #if UNITY_EDITOR else { Debug.LogWarning(slotName + " is not a slot in " + skeletonDataAsset.skeletonJSON.name); } #endif } UpdateSeparatorPartParents(); } #endregion #region API protected Skeleton skeleton; public Skeleton Skeleton { get { Initialize(false); return skeleton; } set { skeleton = value; } } public SkeletonData SkeletonData { get { Initialize(false); return skeleton == null ? null : skeleton.Data; } } public bool IsValid { get { return skeleton != null; } } public delegate void SkeletonRendererDelegate (SkeletonGraphic skeletonGraphic); public delegate void InstructionDelegate (SkeletonRendererInstruction instruction); /// OnRebuild is raised after the Skeleton is successfully initialized. public event SkeletonRendererDelegate OnRebuild; /// OnInstructionsPrepared is raised at the end of LateUpdate after render instructions /// are done, target renderers are prepared, and the mesh is ready to be generated. public event InstructionDelegate OnInstructionsPrepared; /// OnMeshAndMaterialsUpdated is raised at the end of Rebuild after the Mesh and /// all materials have been updated. Note that some Unity API calls are not permitted to be issued from /// Rebuild, so you may want to subscribe to instead /// from where you can issue such preparation calls. public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated; protected Spine.AnimationState state; public Spine.AnimationState AnimationState { get { Initialize(false); return state; } } [SerializeField] protected Spine.Unity.MeshGenerator meshGenerator = new MeshGenerator(); public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } } DoubleBuffered meshBuffers; SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction(); readonly ExposedList meshes = new ExposedList(); readonly ExposedList usedMaterials = new ExposedList(); readonly ExposedList usedTextures = new ExposedList(); public ExposedList MeshesMultipleCanvasRenderers { get { return meshes; } } public ExposedList MaterialsMultipleCanvasRenderers { get { return usedMaterials; } } public ExposedList TexturesMultipleCanvasRenderers { get { return usedTextures; } } public Mesh GetLastMesh () { return meshBuffers.GetCurrent().mesh; } public bool MatchRectTransformWithBounds () { if (!wasUpdatedAfterInit) Update(0); UpdateMesh(); if (!this.allowMultipleCanvasRenderers) return MatchRectTransformSingleRenderer(); else return MatchRectTransformMultipleRenderers(); } protected bool MatchRectTransformSingleRenderer () { Mesh mesh = this.GetLastMesh(); if (mesh == null) { return false; } if (mesh.vertexCount == 0) { this.rectTransform.sizeDelta = new Vector2(50f, 50f); this.rectTransform.pivot = new Vector2(0.5f, 0.5f); return false; } mesh.RecalculateBounds(); SetRectTransformBounds(mesh.bounds); return true; } protected bool MatchRectTransformMultipleRenderers () { bool anyBoundsAdded = false; Bounds combinedBounds = new Bounds(); for (int i = 0; i < canvasRenderers.Count; ++i) { CanvasRenderer canvasRenderer = canvasRenderers[i]; if (!canvasRenderer.gameObject.activeSelf) continue; Mesh mesh = meshes.Items[i]; if (mesh == null || mesh.vertexCount == 0) continue; mesh.RecalculateBounds(); Bounds bounds = mesh.bounds; if (anyBoundsAdded) combinedBounds.Encapsulate(bounds); else { anyBoundsAdded = true; combinedBounds = bounds; } } if (!anyBoundsAdded) { this.rectTransform.sizeDelta = new Vector2(50f, 50f); this.rectTransform.pivot = new Vector2(0.5f, 0.5f); return false; } SetRectTransformBounds(combinedBounds); return true; } private void SetRectTransformBounds (Bounds combinedBounds) { Vector3 size = combinedBounds.size; Vector3 center = combinedBounds.center; Vector2 p = new Vector2( 0.5f - (center.x / size.x), 0.5f - (center.y / size.y) ); this.rectTransform.sizeDelta = size; this.rectTransform.pivot = p; foreach (SkeletonSubmeshGraphic submeshGraphic in submeshGraphics) { submeshGraphic.rectTransform.sizeDelta = size; submeshGraphic.rectTransform.pivot = p; } } /// OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized. public event ISkeletonAnimationDelegate OnAnimationRebuild; public event UpdateBonesDelegate BeforeApply; public event UpdateBonesDelegate UpdateLocal; public event UpdateBonesDelegate UpdateWorld; public event UpdateBonesDelegate UpdateComplete; [SerializeField] protected UpdateTiming updateTiming = UpdateTiming.InUpdate; public UpdateTiming UpdateTiming { get { return updateTiming; } set { updateTiming = value; } } [SerializeField] protected bool unscaledTime; public bool UnscaledTime { get { return unscaledTime; } set { unscaledTime = value; } } /// Occurs after the vertex data populated every frame, before the vertices are pushed into the mesh. public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices; public void Clear () { skeleton = null; canvasRenderer.Clear(); for (int i = 0; i < canvasRenderers.Count; ++i) canvasRenderers[i].Clear(); DestroyMeshes(); usedMaterials.Clear(); usedTextures.Clear(); DisposeMeshBuffers(); } public void TrimRenderers () { List newList = new List(); foreach (CanvasRenderer canvasRenderer in canvasRenderers) { if (canvasRenderer.gameObject.activeSelf) { newList.Add(canvasRenderer); } else { if (Application.isEditor && !Application.isPlaying) DestroyImmediate(canvasRenderer.gameObject); else Destroy(canvasRenderer.gameObject); } } canvasRenderers = newList; SyncSubmeshGraphicsWithCanvasRenderers(); } public void Initialize (bool overwrite) { if (this.IsValid && !overwrite) return; #if UNITY_EDITOR if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing) return; #endif if (this.skeletonDataAsset == null) return; SkeletonData skeletonData = this.skeletonDataAsset.GetSkeletonData(false); if (skeletonData == null) return; if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].MaterialCount <= 0) return; this.skeleton = new Skeleton(skeletonData) { ScaleX = this.initialFlipX ? -1 : 1, ScaleY = this.initialFlipY ? -1 : 1 }; InitMeshBuffers(); baseTexture = skeletonDataAsset.atlasAssets[0].PrimaryMaterial.mainTexture; canvasRenderer.SetTexture(this.mainTexture); // Needed for overwriting initializations. // Set the initial Skin and Animation if (!string.IsNullOrEmpty(initialSkinName)) skeleton.SetSkin(initialSkinName); separatorSlots.Clear(); for (int i = 0; i < separatorSlotNames.Length; i++) separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i])); if (OnRebuild != null) OnRebuild(this); wasUpdatedAfterInit = false; this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData()); if (state == null) { Clear(); return; } if (!string.IsNullOrEmpty(startingAnimation)) { Spine.Animation animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation); if (animationObject != null) { state.SetAnimation(0, animationObject, startingLoop); #if UNITY_EDITOR if (!Application.isPlaying) Update(0f); #endif } } if (OnAnimationRebuild != null) OnAnimationRebuild(this); } public void PrepareInstructionsAndRenderers () { if (!this.allowMultipleCanvasRenderers) { MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null); if (canvasRenderers.Count > 0) DisableUnusedCanvasRenderers(usedCount: 0); usedRenderersCount = 0; } else { MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null, enableSeparatorSlots ? separatorSlots : null, enableSeparatorSlots ? separatorSlots.Count > 0 : false, false); int submeshCount = currentInstructions.submeshInstructions.Count; EnsureCanvasRendererCount(submeshCount); EnsureMeshesCount(submeshCount); EnsureUsedTexturesAndMaterialsCount(submeshCount); EnsureSeparatorPartCount(); PrepareRendererGameObjects(currentInstructions); } } public void UpdateMesh () { PrepareInstructionsAndRenderers(); UpdateMeshToInstructions(); } public void UpdateMeshToInstructions () { if (!this.IsValid || currentInstructions.rawVertexCount < 0) return; skeleton.SetColor(this.color); if (!this.allowMultipleCanvasRenderers) { UpdateMeshSingleCanvasRenderer(currentInstructions); } else { UpdateMaterialsMultipleCanvasRenderers(currentInstructions); UpdateMeshMultipleCanvasRenderers(currentInstructions); } if (OnMeshAndMaterialsUpdated != null) OnMeshAndMaterialsUpdated(this); } public bool HasMultipleSubmeshInstructions () { if (!IsValid) return false; return MeshGenerator.RequiresMultipleSubmeshesByDrawOrder(skeleton); } #endregion protected void InitMeshBuffers () { if (meshBuffers != null) { meshBuffers.GetNext().Clear(); meshBuffers.GetNext().Clear(); } else { meshBuffers = new DoubleBuffered(); } } protected void DisposeMeshBuffers () { if (meshBuffers != null) { meshBuffers.GetNext().Dispose(); meshBuffers.GetNext().Dispose(); meshBuffers = null; } } protected void UpdateMeshSingleCanvasRenderer (SkeletonRendererInstruction currentInstructions) { MeshRendererBuffers.SmartMesh smartMesh = meshBuffers.GetNext(); bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed); meshGenerator.Begin(); bool useAddSubmesh = currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0; if (useAddSubmesh) { meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles); } else { meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles); } if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit); if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers); Mesh mesh = smartMesh.mesh; meshGenerator.FillVertexData(mesh); if (updateTriangles) meshGenerator.FillTriangles(mesh); meshGenerator.FillLateVertexData(mesh); smartMesh.instructionUsed.Set(currentInstructions); if (assignMeshOverrideSingle != null) assignMeshOverrideSingle(mesh, this.canvasRenderer.GetMaterial(), this.mainTexture); bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride); if (assignAtCanvasRenderer) canvasRenderer.SetMesh(mesh); else canvasRenderer.SetMesh(null); if (currentInstructions.submeshInstructions.Count > 0) { Material material = currentInstructions.submeshInstructions.Items[0].material; if (material != null && baseTexture != material.mainTexture) { baseTexture = material.mainTexture; if (overrideTexture == null && assignAtCanvasRenderer) canvasRenderer.SetTexture(this.mainTexture); } } } protected void UpdateMaterialsMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) { int submeshCount = currentInstructions.submeshInstructions.Count; bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0); BlendModeMaterials blendModeMaterials = skeletonDataAsset.blendModeMaterials; bool hasBlendModeMaterials = blendModeMaterials.RequiresBlendModeMaterials; bool pmaVertexColors = meshGenerator.settings.pmaVertexColors; Material[] usedMaterialItems = usedMaterials.Items; Texture[] usedTextureItems = usedTextures.Items; for (int i = 0; i < submeshCount; i++) { SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i]; Material submeshMaterial = submeshInstructionItem.material; if (useOriginalTextureAndMaterial) { usedTextureItems[i] = submeshMaterial.mainTexture; if (!hasBlendModeMaterials) { usedMaterialItems[i] = this.materialForRendering; } else { BlendMode blendMode = blendModeMaterials.BlendModeForMaterial(submeshMaterial); Material usedMaterial = this.materialForRendering; if (blendMode == BlendMode.Additive && !pmaVertexColors && additiveMaterial) { usedMaterial = additiveMaterial; } else if (blendMode == BlendMode.Multiply && multiplyMaterial) usedMaterial = multiplyMaterial; else if (blendMode == BlendMode.Screen && screenMaterial) usedMaterial = screenMaterial; usedMaterialItems[i] = submeshGraphics[i].GetModifiedMaterial(usedMaterial); } } else { Texture originalTexture = submeshMaterial.mainTexture; Material usedMaterial; Texture usedTexture; if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial)) usedMaterial = material; if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture)) usedTexture = originalTexture; usedMaterialItems[i] = submeshGraphics[i].GetModifiedMaterial(usedMaterial); usedTextureItems[i] = usedTexture; } } } protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions) { Canvas c = canvas; float scale = (c == null) ? 100 : c.referencePixelsPerUnit; // Generate meshes. int submeshCount = currentInstructions.submeshInstructions.Count; Mesh[] meshesItems = meshes.Items; bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0); BlendModeMaterials blendModeMaterials = skeletonDataAsset.blendModeMaterials; bool hasBlendModeMaterials = blendModeMaterials.RequiresBlendModeMaterials; #if HAS_CULL_TRANSPARENT_MESH bool mainCullTransparentMesh = this.canvasRenderer.cullTransparentMesh; #endif bool pmaVertexColors = meshGenerator.settings.pmaVertexColors; Material[] usedMaterialItems = usedMaterials.Items; Texture[] usedTextureItems = usedTextures.Items; bool assignAtCanvasRenderer = (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride); for (int i = 0; i < submeshCount; i++) { SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i]; meshGenerator.Begin(); meshGenerator.AddSubmesh(submeshInstructionItem); Mesh targetMesh = meshesItems[i]; meshGenerator.ScaleVertexData(scale); if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers); meshGenerator.FillVertexData(targetMesh); meshGenerator.FillTriangles(targetMesh); meshGenerator.FillLateVertexData(targetMesh); CanvasRenderer canvasRenderer = canvasRenderers[i]; if (assignMeshOverrideSingle == null || !disableMeshAssignmentOnOverride) canvasRenderer.SetMesh(targetMesh); else canvasRenderer.SetMesh(null); SkeletonSubmeshGraphic submeshGraphic = submeshGraphics[i]; if (useOriginalTextureAndMaterial && hasBlendModeMaterials) { bool allowCullTransparentMesh = true; BlendMode materialBlendMode = blendModeMaterials.BlendModeForMaterial(usedMaterialItems[i]); if ((materialBlendMode == BlendMode.Normal && submeshInstructionItem.hasPMAAdditiveSlot) || (materialBlendMode == BlendMode.Additive && pmaVertexColors)) { allowCullTransparentMesh = false; } #if HAS_CULL_TRANSPARENT_MESH canvasRenderer.cullTransparentMesh = allowCullTransparentMesh ? mainCullTransparentMesh : false; #endif } canvasRenderer.materialCount = 1; if (assignAtCanvasRenderer) canvasRenderer.SetMaterial(usedMaterialItems[i], usedTextureItems[i]); } if (assignMeshOverrideMultiple != null) assignMeshOverrideMultiple(submeshCount, meshesItems, usedMaterialItems, usedTextureItems); } protected void EnsureCanvasRendererCount (int targetCount) { #if UNITY_EDITOR RemoveNullCanvasRenderers(); #endif int currentCount = canvasRenderers.Count; for (int i = currentCount; i < targetCount; ++i) { GameObject go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform)); go.transform.SetParent(this.transform, false); go.transform.localPosition = Vector3.zero; CanvasRenderer canvasRenderer = go.AddComponent(); canvasRenderers.Add(canvasRenderer); SkeletonSubmeshGraphic submeshGraphic = go.AddComponent(); submeshGraphic.maskable = this.maskable; submeshGraphic.raycastTarget = false; submeshGraphics.Add(submeshGraphic); } } protected void PrepareRendererGameObjects (SkeletonRendererInstruction currentInstructions) { int submeshCount = currentInstructions.submeshInstructions.Count; DisableUnusedCanvasRenderers(usedCount: submeshCount); int separatorSlotGroupIndex = 0; int targetSiblingIndex = 0; Transform parent = this.separatorSlots.Count == 0 ? this.transform : this.separatorParts[0]; if (updateSeparatorPartLocation) { for (int p = 0; p < this.separatorParts.Count; ++p) { separatorParts[p].position = this.transform.position; separatorParts[p].rotation = this.transform.rotation; } } for (int i = 0; i < submeshCount; i++) { CanvasRenderer canvasRenderer = canvasRenderers[i]; if (i >= usedRenderersCount) { canvasRenderer.gameObject.SetActive(true); } if (canvasRenderer.transform.parent != parent.transform) { canvasRenderer.transform.SetParent(parent.transform, false); canvasRenderer.transform.localPosition = Vector3.zero; } canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++); SubmeshInstruction submeshInstructionItem = currentInstructions.submeshInstructions.Items[i]; if (submeshInstructionItem.forceSeparate) { targetSiblingIndex = 0; parent = separatorParts[++separatorSlotGroupIndex]; } } usedRenderersCount = submeshCount; } protected void DisableUnusedCanvasRenderers (int usedCount) { #if UNITY_EDITOR RemoveNullCanvasRenderers(); #endif for (int i = usedCount; i < canvasRenderers.Count; i++) { canvasRenderers[i].Clear(); canvasRenderers[i].gameObject.SetActive(false); } } #if UNITY_EDITOR private void RemoveNullCanvasRenderers () { if (Application.isEditor && !Application.isPlaying) { for (int i = canvasRenderers.Count - 1; i >= 0; --i) { if (canvasRenderers[i] == null) { canvasRenderers.RemoveAt(i); } } } } private void DestroyOldRawImages () { foreach (CanvasRenderer canvasRenderer in canvasRenderers) { RawImage oldRawImage = canvasRenderer.GetComponent(); if (oldRawImage != null) { DestroyImmediate(oldRawImage); } } } #endif protected void EnsureMeshesCount (int targetCount) { int oldCount = meshes.Count; meshes.EnsureCapacity(targetCount); for (int i = oldCount; i < targetCount; i++) meshes.Add(SpineMesh.NewSkeletonMesh()); } protected void EnsureUsedTexturesAndMaterialsCount (int targetCount) { int oldCount = usedMaterials.Count; usedMaterials.EnsureCapacity(targetCount); usedTextures.EnsureCapacity(targetCount); for (int i = oldCount; i < targetCount; i++) { usedMaterials.Add(null); usedTextures.Add(null); } } protected void DestroyMeshes () { foreach (Mesh mesh in meshes) { #if UNITY_EDITOR if (Application.isEditor && !Application.isPlaying) UnityEngine.Object.DestroyImmediate(mesh); else UnityEngine.Object.Destroy(mesh); #else UnityEngine.Object.Destroy(mesh); #endif } meshes.Clear(); } protected void EnsureSeparatorPartCount () { #if UNITY_EDITOR RemoveNullSeparatorParts(); #endif int targetCount = separatorSlots.Count + 1; if (targetCount == 1) return; #if UNITY_EDITOR if (Application.isEditor && !Application.isPlaying) { for (int i = separatorParts.Count - 1; i >= 0; --i) { if (separatorParts[i] == null) { separatorParts.RemoveAt(i); } } } #endif int currentCount = separatorParts.Count; for (int i = currentCount; i < targetCount; ++i) { GameObject go = new GameObject(string.Format("{0}[{1}]", SeparatorPartGameObjectName, i), typeof(RectTransform)); go.transform.SetParent(this.transform, false); go.transform.localPosition = Vector3.zero; separatorParts.Add(go.transform); } } protected void UpdateSeparatorPartParents () { int usedCount = separatorSlots.Count + 1; if (usedCount == 1) { usedCount = 0; // placed directly at the SkeletonGraphic parent for (int i = 0; i < canvasRenderers.Count; ++i) { CanvasRenderer canvasRenderer = canvasRenderers[i]; if (canvasRenderer.transform.parent.name.Contains(SeparatorPartGameObjectName)) { canvasRenderer.transform.SetParent(this.transform, false); canvasRenderer.transform.localPosition = Vector3.zero; } } } for (int i = 0; i < separatorParts.Count; ++i) { bool isUsed = i < usedCount; separatorParts[i].gameObject.SetActive(isUsed); } } #if UNITY_EDITOR private void RemoveNullSeparatorParts () { if (Application.isEditor && !Application.isPlaying) { for (int i = separatorParts.Count - 1; i >= 0; --i) { if (separatorParts[i] == null) { separatorParts.RemoveAt(i); } } } } #endif } }