#pragma warning disable 0168 // variable declared but not used. #if ENABLE_ANIMATION_COLLECTION && ENABLE_ANIMATION_BURST #define ENABLE_SPRITESKIN_COMPOSITE #endif using System; using System.Collections.Generic; using UnityEngine.Scripting; using UnityEngine.U2D.Common; using Unity.Collections; using UnityEngine.Rendering; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.U2D.Animation { public struct PositionVertex { public Vector3 position; } public struct PositionTangentVertex { public Vector3 position; public Vector4 tangent; } struct DeformVerticesBuffer { public const int k_DefaultBufferSize = 2; int m_BufferCount; int m_CurrentBuffer; NativeArray[] m_DeformedVertices; public DeformVerticesBuffer(int bufferCount) { m_BufferCount = bufferCount; m_DeformedVertices = new NativeArray[m_BufferCount]; for (int i = 0; i < m_BufferCount; ++i) { m_DeformedVertices[i] = new NativeArray(1, Allocator.Persistent); } m_CurrentBuffer = 0; } public void Dispose() { for (int i = 0; i < m_BufferCount; ++i) { if (m_DeformedVertices[i].IsCreated) m_DeformedVertices[i].Dispose(); } } public ref NativeArray GetBuffer(int expectedSize) { m_CurrentBuffer = (m_CurrentBuffer + 1) % m_BufferCount; if (m_DeformedVertices[m_CurrentBuffer].IsCreated && m_DeformedVertices[m_CurrentBuffer].Length != expectedSize) { m_DeformedVertices[m_CurrentBuffer].Dispose(); m_DeformedVertices[m_CurrentBuffer] = new NativeArray(expectedSize, Allocator.Persistent); } return ref m_DeformedVertices[m_CurrentBuffer]; } internal ref NativeArray GetCurrentBuffer() { return ref m_DeformedVertices[m_CurrentBuffer]; } } /// /// Deforms the Sprite that is currently assigned to the SpriteRenderer in the same GameObject /// [Preserve] [ExecuteInEditMode] [DefaultExecutionOrder(-1)] [DisallowMultipleComponent] [RequireComponent(typeof(SpriteRenderer))] [AddComponentMenu("2D Animation/Sprite Skin")] [MovedFrom("UnityEngine.U2D.Experimental.Animation")] [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/index.html%23sprite-skin-component")] public sealed partial class SpriteSkin : MonoBehaviour, ISerializationCallbackReceiver { [SerializeField] private Transform m_RootBone; [SerializeField] private Transform[] m_BoneTransforms = new Transform[0]; [SerializeField] private Bounds m_Bounds; [SerializeField] private bool m_UseBatching = true; [SerializeField] private bool m_AlwaysUpdate = true; [SerializeField] private bool m_AutoRebind = false; // The deformed m_SpriteVertices stores all 'HOT' channels only in single-stream and essentially depends on Sprite Asset data. // The order of storage if present is POSITION, NORMALS, TANGENTS. private DeformVerticesBuffer m_DeformedVertices; private int m_CurrentDeformVerticesLength = 0; private SpriteRenderer m_SpriteRenderer; private int m_CurrentDeformSprite = 0; private bool m_ForceSkinning; private bool m_BatchSkinning = false; bool m_IsValid = false; int m_TransformsHash = 0; internal bool batchSkinning { get { return m_BatchSkinning; } set { m_BatchSkinning = value; } } internal bool autoRebind { get => m_AutoRebind; set { m_AutoRebind = value; CacheCurrentSprite(m_AutoRebind); } } #if UNITY_EDITOR internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent(); private void OnDrawGizmos() { onDrawGizmos.Invoke(); } private bool m_IgnoreNextSpriteChange = true; internal bool ignoreNextSpriteChange { get { return m_IgnoreNextSpriteChange; } set { m_IgnoreNextSpriteChange = value; } } #endif private int GetSpriteInstanceID() { return sprite != null ? sprite.GetInstanceID() : 0; } internal void Awake() { m_SpriteRenderer = GetComponent(); } void OnEnable() { Awake(); m_TransformsHash = 0; CacheCurrentSprite(false); OnEnableBatch(); m_DeformedVertices = new DeformVerticesBuffer(DeformVerticesBuffer.k_DefaultBufferSize); } internal void OnEditorEnable() { Awake(); } void CacheValidFlag() { m_IsValid = isValid; if(!m_IsValid) DeactivateSkinning(); } void Reset() { Awake(); if (isActiveAndEnabled) { CacheValidFlag(); OnResetBatch(); } } internal void UseBatching(bool value) { if (m_UseBatching != value) { m_UseBatching = value; UseBatchingBatch(); } } internal ref NativeArray GetDeformedVertices(int spriteVertexCount) { if (sprite != null) { if (m_CurrentDeformVerticesLength != spriteVertexCount) { m_TransformsHash = 0; m_CurrentDeformVerticesLength = spriteVertexCount; } } else { m_CurrentDeformVerticesLength = 0; } return ref m_DeformedVertices.GetBuffer(m_CurrentDeformVerticesLength); } /// /// Returns whether this SpriteSkin has currently deformed vertices. /// /// Returns true if this SpriteSkin has currently deformed vertices. Returns false otherwise. public bool HasCurrentDeformedVertices() { if (!m_IsValid) return false; #if ENABLE_SPRITESKIN_COMPOSITE return m_DataIndex >= 0 && SpriteSkinComposite.instance.HasDeformableBufferForSprite(m_DataIndex); #else return m_CurrentDeformVerticesLength > 0 && m_DeformedVertices.GetCurrentBuffer().IsCreated; #endif } /// /// Gets a byte array to the currently deformed vertices for this SpriteSkin. /// /// Returns a reference to the currently deformed vertices. This is valid only for this calling frame. /// Thrown when there are no currently deformed vertices internal NativeArray GetCurrentDeformedVertices() { if (!m_IsValid) throw new InvalidOperationException("The SpriteSkin deformation is not valid."); #if ENABLE_SPRITESKIN_COMPOSITE if (m_DataIndex < 0) { throw new InvalidOperationException("There are no currently deformed vertices."); } return SpriteSkinComposite.instance.GetDeformableBufferForSprite(m_DataIndex); #else if (m_CurrentDeformVerticesLength <= 0) throw new InvalidOperationException("There are no currently deformed vertices."); var buffer = m_DeformedVertices.GetCurrentBuffer(); if (!buffer.IsCreated) throw new InvalidOperationException("There are no currently deformed vertices."); return buffer; #endif } /// /// Gets an array of currently deformed position vertices for this SpriteSkin. /// /// Returns a reference to the currently deformed vertices. This is valid only for this calling frame. /// /// Thrown when there are no currently deformed vertices or if the deformed vertices does not contain only /// position data. /// internal NativeSlice GetCurrentDeformedVertexPositions() { if (sprite.HasVertexAttribute(VertexAttribute.Tangent)) throw new InvalidOperationException("This SpriteSkin has deformed tangents"); if (!sprite.HasVertexAttribute(VertexAttribute.Position)) throw new InvalidOperationException("This SpriteSkin does not have deformed positions."); var deformedBuffer = GetCurrentDeformedVertices(); return deformedBuffer.Slice().SliceConvert(); } /// /// Gets an array of currently deformed position and tangent vertices for this SpriteSkin. /// /// /// Returns a reference to the currently deformed position and tangent vertices. This is valid only for this calling frame. /// /// /// Thrown when there are no currently deformed vertices or if the deformed vertices does not contain only /// position and tangent data. /// internal NativeSlice GetCurrentDeformedVertexPositionsAndTangents() { if (!sprite.HasVertexAttribute(VertexAttribute.Tangent)) throw new InvalidOperationException("This SpriteSkin does not have deformed tangents"); if (!sprite.HasVertexAttribute(VertexAttribute.Position)) throw new InvalidOperationException("This SpriteSkin does not have deformed positions."); var deformedBuffer = GetCurrentDeformedVertices(); return deformedBuffer.Slice().SliceConvert(); } /// /// Gets an enumerable to iterate through all deformed vertex positions of this SpriteSkin. /// /// Returns an IEnumerable to deformed vertex positions. /// Thrown when there is no vertex positions or deformed vertices. public IEnumerable GetDeformedVertexPositionData() { bool hasPosition = sprite.HasVertexAttribute(Rendering.VertexAttribute.Position); if (!hasPosition) throw new InvalidOperationException("Sprite does not have vertex position data."); var rawBuffer = GetCurrentDeformedVertices(); var rawSlice = rawBuffer.Slice(sprite.GetVertexStreamOffset(VertexAttribute.Position)); return new NativeCustomSliceEnumerator(rawSlice, sprite.GetVertexCount(), sprite.GetVertexStreamSize()); } /// /// Gets an enumerable to iterate through all deformed vertex tangents of this SpriteSkin. /// /// Returns an IEnumerable to deformed vertex tangents. /// Thrown when there is no vertex tangents or deformed vertices. public IEnumerable GetDeformedVertexTangentData() { bool hasTangent = sprite.HasVertexAttribute(Rendering.VertexAttribute.Tangent); if (!hasTangent) throw new InvalidOperationException("Sprite does not have vertex tangent data."); var rawBuffer = GetCurrentDeformedVertices(); var rawSlice = rawBuffer.Slice(sprite.GetVertexStreamOffset(VertexAttribute.Tangent)); return new NativeCustomSliceEnumerator(rawSlice, sprite.GetVertexCount(), sprite.GetVertexStreamSize()); } void OnDisable() { DeactivateSkinning(); m_DeformedVertices.Dispose(); OnDisableBatch(); } #if ENABLE_SPRITESKIN_COMPOSITE internal void OnLateUpdate() #else void LateUpdate() #endif { CacheCurrentSprite(m_AutoRebind); if (isValid && !batchSkinning && this.enabled && (this.alwaysUpdate || this.spriteRenderer.isVisible)) { var transformHash = SpriteSkinUtility.CalculateTransformHash(this); var spriteVertexCount = sprite.GetVertexStreamSize() * sprite.GetVertexCount(); if (spriteVertexCount > 0 && m_TransformsHash != transformHash) { var inputVertices = GetDeformedVertices(spriteVertexCount); SpriteSkinUtility.Deform(sprite, gameObject.transform.worldToLocalMatrix, boneTransforms, ref inputVertices); SpriteSkinUtility.UpdateBounds(this, inputVertices); InternalEngineBridge.SetDeformableBuffer(spriteRenderer, inputVertices); m_TransformsHash = transformHash; m_CurrentDeformSprite = GetSpriteInstanceID(); } } } void CacheCurrentSprite(bool rebind) { if (m_CurrentDeformSprite != GetSpriteInstanceID()) { DeactivateSkinning(); m_CurrentDeformSprite = GetSpriteInstanceID(); if (rebind && m_CurrentDeformSprite > 0 && rootBone != null) { var spriteBones = sprite.GetBones(); var transforms = new Transform[spriteBones.Length]; if (GetSpriteBonesTransforms(spriteBones, rootBone, transforms)) boneTransforms = transforms; } UpdateSpriteDeform(); CacheValidFlag(); m_TransformsHash = 0; } } internal Sprite sprite => spriteRenderer.sprite; internal SpriteRenderer spriteRenderer => m_SpriteRenderer; /// /// Returns the Transform Components that is used for deformation /// /// An array of Transform Components public Transform[] boneTransforms { get { return m_BoneTransforms; } internal set { m_BoneTransforms = value; CacheValidFlag(); OnBoneTransformChanged(); } } /// /// Returns the Transform Component that represents the root bone for deformation /// /// A Transform Component public Transform rootBone { get { return m_RootBone; } internal set { m_RootBone = value; CacheValidFlag(); OnRootBoneTransformChanged(); } } internal Bounds bounds { get { return m_Bounds; } set { m_Bounds = value; } } /// /// Determines if the SpriteSkin executes even if the associated /// SpriteRenderer has been culled from view. /// public bool alwaysUpdate { get => m_AlwaysUpdate; set => m_AlwaysUpdate = value; } internal static bool GetSpriteBonesTransforms(SpriteBone[] spriteBones, Transform rootBone, Transform[] outTransform) { if(rootBone == null) throw new ArgumentException("rootBone parameter cannot be null"); if(spriteBones == null) throw new ArgumentException("spritebone parameter cannot be null"); if(outTransform == null) throw new ArgumentException("outTransform parameter cannot be null"); if(spriteBones.Length != outTransform.Length) throw new ArgumentException("spritebone and outTransform array length must be the same"); var boneObjects = rootBone.GetComponentsInChildren(); if (boneObjects != null && boneObjects.Length >= spriteBones.Length) { int i = 0; for (; i < spriteBones.Length; ++i) { var boneHash = spriteBones[i].guid; var boneTransform = Array.Find(boneObjects, x => (x.guid == boneHash)); if (boneTransform == null) break; outTransform[i] = boneTransform.transform; } if(i >= spriteBones.Length) return true; } // If unable to successfuly map via guid, fall back to path return GetSpriteBonesTranformFromPath(spriteBones, rootBone, outTransform); } static bool GetSpriteBonesTranformFromPath(SpriteBone[] spriteBones, Transform rootBone, Transform[] outNewBoneTransform) { var bonePath = new string[spriteBones.Length]; for (int i = 0; i < spriteBones.Length; ++i) { if (bonePath[i] == null) CalculateBoneTransformsPath(i, spriteBones, bonePath); if (rootBone.name == spriteBones[i].name) outNewBoneTransform[i] = rootBone; else { var bone = rootBone.Find(bonePath[i]); if (bone == null) return false; outNewBoneTransform[i] = bone; } } return true; } private static void CalculateBoneTransformsPath(int index, SpriteBone[] spriteBones, string[] paths) { var spriteBone = spriteBones[index]; var parentId = spriteBone.parentId; var bonePath = spriteBone.name; if (parentId != -1 && spriteBones[parentId].parentId != -1) { if (paths[parentId] == null) CalculateBoneTransformsPath(spriteBone.parentId, spriteBones, paths); paths[index] = string.Format("{0}/{1}", paths[parentId], bonePath); } else paths[index] = bonePath; } internal bool isValid { get { return this.Validate() == SpriteSkinValidationResult.Ready; } } void OnDestroy() { DeactivateSkinning(); } internal void DeactivateSkinning() { var sprite = spriteRenderer.sprite; if (sprite != null) InternalEngineBridge.SetLocalAABB(spriteRenderer, sprite.bounds); SpriteRendererDataAccessExtensions.DeactivateDeformableBuffer(spriteRenderer); } internal void ResetSprite() { m_CurrentDeformSprite = 0; CacheValidFlag(); } public void OnBeforeSerialize() { OnBeforeSerializeBatch(); } public void OnAfterDeserialize() { OnAfterSerializeBatch(); } } }