/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, 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 #define SPINE_OPTIONAL_RENDEROVERRIDE using UnityEngine; using System.Collections.Generic; namespace Spine.Unity { #if NEW_PREFAB_SYSTEM [ExecuteAlways] #else [ExecuteInEditMode] #endif [HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderSeparator")] public class SkeletonRenderSeparator : MonoBehaviour { public const int DefaultSortingOrderIncrement = 5; #region Inspector [SerializeField] protected SkeletonRenderer skeletonRenderer; public SkeletonRenderer SkeletonRenderer { get { return skeletonRenderer; } set { #if SPINE_OPTIONAL_RENDEROVERRIDE if (skeletonRenderer != null) skeletonRenderer.GenerateMeshOverride -= HandleRender; #endif skeletonRenderer = value; if (value == null) this.enabled = false; } } MeshRenderer mainMeshRenderer; public bool copyPropertyBlock = true; [Tooltip("Copies MeshRenderer flags into each parts renderer")] public bool copyMeshRendererFlags = true; public List partsRenderers = new List(); #if UNITY_EDITOR void Reset () { if (skeletonRenderer == null) skeletonRenderer = GetComponent(); } #endif #endregion #region Callback Delegates /// OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and /// all materials have been updated. public event SkeletonRenderer.SkeletonRendererDelegate OnMeshAndMaterialsUpdated; #endregion #region Runtime Instantiation /// Adds a SkeletonRenderSeparator and child SkeletonPartsRenderer GameObjects to a given SkeletonRenderer. /// The to skeleton renderer. /// The target SkeletonRenderer or SkeletonAnimation. /// Sorting layer to be used for the parts renderers. /// Number of additional SkeletonPartsRenderers on top of the ones determined by counting the number of separator slots. /// The integer to increment the sorting order per SkeletonPartsRenderer to separate them. /// The sorting order value of the first SkeletonPartsRenderer. /// If set to true, a minimum number of SkeletonPartsRenderer GameObjects (determined by separatorSlots.Count + 1) will be added. public static SkeletonRenderSeparator AddToSkeletonRenderer (SkeletonRenderer skeletonRenderer, int sortingLayerID = 0, int extraPartsRenderers = 0, int sortingOrderIncrement = DefaultSortingOrderIncrement, int baseSortingOrder = 0, bool addMinimumPartsRenderers = true) { if (skeletonRenderer == null) { Debug.Log("Tried to add SkeletonRenderSeparator to a null SkeletonRenderer reference."); return null; } var srs = skeletonRenderer.gameObject.AddComponent(); srs.skeletonRenderer = skeletonRenderer; skeletonRenderer.Initialize(false); int count = extraPartsRenderers; if (addMinimumPartsRenderers) count = extraPartsRenderers + skeletonRenderer.separatorSlots.Count + 1; var skeletonRendererTransform = skeletonRenderer.transform; var componentRenderers = srs.partsRenderers; for (int i = 0; i < count; i++) { var spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRendererTransform, i.ToString()); var mr = spr.MeshRenderer; mr.sortingLayerID = sortingLayerID; mr.sortingOrder = baseSortingOrder + (i * sortingOrderIncrement); componentRenderers.Add(spr); } srs.OnEnable(); #if UNITY_EDITOR // Make sure editor updates properly in edit mode. if (!Application.isPlaying) { skeletonRenderer.enabled = false; skeletonRenderer.enabled = true; skeletonRenderer.LateUpdate(); } #endif return srs; } /// Add a child SkeletonPartsRenderer GameObject to this SkeletonRenderSeparator. public SkeletonPartsRenderer AddPartsRenderer (int sortingOrderIncrement = DefaultSortingOrderIncrement, string name = null) { int sortingLayerID = 0; int sortingOrder = 0; if (partsRenderers.Count > 0) { var previous = partsRenderers[partsRenderers.Count - 1]; var previousMeshRenderer = previous.MeshRenderer; sortingLayerID = previousMeshRenderer.sortingLayerID; sortingOrder = previousMeshRenderer.sortingOrder + sortingOrderIncrement; } if (string.IsNullOrEmpty(name)) name = partsRenderers.Count.ToString(); var spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRenderer.transform, name); partsRenderers.Add(spr); var mr = spr.MeshRenderer; mr.sortingLayerID = sortingLayerID; mr.sortingOrder = sortingOrder; return spr; } #endregion public void OnEnable () { if (skeletonRenderer == null) return; if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock(); mainMeshRenderer = skeletonRenderer.GetComponent(); #if SPINE_OPTIONAL_RENDEROVERRIDE skeletonRenderer.GenerateMeshOverride -= HandleRender; skeletonRenderer.GenerateMeshOverride += HandleRender; #endif if (copyMeshRendererFlags) { var lightProbeUsage = mainMeshRenderer.lightProbeUsage; bool receiveShadows = mainMeshRenderer.receiveShadows; var reflectionProbeUsage = mainMeshRenderer.reflectionProbeUsage; var shadowCastingMode = mainMeshRenderer.shadowCastingMode; var motionVectorGenerationMode = mainMeshRenderer.motionVectorGenerationMode; var probeAnchor = mainMeshRenderer.probeAnchor; for (int i = 0; i < partsRenderers.Count; i++) { var currentRenderer = partsRenderers[i]; if (currentRenderer == null) continue; // skip null items. var mr = currentRenderer.MeshRenderer; mr.lightProbeUsage = lightProbeUsage; mr.receiveShadows = receiveShadows; mr.reflectionProbeUsage = reflectionProbeUsage; mr.shadowCastingMode = shadowCastingMode; mr.motionVectorGenerationMode = motionVectorGenerationMode; mr.probeAnchor = probeAnchor; } } } public void OnDisable () { if (skeletonRenderer == null) return; #if SPINE_OPTIONAL_RENDEROVERRIDE skeletonRenderer.GenerateMeshOverride -= HandleRender; #endif skeletonRenderer.LateUpdate(); foreach (var partsRenderer in partsRenderers) { if (partsRenderer != null) partsRenderer.ClearMesh(); } } MaterialPropertyBlock copiedBlock; void HandleRender (SkeletonRendererInstruction instruction) { int rendererCount = partsRenderers.Count; if (rendererCount <= 0) return; if (copyPropertyBlock) mainMeshRenderer.GetPropertyBlock(copiedBlock); var settings = new MeshGenerator.Settings { addNormals = skeletonRenderer.addNormals, calculateTangents = skeletonRenderer.calculateTangents, immutableTriangles = false, // parts cannot do immutable triangles. pmaVertexColors = skeletonRenderer.pmaVertexColors, tintBlack = skeletonRenderer.tintBlack, useClipping = true, zSpacing = skeletonRenderer.zSpacing }; var submeshInstructions = instruction.submeshInstructions; var submeshInstructionsItems = submeshInstructions.Items; int lastSubmeshInstruction = submeshInstructions.Count - 1; int rendererIndex = 0; var currentRenderer = partsRenderers[rendererIndex]; for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) { if (currentRenderer == null) continue; if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) { // Apply properties var meshGenerator = currentRenderer.MeshGenerator; meshGenerator.settings = settings; if (copyPropertyBlock) currentRenderer.SetPropertyBlock(copiedBlock); // Render currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1); start = si + 1; rendererIndex++; if (rendererIndex < rendererCount) { currentRenderer = partsRenderers[rendererIndex]; } else { // Not enough renderers. Skip the rest of the instructions. break; } } } if (OnMeshAndMaterialsUpdated != null) OnMeshAndMaterialsUpdated(this.skeletonRenderer); // Clear extra renderers if they exist. for (; rendererIndex < rendererCount; rendererIndex++) { currentRenderer = partsRenderers[rendererIndex]; if (currentRenderer != null) partsRenderers[rendererIndex].ClearMesh(); } } } }