ball 项目提交

This commit is contained in:
2026-04-20 12:06:34 +08:00
parent 4331ebba60
commit 99145facbd
6052 changed files with 576445 additions and 0 deletions
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: b0fd98019ca00c74f929c6d1f7ee3544
folderAsset: yes
timeCreated: 1563290418
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,217 @@
/******************************************************************************
* 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
using System;
using UnityEngine;
namespace Spine.Unity {
/// <summary>Sets a GameObject's transform to match a bone on a Spine skeleton.</summary>
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[AddComponentMenu("Spine/BoneFollower")]
[HelpURL("http://esotericsoftware.com/spine-unity#BoneFollower")]
public class BoneFollower : MonoBehaviour {
#region Inspector
public SkeletonRenderer skeletonRenderer;
public SkeletonRenderer SkeletonRenderer {
get { return skeletonRenderer; }
set {
skeletonRenderer = value;
Initialize();
}
}
/// <summary>If a bone isn't set in code, boneName is used to find the bone at the beginning. For runtime switching by name, use SetBoneByName. You can also set the BoneFollower.bone field directly.</summary>
[SpineBone(dataField: "skeletonRenderer")]
public string boneName;
public bool followXYPosition = true;
public bool followZPosition = true;
public bool followBoneRotation = true;
[Tooltip("Follows the skeleton's flip state by controlling this Transform's local scale.")]
public bool followSkeletonFlip = true;
[Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")]
public bool followLocalScale = false;
public enum AxisOrientation {
XAxis = 1,
YAxis
}
[Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled."
+ " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted"
+ " instead of its scale to follow the bone orientation. When one of the axes is flipped, "
+ " only one axis can be followed, either the X or the Y axis, which is selected here.")]
public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis;
[UnityEngine.Serialization.FormerlySerializedAs("resetOnAwake")]
public bool initializeOnAwake = true;
#endregion
[NonSerialized] public bool valid;
[NonSerialized] public Bone bone;
Transform skeletonTransform;
bool skeletonTransformIsParent;
/// <summary>
/// Sets the target bone by its bone name. Returns false if no bone was found. To set the bone by reference, use BoneFollower.bone directly.</summary>
public bool SetBone (string name) {
bone = skeletonRenderer.skeleton.FindBone(name);
if (bone == null) {
Debug.LogError("Bone not found: " + name, this);
return false;
}
boneName = name;
return true;
}
public void Awake () {
if (initializeOnAwake) Initialize();
}
public void HandleRebuildRenderer (SkeletonRenderer skeletonRenderer) {
Initialize();
}
public void Initialize () {
bone = null;
valid = skeletonRenderer != null && skeletonRenderer.valid;
if (!valid) return;
skeletonTransform = skeletonRenderer.transform;
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
skeletonRenderer.OnRebuild += HandleRebuildRenderer;
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
if (!string.IsNullOrEmpty(boneName))
bone = skeletonRenderer.skeleton.FindBone(boneName);
#if UNITY_EDITOR
if (Application.isEditor)
LateUpdate();
#endif
}
void OnDestroy () {
if (skeletonRenderer != null)
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
}
public void LateUpdate () {
if (!valid) {
Initialize();
return;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
#endif
if (bone == null) {
if (string.IsNullOrEmpty(boneName)) return;
bone = skeletonRenderer.skeleton.FindBone(boneName);
if (!SetBone(boneName)) return;
}
Transform thisTransform = this.transform;
float additionalFlipScale = 1;
if (skeletonTransformIsParent) {
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX : thisTransform.localPosition.x,
followXYPosition ? bone.worldY : thisTransform.localPosition.y,
followZPosition ? 0f : thisTransform.localPosition.z);
if (followBoneRotation) {
float halfRotation = Mathf.Atan2(bone.c, bone.a) * 0.5f;
if (followLocalScale && bone.scaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation.
halfRotation += Mathf.PI * 0.5f;
var q = default(Quaternion);
q.z = Mathf.Sin(halfRotation);
q.w = Mathf.Cos(halfRotation);
thisTransform.localRotation = q;
}
} else {
// For special cases: Use transform world properties if transform relationship is complicated
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY, 0f));
if (!followZPosition) targetWorldPosition.z = thisTransform.position.z;
if (!followXYPosition) {
targetWorldPosition.x = thisTransform.position.x;
targetWorldPosition.y = thisTransform.position.y;
}
Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
Transform transformParent = thisTransform.parent;
Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
if (followBoneRotation) {
float boneWorldRotation = bone.WorldRotationX;
if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
boneWorldRotation = -boneWorldRotation;
if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) {
if ((skeletonLossyScale.x * parentLossyScale.x < 0))
boneWorldRotation += 180f;
}
else {
if ((skeletonLossyScale.y * parentLossyScale.y < 0))
boneWorldRotation += 180f;
}
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
} else {
thisTransform.position = targetWorldPosition;
}
additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x
* skeletonLossyScale.y * parentLossyScale.y);
}
Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f);
if (followSkeletonFlip)
localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale;
thisTransform.localScale = localScale;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a1fd8daaed7b64148a34acb96ba14ce1
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,196 @@
/******************************************************************************
* 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
using UnityEngine;
namespace Spine.Unity {
using AxisOrientation = BoneFollower.AxisOrientation;
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[RequireComponent(typeof(RectTransform)), DisallowMultipleComponent]
[AddComponentMenu("Spine/UI/BoneFollowerGraphic")]
[HelpURL("http://esotericsoftware.com/spine-unity#BoneFollowerGraphic")]
public class BoneFollowerGraphic : MonoBehaviour {
public SkeletonGraphic skeletonGraphic;
public SkeletonGraphic SkeletonGraphic {
get { return skeletonGraphic; }
set {
skeletonGraphic = value;
Initialize();
}
}
public bool initializeOnAwake = true;
/// <summary>If a bone isn't set in code, boneName is used to find the bone at the beginning. For runtime switching by name, use SetBoneByName. You can also set the BoneFollower.bone field directly.</summary>
[SpineBone(dataField: "skeletonGraphic")]
public string boneName;
public bool followBoneRotation = true;
[Tooltip("Follows the skeleton's flip state by controlling this Transform's local scale.")]
public bool followSkeletonFlip = true;
[Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")]
public bool followLocalScale = false;
public bool followXYPosition = true;
public bool followZPosition = true;
[Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled."
+ " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted"
+ " instead of its scale to follow the bone orientation. When one of the axes is flipped, "
+ " only one axis can be followed, either the X or the Y axis, which is selected here.")]
public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis;
[System.NonSerialized] public Bone bone;
Transform skeletonTransform;
bool skeletonTransformIsParent;
[System.NonSerialized] public bool valid;
/// <summary>
/// Sets the target bone by its bone name. Returns false if no bone was found.</summary>
public bool SetBone (string name) {
bone = skeletonGraphic.Skeleton.FindBone(name);
if (bone == null) {
Debug.LogError("Bone not found: " + name, this);
return false;
}
boneName = name;
return true;
}
public void Awake () {
if (initializeOnAwake) Initialize();
}
public void Initialize () {
bone = null;
valid = skeletonGraphic != null && skeletonGraphic.IsValid;
if (!valid) return;
skeletonTransform = skeletonGraphic.transform;
// skeletonGraphic.OnRebuild -= HandleRebuildRenderer;
// skeletonGraphic.OnRebuild += HandleRebuildRenderer;
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
if (!string.IsNullOrEmpty(boneName))
bone = skeletonGraphic.Skeleton.FindBone(boneName);
#if UNITY_EDITOR
if (Application.isEditor) {
LateUpdate();
}
#endif
}
public void LateUpdate () {
if (!valid) {
Initialize();
return;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
#endif
if (bone == null) {
if (string.IsNullOrEmpty(boneName)) return;
bone = skeletonGraphic.Skeleton.FindBone(boneName);
if (!SetBone(boneName)) return;
}
var thisTransform = this.transform as RectTransform;
if (thisTransform == null) return;
var canvas = skeletonGraphic.canvas;
if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
float additionalFlipScale = 1;
if (skeletonTransformIsParent) {
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX * scale : thisTransform.localPosition.x,
followXYPosition ? bone.worldY * scale : thisTransform.localPosition.y,
followZPosition ? 0f : thisTransform.localPosition.z);
if (followBoneRotation) thisTransform.localRotation = bone.GetQuaternion();
} else {
// For special cases: Use transform world properties if transform relationship is complicated
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.worldX * scale, bone.worldY * scale, 0f));
if (!followZPosition) targetWorldPosition.z = thisTransform.position.z;
if (!followXYPosition) {
targetWorldPosition.x = thisTransform.position.x;
targetWorldPosition.y = thisTransform.position.y;
}
Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
Transform transformParent = thisTransform.parent;
Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
if (followBoneRotation) {
float boneWorldRotation = bone.WorldRotationX;
if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
boneWorldRotation = -boneWorldRotation;
if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) {
if ((skeletonLossyScale.x * parentLossyScale.x < 0))
boneWorldRotation += 180f;
}
else {
if ((skeletonLossyScale.y * parentLossyScale.y < 0))
boneWorldRotation += 180f;
}
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
} else {
thisTransform.position = targetWorldPosition;
}
additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x
* skeletonLossyScale.y * parentLossyScale.y);
}
Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f);
if (followSkeletonFlip)
localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale;
thisTransform.localScale = localScale;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: b42a195b47491d34b9bcbc40898bcb29
timeCreated: 1499211965
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,253 @@
/******************************************************************************
* 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
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollower")]
public class BoundingBoxFollower : MonoBehaviour {
internal static bool DebugMessages = true;
#region Inspector
public SkeletonRenderer skeletonRenderer;
[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
public string slotName;
public bool isTrigger;
public bool clearStateOnDisable = true;
#endregion
Slot slot;
BoundingBoxAttachment currentAttachment;
string currentAttachmentName;
PolygonCollider2D currentCollider;
public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
public Slot Slot { get { return slot; } }
public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
public string CurrentAttachmentName { get { return currentAttachmentName; } }
public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
public bool IsTrigger { get { return isTrigger; } }
void Start () {
Initialize();
}
void OnEnable () {
if (skeletonRenderer != null) {
skeletonRenderer.OnRebuild -= HandleRebuild;
skeletonRenderer.OnRebuild += HandleRebuild;
}
Initialize();
}
void HandleRebuild (SkeletonRenderer sr) {
//if (BoundingBoxFollower.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollower.");
Initialize();
}
/// <summary>
/// Initialize and instantiate the BoundingBoxFollower colliders. This is method checks if the BoundingBoxFollower has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
public void Initialize (bool overwrite = false) {
if (skeletonRenderer == null)
return;
skeletonRenderer.Initialize(false);
if (string.IsNullOrEmpty(slotName))
return;
// Don't reinitialize if the setup did not change.
if (!overwrite
&&
colliderTable.Count > 0 && slot != null // Slot is set and colliders already populated.
&&
skeletonRenderer.skeleton == slot.Skeleton // Skeleton object did not change.
&&
slotName == slot.data.name // Slot object did not change.
)
return;
slot = null;
currentAttachment = null;
currentAttachmentName = null;
currentCollider = null;
colliderTable.Clear();
nameTable.Clear();
var skeleton = skeletonRenderer.skeleton;
if (skeleton == null)
return;
slot = skeleton.FindSlot(slotName);
int slotIndex = skeleton.FindSlotIndex(slotName);
if (slot == null) {
if (BoundingBoxFollower.DebugMessages)
Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollower on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
return;
}
int requiredCollidersCount = 0;
var colliders = GetComponents<PolygonCollider2D>();
if (this.gameObject.activeInHierarchy) {
foreach (var skin in skeleton.Data.Skins)
AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount);
if (skeleton.skin != null)
AddCollidersForSkin(skeleton.skin, slotIndex, colliders, ref requiredCollidersCount);
}
DisposeExcessCollidersAfter(requiredCollidersCount);
if (BoundingBoxFollower.DebugMessages) {
bool valid = colliderTable.Count != 0;
if (!valid) {
if (this.gameObject.activeInHierarchy)
Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
else
Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
}
}
}
void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) {
if (skin == null) return;
var skinEntries = new List<Skin.SkinEntry>();
skin.GetAttachments(slotIndex, skinEntries);
foreach (var entry in skinEntries) {
var attachment = skin.GetAttachment(slotIndex, entry.Name);
var boundingBoxAttachment = attachment as BoundingBoxAttachment;
if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
Debug.Log("BoundingBoxFollower tried to follow a slot that contains non-boundingbox attachments: " + slotName);
if (boundingBoxAttachment != null) {
if (!colliderTable.ContainsKey(boundingBoxAttachment)) {
var bbCollider = collidersCount < previousColliders.Length ?
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
++collidersCount;
SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment);
bbCollider.isTrigger = isTrigger;
bbCollider.enabled = false;
bbCollider.hideFlags = HideFlags.NotEditable;
bbCollider.isTrigger = IsTrigger;
colliderTable.Add(boundingBoxAttachment, bbCollider);
nameTable.Add(boundingBoxAttachment, entry.Name);
}
}
}
}
void OnDisable () {
if (clearStateOnDisable)
ClearState();
if (skeletonRenderer != null)
skeletonRenderer.OnRebuild -= HandleRebuild;
}
public void ClearState () {
if (colliderTable != null)
foreach (var col in colliderTable.Values)
col.enabled = false;
currentAttachment = null;
currentAttachmentName = null;
currentCollider = null;
}
void DisposeExcessCollidersAfter (int requiredCount) {
var colliders = GetComponents<PolygonCollider2D>();
if (colliders.Length == 0) return;
for (int i = requiredCount; i < colliders.Length; ++i) {
var collider = colliders[i];
if (collider != null) {
#if UNITY_EDITOR
if (Application.isEditor && !Application.isPlaying)
DestroyImmediate(collider);
else
#endif
Destroy(collider);
}
}
}
void LateUpdate () {
if (slot != null && slot.Attachment != currentAttachment)
MatchAttachment(slot.Attachment);
}
/// <summary>Sets the current collider to match attachment.</summary>
/// <param name="attachment">If the attachment is not a bounding box, it will be treated as null.</param>
void MatchAttachment (Attachment attachment) {
var bbAttachment = attachment as BoundingBoxAttachment;
if (BoundingBoxFollower.DebugMessages && attachment != null && bbAttachment == null)
Debug.LogWarning("BoundingBoxFollower tried to match a non-boundingbox attachment. It will treat it as null.");
if (currentCollider != null)
currentCollider.enabled = false;
if (bbAttachment == null) {
currentCollider = null;
currentAttachment = null;
currentAttachmentName = null;
} else {
PolygonCollider2D foundCollider;
colliderTable.TryGetValue(bbAttachment, out foundCollider);
if (foundCollider != null) {
currentCollider = foundCollider;
currentCollider.enabled = true;
currentAttachment = bbAttachment;
currentAttachmentName = nameTable[bbAttachment];
} else {
currentCollider = null;
currentAttachment = bbAttachment;
currentAttachmentName = null;
if (BoundingBoxFollower.DebugMessages) Debug.LogFormat("Collider for BoundingBoxAttachment named '{0}' was not initialized. It is possibly from a new skin. currentAttachmentName will be null. You may need to call BoundingBoxFollower.Initialize(overwrite: true);", bbAttachment.Name);
}
}
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0317ee9ba6e1b1e49a030268e026d372
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
@@ -0,0 +1,257 @@
/******************************************************************************
* 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
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollowerGraphic")]
public class BoundingBoxFollowerGraphic : MonoBehaviour {
internal static bool DebugMessages = true;
#region Inspector
public SkeletonGraphic skeletonGraphic;
[SpineSlot(dataField: "skeletonGraphic", containsBoundingBoxes: true)]
public string slotName;
public bool isTrigger;
public bool clearStateOnDisable = true;
#endregion
Slot slot;
BoundingBoxAttachment currentAttachment;
string currentAttachmentName;
PolygonCollider2D currentCollider;
public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
public Slot Slot { get { return slot; } }
public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
public string CurrentAttachmentName { get { return currentAttachmentName; } }
public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
public bool IsTrigger { get { return isTrigger; } }
void Start () {
Initialize();
}
void OnEnable () {
if (skeletonGraphic != null) {
skeletonGraphic.OnRebuild -= HandleRebuild;
skeletonGraphic.OnRebuild += HandleRebuild;
}
Initialize();
}
void HandleRebuild (SkeletonGraphic sr) {
//if (BoundingBoxFollowerGraphic.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollowerGraphic.");
Initialize();
}
/// <summary>
/// Initialize and instantiate the BoundingBoxFollowerGraphic colliders. This is method checks if the BoundingBoxFollowerGraphic has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
public void Initialize (bool overwrite = false) {
if (skeletonGraphic == null)
return;
skeletonGraphic.Initialize(false);
if (string.IsNullOrEmpty(slotName))
return;
// Don't reinitialize if the setup did not change.
if (!overwrite
&&
colliderTable.Count > 0 && slot != null // Slot is set and colliders already populated.
&&
skeletonGraphic.Skeleton == slot.Skeleton // Skeleton object did not change.
&&
slotName == slot.data.name // Slot object did not change.
)
return;
slot = null;
currentAttachment = null;
currentAttachmentName = null;
currentCollider = null;
colliderTable.Clear();
nameTable.Clear();
var skeleton = skeletonGraphic.Skeleton;
if (skeleton == null)
return;
slot = skeleton.FindSlot(slotName);
int slotIndex = skeleton.FindSlotIndex(slotName);
if (slot == null) {
if (BoundingBoxFollowerGraphic.DebugMessages)
Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollowerGraphic on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
return;
}
int requiredCollidersCount = 0;
var colliders = GetComponents<PolygonCollider2D>();
if (this.gameObject.activeInHierarchy) {
var canvas = skeletonGraphic.canvas;
if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
foreach (var skin in skeleton.Data.Skins)
AddCollidersForSkin(skin, slotIndex, colliders, scale, ref requiredCollidersCount);
if (skeleton.skin != null)
AddCollidersForSkin(skeleton.skin, slotIndex, colliders, scale, ref requiredCollidersCount);
}
DisposeExcessCollidersAfter(requiredCollidersCount);
if (BoundingBoxFollowerGraphic.DebugMessages) {
bool valid = colliderTable.Count != 0;
if (!valid) {
if (this.gameObject.activeInHierarchy)
Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
else
Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
}
}
}
void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) {
if (skin == null) return;
var skinEntries = new List<Skin.SkinEntry>();
skin.GetAttachments(slotIndex, skinEntries);
foreach (var entry in skinEntries) {
var attachment = skin.GetAttachment(slotIndex, entry.Name);
var boundingBoxAttachment = attachment as BoundingBoxAttachment;
if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && boundingBoxAttachment == null)
Debug.Log("BoundingBoxFollowerGraphic tried to follow a slot that contains non-boundingbox attachments: " + slotName);
if (boundingBoxAttachment != null) {
if (!colliderTable.ContainsKey(boundingBoxAttachment)) {
var bbCollider = collidersCount < previousColliders.Length ?
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
++collidersCount;
SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment, scale);
bbCollider.isTrigger = isTrigger;
bbCollider.enabled = false;
bbCollider.hideFlags = HideFlags.NotEditable;
bbCollider.isTrigger = IsTrigger;
colliderTable.Add(boundingBoxAttachment, bbCollider);
nameTable.Add(boundingBoxAttachment, entry.Name);
}
}
}
}
void OnDisable () {
if (clearStateOnDisable)
ClearState();
if (skeletonGraphic != null)
skeletonGraphic.OnRebuild -= HandleRebuild;
}
public void ClearState () {
if (colliderTable != null)
foreach (var col in colliderTable.Values)
col.enabled = false;
currentAttachment = null;
currentAttachmentName = null;
currentCollider = null;
}
void DisposeExcessCollidersAfter (int requiredCount) {
var colliders = GetComponents<PolygonCollider2D>();
if (colliders.Length == 0) return;
for (int i = requiredCount; i < colliders.Length; ++i) {
var collider = colliders[i];
if (collider != null) {
#if UNITY_EDITOR
if (Application.isEditor && !Application.isPlaying)
DestroyImmediate(collider);
else
#endif
Destroy(collider);
}
}
}
void LateUpdate () {
if (slot != null && slot.Attachment != currentAttachment)
MatchAttachment(slot.Attachment);
}
/// <summary>Sets the current collider to match attachment.</summary>
/// <param name="attachment">If the attachment is not a bounding box, it will be treated as null.</param>
void MatchAttachment (Attachment attachment) {
var bbAttachment = attachment as BoundingBoxAttachment;
if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && bbAttachment == null)
Debug.LogWarning("BoundingBoxFollowerGraphic tried to match a non-boundingbox attachment. It will treat it as null.");
if (currentCollider != null)
currentCollider.enabled = false;
if (bbAttachment == null) {
currentCollider = null;
currentAttachment = null;
currentAttachmentName = null;
} else {
PolygonCollider2D foundCollider;
colliderTable.TryGetValue(bbAttachment, out foundCollider);
if (foundCollider != null) {
currentCollider = foundCollider;
currentCollider.enabled = true;
currentAttachment = bbAttachment;
currentAttachmentName = nameTable[bbAttachment];
} else {
currentCollider = null;
currentAttachment = bbAttachment;
currentAttachmentName = null;
if (BoundingBoxFollowerGraphic.DebugMessages) Debug.LogFormat("Collider for BoundingBoxAttachment named '{0}' was not initialized. It is possibly from a new skin. currentAttachmentName will be null. You may need to call BoundingBoxFollowerGraphic.Initialize(overwrite: true);", bbAttachment.Name);
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c0bf7b497af9f74280040d96cdf88da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,164 @@
/******************************************************************************
* 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
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[AddComponentMenu("Spine/Point Follower")]
[HelpURL("http://esotericsoftware.com/spine-unity#PointFollower")]
public class PointFollower : MonoBehaviour, IHasSkeletonRenderer, IHasSkeletonComponent {
public SkeletonRenderer skeletonRenderer;
public SkeletonRenderer SkeletonRenderer { get { return this.skeletonRenderer; } }
public ISkeletonComponent SkeletonComponent { get { return skeletonRenderer as ISkeletonComponent; } }
[SpineSlot(dataField:"skeletonRenderer", includeNone: true)]
public string slotName;
[SpineAttachment(slotField:"slotName", dataField: "skeletonRenderer", fallbackToTextField:true, includeNone: true)]
public string pointAttachmentName;
public bool followRotation = true;
public bool followSkeletonFlip = true;
public bool followSkeletonZPosition = false;
Transform skeletonTransform;
bool skeletonTransformIsParent;
PointAttachment point;
Bone bone;
bool valid;
public bool IsValid { get { return valid; } }
public void Initialize () {
valid = skeletonRenderer != null && skeletonRenderer.valid;
if (!valid)
return;
UpdateReferences();
#if UNITY_EDITOR
if (Application.isEditor) LateUpdate();
#endif
}
private void HandleRebuildRenderer (SkeletonRenderer skeletonRenderer) {
Initialize();
}
void UpdateReferences () {
skeletonTransform = skeletonRenderer.transform;
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
skeletonRenderer.OnRebuild += HandleRebuildRenderer;
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
bone = null;
point = null;
if (!string.IsNullOrEmpty(pointAttachmentName)) {
var skeleton = skeletonRenderer.Skeleton;
int slotIndex = skeleton.FindSlotIndex(slotName);
if (slotIndex >= 0) {
var slot = skeleton.slots.Items[slotIndex];
bone = slot.bone;
point = skeleton.GetAttachment(slotIndex, pointAttachmentName) as PointAttachment;
}
}
}
void OnDestroy () {
if (skeletonRenderer != null)
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
}
public void LateUpdate () {
#if UNITY_EDITOR
if (!Application.isPlaying) skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
#endif
if (point == null) {
if (string.IsNullOrEmpty(pointAttachmentName)) return;
UpdateReferences();
if (point == null) return;
}
Vector2 worldPos;
point.ComputeWorldPosition(bone, out worldPos.x, out worldPos.y);
float rotation = point.ComputeWorldRotation(bone);
Transform thisTransform = this.transform;
if (skeletonTransformIsParent) {
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
thisTransform.localPosition = new Vector3(worldPos.x, worldPos.y, followSkeletonZPosition ? 0f : thisTransform.localPosition.z);
if (followRotation) {
float halfRotation = rotation * 0.5f * Mathf.Deg2Rad;
var q = default(Quaternion);
q.z = Mathf.Sin(halfRotation);
q.w = Mathf.Cos(halfRotation);
thisTransform.localRotation = q;
}
} else {
// For special cases: Use transform world properties if transform relationship is complicated
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(worldPos.x, worldPos.y, 0f));
if (!followSkeletonZPosition)
targetWorldPosition.z = thisTransform.position.z;
Transform transformParent = thisTransform.parent;
if (transformParent != null) {
Matrix4x4 m = transformParent.localToWorldMatrix;
if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative
rotation = -rotation;
}
if (followRotation) {
Vector3 transformWorldRotation = skeletonTransform.rotation.eulerAngles;
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(transformWorldRotation.x, transformWorldRotation.y, transformWorldRotation.z + rotation));
} else {
thisTransform.position = targetWorldPosition;
}
}
if (followSkeletonFlip) {
Vector3 localScale = thisTransform.localScale;
localScale.y = Mathf.Abs(localScale.y) * Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY);
thisTransform.localScale = localScale;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: daf461e4341180341a648c07e1899528
timeCreated: 1518094986
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 01776890e2374864d9a09bb3d687ae9f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,121 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
using System.Collections.Generic;
using Spine.Unity.AnimationTools;
namespace Spine.Unity {
/// <summary>
/// Add this component to a SkeletonMecanim GameObject
/// to turn motion of a selected root bone into Transform or RigidBody motion.
/// Local bone translation movement is used as motion.
/// All top-level bones of the skeleton are moved to compensate the root
/// motion bone location, keeping the distance relationship between bones intact.
/// </summary>
/// <remarks>
/// Only compatible with <c>SkeletonMecanim</c>.
/// For <c>SkeletonAnimation</c> or <c>SkeletonGraphic</c> please use
/// <see cref="SkeletonRootMotion">SkeletonRootMotion</see> instead.
/// </remarks>
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonMecanimRootMotion")]
public class SkeletonMecanimRootMotion : SkeletonRootMotionBase {
#region Inspector
const int DefaultMecanimLayerFlags = -1;
public int mecanimLayerFlags = DefaultMecanimLayerFlags;
#endregion
protected Vector2 movementDelta;
SkeletonMecanim skeletonMecanim;
public SkeletonMecanim SkeletonMecanim {
get {
return skeletonMecanim ? skeletonMecanim : skeletonMecanim = GetComponent<SkeletonMecanim>();
}
}
public override Vector2 GetRemainingRootMotion (int layerIndex) {
var pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
var animation = pair.Key;
var time = pair.Value;
if (animation == null)
return Vector2.zero;
float start = time;
float end = animation.duration;
return GetAnimationRootMotion(start, end, animation);
}
public override RootMotionInfo GetRootMotionInfo (int layerIndex) {
var pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
var animation = pair.Key;
var time = pair.Value;
if (animation == null)
return new RootMotionInfo();
return GetAnimationRootMotionInfo(animation, time);
}
protected override void Reset () {
base.Reset();
mecanimLayerFlags = DefaultMecanimLayerFlags;
}
protected override void Start () {
base.Start();
skeletonMecanim = GetComponent<SkeletonMecanim>();
if (skeletonMecanim) {
skeletonMecanim.Translator.OnClipApplied -= OnClipApplied;
skeletonMecanim.Translator.OnClipApplied += OnClipApplied;
}
}
void OnClipApplied(Spine.Animation animation, int layerIndex, float weight,
float time, float lastTime, bool playsBackward) {
if (((mecanimLayerFlags & 1<<layerIndex) == 0) || weight == 0)
return;
if (!playsBackward) {
movementDelta += weight * GetAnimationRootMotion(lastTime, time, animation);
}
else {
movementDelta -= weight * GetAnimationRootMotion(time, lastTime, animation);
}
}
protected override Vector2 CalculateAnimationsMovementDelta () {
// Note: movement delta is not gather after animation but
// in OnClipApplied after every applied animation.
Vector2 result = movementDelta;
movementDelta = Vector2.zero;
return result;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 95813afe390494344a6ce2cbc8bfb7d1
timeCreated: 1592849332
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,157 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
using System.Collections.Generic;
using Spine.Unity.AnimationTools;
namespace Spine.Unity {
/// <summary>
/// Add this component to a SkeletonAnimation or SkeletonGraphic GameObject
/// to turn motion of a selected root bone into Transform or RigidBody motion.
/// Local bone translation movement is used as motion.
/// All top-level bones of the skeleton are moved to compensate the root
/// motion bone location, keeping the distance relationship between bones intact.
/// </summary>
/// <remarks>
/// Only compatible with SkeletonAnimation (or other components that implement
/// ISkeletonComponent, ISkeletonAnimation and IAnimationStateComponent).
/// For <c>SkeletonMecanim</c> please use
/// <see cref="SkeletonMecanimRootMotion">SkeletonMecanimRootMotion</see> instead.
/// </remarks>
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRootMotion")]
public class SkeletonRootMotion : SkeletonRootMotionBase {
#region Inspector
const int DefaultAnimationTrackFlags = -1;
public int animationTrackFlags = DefaultAnimationTrackFlags;
#endregion
AnimationState animationState;
Canvas canvas;
public override Vector2 GetRemainingRootMotion (int trackIndex) {
TrackEntry track = animationState.GetCurrent(trackIndex);
if (track == null)
return Vector2.zero;
var animation = track.Animation;
float start = track.AnimationTime;
float end = animation.duration;
return GetAnimationRootMotion(start, end, animation);
}
public override RootMotionInfo GetRootMotionInfo (int trackIndex) {
TrackEntry track = animationState.GetCurrent(trackIndex);
if (track == null)
return new RootMotionInfo();
var animation = track.Animation;
float time = track.AnimationTime;
return GetAnimationRootMotionInfo(track.Animation, time);
}
protected override float AdditionalScale {
get {
return canvas ? canvas.referencePixelsPerUnit: 1.0f;
}
}
protected override void Reset () {
base.Reset();
animationTrackFlags = DefaultAnimationTrackFlags;
}
protected override void Start () {
base.Start();
var animstateComponent = skeletonComponent as IAnimationStateComponent;
this.animationState = (animstateComponent != null) ? animstateComponent.AnimationState : null;
if (this.GetComponent<CanvasRenderer>() != null) {
canvas = this.GetComponentInParent<Canvas>();
}
}
protected override Vector2 CalculateAnimationsMovementDelta () {
Vector2 localDelta = Vector2.zero;
int trackCount = animationState.Tracks.Count;
for (int trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
// note: animationTrackFlags != -1 below covers trackIndex >= 32,
// with -1 corresponding to entry "everything" of the dropdown list.
if (animationTrackFlags != -1 && (animationTrackFlags & 1 << trackIndex) == 0)
continue;
TrackEntry track = animationState.GetCurrent(trackIndex);
TrackEntry next = null;
while (track != null) {
var animation = track.Animation;
float start = track.animationLast;
float end = track.AnimationTime;
var currentDelta = GetAnimationRootMotion(start, end, animation);
if (currentDelta != Vector2.zero) {
ApplyMixAlphaToDelta(ref currentDelta, next, track);
localDelta += currentDelta;
}
// Traverse mixingFrom chain.
next = track;
track = track.mixingFrom;
}
}
return localDelta;
}
void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry track) {
// Apply mix alpha to the delta position (based on AnimationState.cs).
float mix;
if (next != null) {
if (next.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
mix = 1;
}
else {
mix = next.mixTime / next.mixDuration;
if (mix > 1) mix = 1;
}
float mixAndAlpha = track.alpha * next.interruptAlpha * (1 - mix);
currentDelta *= mixAndAlpha;
}
else {
if (track.mixDuration == 0) {
mix = 1;
}
else {
mix = track.alpha * (track.mixTime / track.mixDuration);
if (mix > 1) mix = 1;
}
currentDelta *= mix;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f21c9538588898a45a3da22bf4779ab3
timeCreated: 1591121072
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,322 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
using System.Collections.Generic;
using Spine.Unity.AnimationTools;
using System;
namespace Spine.Unity {
/// <summary>
/// Base class for skeleton root motion components.
/// </summary>
abstract public class SkeletonRootMotionBase : MonoBehaviour {
#region Inspector
[SpineBone]
[SerializeField]
protected string rootMotionBoneName = "root";
public bool transformPositionX = true;
public bool transformPositionY = true;
public float rootMotionScaleX = 1;
public float rootMotionScaleY = 1;
/// <summary>Skeleton space X translation per skeleton space Y translation root motion.</summary>
public float rootMotionTranslateXPerY = 0;
/// <summary>Skeleton space Y translation per skeleton space X translation root motion.</summary>
public float rootMotionTranslateYPerX = 0;
[Header("Optional")]
public Rigidbody2D rigidBody2D;
public Rigidbody rigidBody;
public bool UsesRigidbody {
get { return rigidBody != null || rigidBody2D != null; }
}
#endregion
protected ISkeletonComponent skeletonComponent;
protected Bone rootMotionBone;
protected int rootMotionBoneIndex;
protected List<Bone> topLevelBones = new List<Bone>();
protected Vector2 initialOffset = Vector2.zero;
protected Vector2 tempSkeletonDisplacement;
protected Vector2 rigidbodyDisplacement;
protected virtual void Reset () {
FindRigidbodyComponent();
}
protected virtual void Start () {
skeletonComponent = GetComponent<ISkeletonComponent>();
GatherTopLevelBones();
SetRootMotionBone(rootMotionBoneName);
if (rootMotionBone != null)
initialOffset = new Vector2(rootMotionBone.x, rootMotionBone.y);
var skeletonAnimation = skeletonComponent as ISkeletonAnimation;
if (skeletonAnimation != null) {
skeletonAnimation.UpdateLocal -= HandleUpdateLocal;
skeletonAnimation.UpdateLocal += HandleUpdateLocal;
}
}
protected virtual void FixedUpdate () {
if (!this.isActiveAndEnabled)
return; // Root motion is only applied when component is enabled.
if (rigidBody2D != null) {
rigidBody2D.MovePosition(new Vector2(transform.position.x, transform.position.y)
+ rigidbodyDisplacement);
}
if (rigidBody != null) {
rigidBody.MovePosition(transform.position
+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
}
Vector2 parentBoneScale;
GetScaleAffectingRootMotion(out parentBoneScale);
ClearEffectiveBoneOffsets(parentBoneScale);
rigidbodyDisplacement = Vector2.zero;
tempSkeletonDisplacement = Vector2.zero;
}
protected virtual void OnDisable () {
rigidbodyDisplacement = Vector2.zero;
tempSkeletonDisplacement = Vector2.zero;
}
protected void FindRigidbodyComponent () {
rigidBody2D = this.GetComponent<Rigidbody2D>();
if (!rigidBody2D)
rigidBody = this.GetComponent<Rigidbody>();
if (!rigidBody2D && !rigidBody) {
rigidBody2D = this.GetComponentInParent<Rigidbody2D>();
if (!rigidBody2D)
rigidBody = this.GetComponentInParent<Rigidbody>();
}
}
protected virtual float AdditionalScale { get { return 1.0f; } }
abstract protected Vector2 CalculateAnimationsMovementDelta ();
abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0);
public struct RootMotionInfo {
public Vector2 start;
public Vector2 current;
public Vector2 mid;
public Vector2 end;
public bool timeIsPastMid;
};
abstract public RootMotionInfo GetRootMotionInfo (int trackIndex = 0);
public void SetRootMotionBone (string name) {
var skeleton = skeletonComponent.Skeleton;
int index = skeleton.FindBoneIndex(name);
if (index >= 0) {
this.rootMotionBoneIndex = index;
this.rootMotionBone = skeleton.bones.Items[index];
}
else {
Debug.Log("Bone named \"" + name + "\" could not be found.");
this.rootMotionBoneIndex = 0;
this.rootMotionBone = skeleton.RootBone;
}
}
public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true,
float minX = 0, float maxX = float.MaxValue, float minY = 0, float maxY = float.MaxValue,
bool allowXTranslation = false, bool allowYTranslation = false) {
Vector2 distanceToTargetSkeletonSpace = (Vector2)transform.InverseTransformVector(distanceToTarget);
Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion();
if (UsesRigidbody)
distanceToTargetSkeletonSpace -= tempSkeletonDisplacement;
Vector2 remainingRootMotionSkeletonSpace = GetRemainingRootMotion(trackIndex);
remainingRootMotionSkeletonSpace.Scale(scaleAffectingRootMotion);
if (remainingRootMotionSkeletonSpace.x == 0)
remainingRootMotionSkeletonSpace.x = 0.0001f;
if (remainingRootMotionSkeletonSpace.y == 0)
remainingRootMotionSkeletonSpace.y = 0.0001f;
if (adjustX)
rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTargetSkeletonSpace.x / remainingRootMotionSkeletonSpace.x));
if (adjustY)
rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTargetSkeletonSpace.y / remainingRootMotionSkeletonSpace.y));
if (allowXTranslation)
rootMotionTranslateXPerY = (distanceToTargetSkeletonSpace.x - remainingRootMotionSkeletonSpace.x * rootMotionScaleX) / remainingRootMotionSkeletonSpace.y;
if (allowYTranslation)
rootMotionTranslateYPerX = (distanceToTargetSkeletonSpace.y - remainingRootMotionSkeletonSpace.y * rootMotionScaleY) / remainingRootMotionSkeletonSpace.x;
}
public Vector2 GetAnimationRootMotion (Animation animation) {
return GetAnimationRootMotion(0, animation.duration, animation);
}
public Vector2 GetAnimationRootMotion (float startTime, float endTime,
Animation animation) {
var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
if (timeline != null) {
return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
}
return Vector2.zero;
}
public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
RootMotionInfo rootMotion = new RootMotionInfo();
var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
if (timeline != null) {
float duration = animation.duration;
float mid = duration * 0.5f;
rootMotion.start = timeline.Evaluate(0);
rootMotion.current = timeline.Evaluate(currentTime);
rootMotion.mid = timeline.Evaluate(mid);
rootMotion.end = timeline.Evaluate(duration);
rootMotion.timeIsPastMid = currentTime > mid;
}
return rootMotion;
}
Vector2 GetTimelineMovementDelta (float startTime, float endTime,
TranslateTimeline timeline, Animation animation) {
Vector2 currentDelta;
if (startTime > endTime) // Looped
currentDelta = (timeline.Evaluate(animation.duration) - timeline.Evaluate(startTime))
+ (timeline.Evaluate(endTime) - timeline.Evaluate(0));
else if (startTime != endTime) // Non-looped
currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime);
else
currentDelta = Vector2.zero;
return currentDelta;
}
void GatherTopLevelBones () {
topLevelBones.Clear();
var skeleton = skeletonComponent.Skeleton;
foreach (var bone in skeleton.Bones) {
if (bone.Parent == null)
topLevelBones.Add(bone);
}
}
void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) {
if (!this.isActiveAndEnabled)
return; // Root motion is only applied when component is enabled.
var boneLocalDelta = CalculateAnimationsMovementDelta();
Vector2 parentBoneScale;
Vector2 skeletonDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale);
ApplyRootMotion(skeletonDelta, parentBoneScale);
}
void ApplyRootMotion (Vector2 skeletonDelta, Vector2 parentBoneScale) {
// Apply root motion to Transform or RigidBody;
if (UsesRigidbody) {
rigidbodyDisplacement += (Vector2)transform.TransformVector(skeletonDelta);
// Accumulated displacement is applied on the next Physics update in FixedUpdate.
// Until the next Physics update, tempBoneDisplacement is offsetting bone locations
// to prevent stutter which would otherwise occur if we don't move every Update.
tempSkeletonDisplacement += skeletonDelta;
SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, parentBoneScale);
}
else {
transform.position += transform.TransformVector(skeletonDelta);
ClearEffectiveBoneOffsets(parentBoneScale);
}
}
Vector2 GetScaleAffectingRootMotion () {
Vector2 parentBoneScale;
return GetScaleAffectingRootMotion(out parentBoneScale);
}
Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) {
var skeleton = skeletonComponent.Skeleton;
Vector2 totalScale = Vector2.one;
totalScale.x *= skeleton.ScaleX;
totalScale.y *= skeleton.ScaleY;
parentBoneScale = Vector2.one;
Bone scaleBone = rootMotionBone;
while ((scaleBone = scaleBone.parent) != null) {
parentBoneScale.x *= scaleBone.ScaleX;
parentBoneScale.y *= scaleBone.ScaleY;
}
totalScale = Vector2.Scale(totalScale, parentBoneScale);
totalScale *= AdditionalScale;
return totalScale;
}
Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale) {
Vector2 skeletonDelta = boneLocalDelta;
Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
skeletonDelta.Scale(totalScale);
Vector2 rootMotionTranslation = new Vector2(
rootMotionTranslateXPerY * skeletonDelta.y,
rootMotionTranslateYPerX * skeletonDelta.x);
skeletonDelta.x *= rootMotionScaleX;
skeletonDelta.y *= rootMotionScaleY;
skeletonDelta.x += rootMotionTranslation.x;
skeletonDelta.y += rootMotionTranslation.y;
if (!transformPositionX) skeletonDelta.x = 0f;
if (!transformPositionY) skeletonDelta.y = 0f;
return skeletonDelta;
}
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) {
// Move top level bones in opposite direction of the root motion bone
var skeleton = skeletonComponent.Skeleton;
foreach (var topLevelBone in topLevelBones) {
if (topLevelBone == rootMotionBone) {
if (transformPositionX) topLevelBone.x = displacementSkeletonSpace.x / skeleton.ScaleX;
if (transformPositionY) topLevelBone.y = displacementSkeletonSpace.y / skeleton.ScaleY;
}
else {
float offsetX = (initialOffset.x - rootMotionBone.x) * parentBoneScale.x;
float offsetY = (initialOffset.y - rootMotionBone.y) * parentBoneScale.y;
if (transformPositionX) topLevelBone.x = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
if (transformPositionY) topLevelBone.y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
}
}
}
void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
SetEffectiveBoneOffsetsTo(Vector2.zero, parentBoneScale);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fc23a4f220b20024ab0592719f92587d
timeCreated: 1592849332
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,251 @@
/******************************************************************************
* 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
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[AddComponentMenu("Spine/SkeletonAnimation")]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonAnimation-Component")]
public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation, IAnimationStateComponent {
#region IAnimationStateComponent
/// <summary>
/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it.
/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
public Spine.AnimationState state;
/// <summary>
/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it.
/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
public Spine.AnimationState AnimationState {
get {
Initialize(false);
return this.state;
}
}
private bool wasUpdatedAfterInit = true;
#endregion
#region Bone Callbacks ISkeletonAnimation
protected event UpdateBonesDelegate _BeforeApply;
protected event UpdateBonesDelegate _UpdateLocal;
protected event UpdateBonesDelegate _UpdateWorld;
protected event UpdateBonesDelegate _UpdateComplete;
/// <summary>
/// Occurs before the animations are applied.
/// Use this callback when you want to change the skeleton state before animations are applied on top.
/// </summary>
public event UpdateBonesDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
/// <summary>
/// Occurs after the animations are applied and before world space values are resolved.
/// Use this callback when you want to set bone local values.
/// </summary>
public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Using this callback will cause the world space values to be solved an extra time.
/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
/// This callback can also be used when setting world position and the bone matrix.</summary>
public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
#endregion
#region Serialized state and Beginner API
[SerializeField]
[SpineAnimation]
private string _animationName;
/// <summary>
/// Setting this property sets the animation of the skeleton. If invalid, it will store the animation name for the next time the skeleton is properly initialized.
/// Getting this property gets the name of the currently playing animation. If invalid, it will return the last stored animation name set through this property.</summary>
public string AnimationName {
get {
if (!valid) {
return _animationName;
} else {
TrackEntry entry = state.GetCurrent(0);
return entry == null ? null : entry.Animation.Name;
}
}
set {
Initialize(false);
if (_animationName == value) {
TrackEntry entry = state.GetCurrent(0);
if (entry != null && entry.loop == loop)
return;
}
_animationName = value;
if (string.IsNullOrEmpty(value)) {
state.ClearTrack(0);
} else {
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(value);
if (animationObject != null)
state.SetAnimation(0, animationObject, loop);
}
}
}
/// <summary>Whether or not <see cref="AnimationName"/> should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.</summary>
public bool loop;
/// <summary>
/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.</summary>
/// <remarks>AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.</remarks>
public float timeScale = 1;
#endregion
#region Runtime Instantiation
/// <summary>Adds and prepares a SkeletonAnimation component to a GameObject at runtime.</summary>
/// <returns>The newly instantiated SkeletonAnimation</returns>
public static SkeletonAnimation AddToGameObject (GameObject gameObject, SkeletonDataAsset skeletonDataAsset,
bool quiet = false) {
return SkeletonRenderer.AddSpineComponent<SkeletonAnimation>(gameObject, skeletonDataAsset, quiet);
}
/// <summary>Instantiates a new UnityEngine.GameObject and adds a prepared SkeletonAnimation component to it.</summary>
/// <returns>The newly instantiated SkeletonAnimation component.</returns>
public static SkeletonAnimation NewSkeletonAnimationGameObject (SkeletonDataAsset skeletonDataAsset,
bool quiet = false) {
return SkeletonRenderer.NewSpineGameObject<SkeletonAnimation>(skeletonDataAsset, quiet);
}
#endregion
/// <summary>
/// Clears the previously generated mesh, resets the skeleton's pose, and clears all previously active animations.</summary>
public override void ClearState () {
base.ClearState();
if (state != null) state.ClearTracks();
}
/// <summary>
/// Initialize this component. Attempts to load the SkeletonData and creates the internal Spine objects and buffers.</summary>
/// <param name="overwrite">If set to <c>true</c>, force overwrite an already initialized object.</param>
public override void Initialize (bool overwrite, bool quiet = false) {
if (valid && !overwrite)
return;
base.Initialize(overwrite, quiet);
if (!valid)
return;
state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
wasUpdatedAfterInit = false;
if (!string.IsNullOrEmpty(_animationName)) {
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
if (animationObject != null) {
state.SetAnimation(0, animationObject, loop);
#if UNITY_EDITOR
if (!Application.isPlaying)
Update(0f);
#endif
}
}
}
void Update () {
#if UNITY_EDITOR
if (!Application.isPlaying) {
Update(0f);
return;
}
#endif
Update(Time.deltaTime);
}
/// <summary>Progresses the AnimationState according to the given deltaTime, and applies it to the Skeleton. Use Time.deltaTime to update manually. Use deltaTime 0 to update without progressing the time.</summary>
public void Update (float deltaTime) {
if (!valid || state == null)
return;
wasUpdatedAfterInit = true;
if (updateMode < UpdateMode.OnlyAnimationStatus)
return;
UpdateAnimationStatus(deltaTime);
if (updateMode == UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation();
}
protected void UpdateAnimationStatus (float deltaTime) {
deltaTime *= timeScale;
skeleton.Update(deltaTime);
state.Update(deltaTime);
}
protected void ApplyAnimation () {
if (_BeforeApply != null)
_BeforeApply(this);
if (updateMode != UpdateMode.OnlyEventTimelines)
state.Apply(skeleton);
else
state.ApplyEventTimelinesOnly(skeleton);
if (_UpdateLocal != null)
_UpdateLocal(this);
skeleton.UpdateWorldTransform();
if (_UpdateWorld != null) {
_UpdateWorld(this);
skeleton.UpdateWorldTransform();
}
if (_UpdateComplete != null) {
_UpdateComplete(this);
}
}
public override void LateUpdate () {
// instantiation can happen from Update() after this component, leading to a missing Update() call.
if (!wasUpdatedAfterInit) Update(0);
base.LateUpdate();
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d247ba06193faa74d9335f5481b2b56c
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,825 @@
/******************************************************************************
* 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
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; } }
[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;
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
protected UpdateMode updateMode = UpdateMode.FullUpdate;
/// <summary>Update mode used when the MeshRenderer becomes invisible
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
public bool unscaledTime;
public bool allowMultipleCanvasRenderers = false;
public List<CanvasRenderer> canvasRenderers = new List<CanvasRenderer>();
protected List<RawImage> rawImages = new List<RawImage>();
protected int usedRenderersCount = 0;
// Submesh Separation
public const string SeparatorPartGameObjectName = "Part";
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
[SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0];
/// <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>
[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
public bool enableSeparatorSlots = false;
[SerializeField] protected List<Transform> separatorParts = new List<Transform>();
public List<Transform> 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 (!string.IsNullOrEmpty(initialSkinName)) {
var skin = skeleton.data.FindSkin(initialSkinName);
if (skin != null) {
if (skin == skeleton.data.defaultSkin)
skeleton.SetSkin((Skin)null);
else
skeleton.SetSkin(skin);
}
}
// Only provide visual feedback to inspector changes in Unity Editor Edit mode.
if (!Application.isPlaying) {
skeleton.ScaleX = this.initialFlipX ? -1 : 1;
skeleton.ScaleY = this.initialFlipY ? -1 : 1;
state.ClearTrack(0);
skeleton.SetToSetupPose();
if (!string.IsNullOrEmpty(startingAnimation)) {
state.SetAnimation(0, startingAnimation, startingLoop);
Update(0f);
}
}
}
} 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
/// <summary>Create a new GameObject with a SkeletonGraphic component.</summary>
/// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
public static SkeletonGraphic NewSkeletonGraphicGameObject (SkeletonDataAsset skeletonDataAsset, Transform parent, Material material) {
var sg = SkeletonGraphic.AddSkeletonGraphicComponent(new GameObject("New Spine GameObject"), skeletonDataAsset, material);
if (parent != null) sg.transform.SetParent(parent, false);
return sg;
}
/// <summary>Add a SkeletonGraphic component to a GameObject.</summary>
/// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) {
var c = gameObject.AddComponent<SkeletonGraphic>();
if (skeletonDataAsset != null) {
c.material = material;
c.skeletonDataAsset = skeletonDataAsset;
c.Initialize(false);
}
return c;
}
#endregion
#region Overrides
[System.NonSerialized] readonly Dictionary<Texture, Texture> customTextureOverride = new Dictionary<Texture, Texture>();
/// <summary>Use this Dictionary to override a Texture with a different Texture.</summary>
public Dictionary<Texture, Texture> CustomTextureOverride { get { return customTextureOverride; } }
[System.NonSerialized] readonly Dictionary<Texture, Material> customMaterialOverride = new Dictionary<Texture, Material>();
/// <summary>Use this Dictionary to override the Material where the Texture was used at the original atlas.</summary>
public Dictionary<Texture, Material> 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);
SyncRawImagesWithCanvasRenderers();
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) UpdateMesh(keepRendererCount : true);
if (allowMultipleCanvasRenderers) canvasRenderer.Clear();
}
protected override void OnDisable () {
base.OnDisable();
foreach (var canvasRenderer in canvasRenderers) {
canvasRenderer.Clear();
}
}
public virtual void Update () {
#if UNITY_EDITOR
if (!Application.isPlaying) {
Update(0f);
return;
}
#endif
if (freeze) 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)
return;
ApplyAnimation();
}
protected void SyncRawImagesWithCanvasRenderers () {
rawImages.Clear();
foreach (var canvasRenderer in canvasRenderers) {
var rawImage = canvasRenderer.GetComponent<RawImage>();
if (rawImage == null) {
rawImage = canvasRenderer.gameObject.AddComponent<RawImage>();
rawImage.maskable = this.maskable;
rawImage.raycastTarget = false;
}
rawImages.Add(rawImage);
}
}
protected void UpdateAnimationStatus (float deltaTime) {
deltaTime *= timeScale;
skeleton.Update(deltaTime);
state.Update(deltaTime);
}
protected void ApplyAnimation () {
if (BeforeApply != null)
BeforeApply(this);
if (updateMode != UpdateMode.OnlyEventTimelines)
state.Apply(skeleton);
else
state.ApplyEventTimelinesOnly(skeleton);
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;
UpdateMesh();
}
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;
var 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 { return skeleton == null ? null : skeleton.data; } }
public bool IsValid { get { return skeleton != null; } }
public delegate void SkeletonRendererDelegate (SkeletonGraphic skeletonGraphic);
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
public event SkeletonRendererDelegate OnRebuild;
/// <summary>OnMeshAndMaterialsUpdated is at the end of LateUpdate after the Mesh and
/// all materials have been updated.</summary>
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<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
readonly ExposedList<Mesh> meshes = new ExposedList<Mesh>();
public Mesh GetLastMesh () {
return meshBuffers.GetCurrent().mesh;
}
public bool MatchRectTransformWithBounds () {
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) {
var canvasRenderer = canvasRenderers[i];
if (!canvasRenderer.gameObject.activeSelf)
continue;
Mesh mesh = meshes.Items[i];
if (mesh == null || mesh.vertexCount == 0)
continue;
mesh.RecalculateBounds();
var 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) {
var size = combinedBounds.size;
var center = combinedBounds.center;
var p = new Vector2(
0.5f - (center.x / size.x),
0.5f - (center.y / size.y)
);
this.rectTransform.sizeDelta = size;
this.rectTransform.pivot = p;
}
public event UpdateBonesDelegate BeforeApply;
public event UpdateBonesDelegate UpdateLocal;
public event UpdateBonesDelegate UpdateWorld;
public event UpdateBonesDelegate UpdateComplete;
/// <summary> Occurs after the vertex data populated every frame, before the vertices are pushed into the mesh.</summary>
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();
DisposeMeshBuffers();
}
public void TrimRenderers () {
var newList = new List<CanvasRenderer>();
foreach (var 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;
SyncRawImagesWithCanvasRenderers();
}
public void Initialize (bool overwrite) {
if (this.IsValid && !overwrite) return;
if (this.skeletonDataAsset == null) return;
var skeletonData = this.skeletonDataAsset.GetSkeletonData(false);
if (skeletonData == null) return;
if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].MaterialCount <= 0) return;
this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
if (state == null) {
Clear();
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]));
wasUpdatedAfterInit = false;
if (!string.IsNullOrEmpty(startingAnimation)) {
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
if (animationObject != null) {
state.SetAnimation(0, animationObject, startingLoop);
#if UNITY_EDITOR
if (!Application.isPlaying)
Update(0f);
#endif
}
}
if (OnRebuild != null)
OnRebuild(this);
}
public void UpdateMesh (bool keepRendererCount = false) {
if (!this.IsValid) return;
skeleton.SetColor(this.color);
var currentInstructions = this.currentInstructions;
if (!this.allowMultipleCanvasRenderers) {
UpdateMeshSingleCanvasRenderer();
}
else {
UpdateMeshMultipleCanvasRenderers(currentInstructions, keepRendererCount);
}
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<MeshRendererBuffers.SmartMesh>();
}
}
protected void DisposeMeshBuffers () {
if (meshBuffers != null) {
meshBuffers.GetNext().Dispose();
meshBuffers.GetNext().Dispose();
meshBuffers = null;
}
}
protected void UpdateMeshSingleCanvasRenderer () {
if (canvasRenderers.Count > 0)
DisableUnusedCanvasRenderers(usedCount : 0);
var smartMesh = meshBuffers.GetNext();
MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null);
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
meshGenerator.Begin();
if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) {
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);
var mesh = smartMesh.mesh;
meshGenerator.FillVertexData(mesh);
if (updateTriangles) meshGenerator.FillTriangles(mesh);
meshGenerator.FillLateVertexData(mesh);
canvasRenderer.SetMesh(mesh);
smartMesh.instructionUsed.Set(currentInstructions);
if (currentInstructions.submeshInstructions.Count > 0) {
var material = currentInstructions.submeshInstructions.Items[0].material;
if (material != null && baseTexture != material.mainTexture) {
baseTexture = material.mainTexture;
if (overrideTexture == null)
canvasRenderer.SetTexture(this.mainTexture);
}
}
//this.UpdateMaterial(); // note: This would allocate memory.
usedRenderersCount = 0;
}
protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions, bool keepRendererCount) {
MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null,
enableSeparatorSlots ? separatorSlots : null,
enableSeparatorSlots ? separatorSlots.Count > 0 : false,
false);
int submeshCount = currentInstructions.submeshInstructions.Count;
if (keepRendererCount && submeshCount != usedRenderersCount)
return;
EnsureCanvasRendererCount(submeshCount);
EnsureMeshesCount(submeshCount);
EnsureSeparatorPartCount();
var c = canvas;
float scale = (c == null) ? 100 : c.referencePixelsPerUnit;
// Generate meshes.
var meshesItems = meshes.Items;
bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0);
int separatorSlotGroupIndex = 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;
}
}
int targetSiblingIndex = 0;
for (int i = 0; i < submeshCount; i++) {
var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
meshGenerator.Begin();
meshGenerator.AddSubmesh(submeshInstructionItem);
var targetMesh = meshesItems[i];
meshGenerator.ScaleVertexData(scale);
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
meshGenerator.FillVertexData(targetMesh);
meshGenerator.FillTriangles(targetMesh);
meshGenerator.FillLateVertexData(targetMesh);
var submeshMaterial = submeshInstructionItem.material;
var canvasRenderer = canvasRenderers[i];
if (i >= usedRenderersCount)
canvasRenderer.gameObject.SetActive(true);
canvasRenderer.SetMesh(targetMesh);
canvasRenderer.materialCount = 1;
if (canvasRenderer.transform.parent != parent.transform) {
canvasRenderer.transform.SetParent(parent.transform, false);
canvasRenderer.transform.localPosition = Vector3.zero;
}
canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++);
if (submeshInstructionItem.forceSeparate) {
targetSiblingIndex = 0;
parent = separatorParts[++separatorSlotGroupIndex];
}
if (useOriginalTextureAndMaterial)
canvasRenderer.SetMaterial(this.materialForRendering, submeshMaterial.mainTexture);
else {
var originalTexture = submeshMaterial.mainTexture;
Material usedMaterial;
Texture usedTexture;
if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial))
usedMaterial = material;
if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture))
usedTexture = originalTexture;
canvasRenderer.SetMaterial(usedMaterial, usedTexture);
}
}
DisableUnusedCanvasRenderers(usedCount : submeshCount);
usedRenderersCount = submeshCount;
}
protected void EnsureCanvasRendererCount (int targetCount) {
#if UNITY_EDITOR
RemoveNullCanvasRenderers();
#endif
int currentCount = canvasRenderers.Count;
for (int i = currentCount; i < targetCount; ++i) {
var go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform));
go.transform.SetParent(this.transform, false);
go.transform.localPosition = Vector3.zero;
var canvasRenderer = go.AddComponent<CanvasRenderer>();
canvasRenderers.Add(canvasRenderer);
var rawImage = go.AddComponent<RawImage>();
rawImage.maskable = this.maskable;
rawImage.raycastTarget = false;
rawImages.Add(rawImage);
}
}
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);
}
}
}
}
#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 DestroyMeshes () {
foreach (var 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) {
var 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) {
var 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
}
}
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: d85b887af7e6c3f45a2e2d2920d641bc
timeCreated: 1455576193
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences:
- m_Material: {fileID: 2100000, guid: b66cf7a186d13054989b33a5c90044e4, type: 2}
- skeletonDataAsset: {instanceID: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,661 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
[RequireComponent(typeof(Animator))]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonMecanim-Component")]
public class SkeletonMecanim : SkeletonRenderer, ISkeletonAnimation {
[SerializeField] protected MecanimTranslator translator;
public MecanimTranslator Translator { get { return translator; } }
private bool wasUpdatedAfterInit = true;
#region Bone Callbacks (ISkeletonAnimation)
protected event UpdateBonesDelegate _BeforeApply;
protected event UpdateBonesDelegate _UpdateLocal;
protected event UpdateBonesDelegate _UpdateWorld;
protected event UpdateBonesDelegate _UpdateComplete;
/// <summary>
/// Occurs before the animations are applied.
/// Use this callback when you want to change the skeleton state before animations are applied on top.
/// </summary>
public event UpdateBonesDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
/// <summary>
/// Occurs after the animations are applied and before world space values are resolved.
/// Use this callback when you want to set bone local values.</summary>
public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Using this callback will cause the world space values to be solved an extra time.
/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
/// This callback can also be used when setting world position and the bone matrix.</summary>
public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
#endregion
public override void Initialize (bool overwrite, bool quiet = false) {
if (valid && !overwrite)
return;
base.Initialize(overwrite, quiet);
if (!valid)
return;
if (translator == null) translator = new MecanimTranslator();
translator.Initialize(GetComponent<Animator>(), this.skeletonDataAsset);
wasUpdatedAfterInit = false;
}
public void Update () {
if (!valid) return;
wasUpdatedAfterInit = true;
// animation status is kept by Mecanim Animator component
if (updateMode <= UpdateMode.OnlyAnimationStatus)
return;
ApplyAnimation();
}
protected void ApplyAnimation () {
if (_BeforeApply != null)
_BeforeApply(this);
#if UNITY_EDITOR
var translatorAnimator = translator.Animator;
if (translatorAnimator != null && !translatorAnimator.isInitialized)
translatorAnimator.Rebind();
if (Application.isPlaying) {
translator.Apply(skeleton);
}
else {
if (translatorAnimator != null && translatorAnimator.isInitialized &&
translatorAnimator.isActiveAndEnabled && translatorAnimator.runtimeAnimatorController != null) {
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController" with prefabs
translatorAnimator.Rebind();
translator.Apply(skeleton);
}
}
#else
translator.Apply(skeleton);
#endif
// UpdateWorldTransform and Bone Callbacks
{
if (_UpdateLocal != null)
_UpdateLocal(this);
skeleton.UpdateWorldTransform();
if (_UpdateWorld != null) {
_UpdateWorld(this);
skeleton.UpdateWorldTransform();
}
if (_UpdateComplete != null)
_UpdateComplete(this);
}
}
public override void LateUpdate () {
// instantiation can happen from Update() after this component, leading to a missing Update() call.
if (!wasUpdatedAfterInit) Update();
base.LateUpdate();
}
[System.Serializable]
public class MecanimTranslator {
const float WeightEpsilon = 0.0001f;
#region Inspector
public bool autoReset = true;
public bool useCustomMixMode = true;
public MixMode[] layerMixModes = new MixMode[0];
public MixBlend[] layerBlendModes = new MixBlend[0];
#endregion
public delegate void OnClipAppliedDelegate (Spine.Animation clip, int layerIndex, float weight,
float time, float lastTime, bool playsBackward);
protected event OnClipAppliedDelegate _OnClipApplied;
public event OnClipAppliedDelegate OnClipApplied { add { _OnClipApplied += value; } remove { _OnClipApplied -= value; } }
public enum MixMode { AlwaysMix, MixNext, Hard }
readonly Dictionary<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>(IntEqualityComparer.Instance);
readonly Dictionary<AnimationClip, int> clipNameHashCodeTable = new Dictionary<AnimationClip, int>(AnimationClipEqualityComparer.Instance);
readonly List<Animation> previousAnimations = new List<Animation>();
protected class ClipInfos {
public bool isInterruptionActive = false;
public bool isLastFrameOfInterruption = false;
public int clipInfoCount = 0;
public int nextClipInfoCount = 0;
public int interruptingClipInfoCount = 0;
public readonly List<AnimatorClipInfo> clipInfos = new List<AnimatorClipInfo>();
public readonly List<AnimatorClipInfo> nextClipInfos = new List<AnimatorClipInfo>();
public readonly List<AnimatorClipInfo> interruptingClipInfos = new List<AnimatorClipInfo>();
public AnimatorStateInfo stateInfo;
public AnimatorStateInfo nextStateInfo;
public AnimatorStateInfo interruptingStateInfo;
public float interruptingClipTimeAddition = 0;
}
protected ClipInfos[] layerClipInfos = new ClipInfos[0];
Animator animator;
public Animator Animator { get { return this.animator; } }
public int MecanimLayerCount {
get {
if (!animator)
return 0;
return animator.layerCount;
}
}
public string[] MecanimLayerNames {
get {
if (!animator)
return new string[0];
string[] layerNames = new string[animator.layerCount];
for (int i = 0; i < animator.layerCount; ++i) {
layerNames[i] = animator.GetLayerName(i);
}
return layerNames;
}
}
public void Initialize(Animator animator, SkeletonDataAsset skeletonDataAsset) {
this.animator = animator;
previousAnimations.Clear();
animationTable.Clear();
var data = skeletonDataAsset.GetSkeletonData(true);
foreach (var a in data.Animations)
animationTable.Add(a.Name.GetHashCode(), a);
clipNameHashCodeTable.Clear();
ClearClipInfosForLayers();
}
private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo,
int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useClipWeight1 = false) {
float weight = info.weight * layerWeight;
if (weight < WeightEpsilon)
return false;
var clip = GetAnimation(info.clip);
if (clip == null)
return false;
var time = AnimationTime(stateInfo.normalizedTime, info.clip.length,
info.clip.isLooping, stateInfo.speed < 0);
weight = useClipWeight1 ? layerWeight : weight;
clip.Apply(skeleton, 0, time, info.clip.isLooping, null,
weight, layerBlendMode, MixDirection.In);
if (_OnClipApplied != null)
OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight);
return true;
}
private bool ApplyInterruptionAnimation (Skeleton skeleton,
bool interpolateWeightTo1, AnimatorClipInfo info, AnimatorStateInfo stateInfo,
int layerIndex, float layerWeight, MixBlend layerBlendMode, float interruptingClipTimeAddition,
bool useClipWeight1 = false) {
float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
float weight = clipWeight * layerWeight;
if (weight < WeightEpsilon)
return false;
var clip = GetAnimation(info.clip);
if (clip == null)
return false;
var time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition,
info.clip.length, stateInfo.speed < 0);
weight = useClipWeight1 ? layerWeight : weight;
clip.Apply(skeleton, 0, time, info.clip.isLooping, null,
weight, layerBlendMode, MixDirection.In);
if (_OnClipApplied != null) {
OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight);
}
return true;
}
private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo,
int layerIndex, float time, bool isLooping, float weight) {
float speedFactor = stateInfo.speedMultiplier * stateInfo.speed;
float lastTime = time - (Time.deltaTime * speedFactor);
if (isLooping && clip.duration != 0) {
time %= clip.duration;
lastTime %= clip.duration;
}
_OnClipApplied(clip, layerIndex, weight, time, lastTime, speedFactor < 0);
}
public void Apply (Skeleton skeleton) {
#if UNITY_EDITOR
if (!Application.isPlaying) {
GetLayerBlendModes();
}
#endif
if (layerMixModes.Length < animator.layerCount) {
int oldSize = layerMixModes.Length;
System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
for (int layer = oldSize; layer < animator.layerCount; ++layer) {
bool isAdditiveLayer = false;
if (layer < layerBlendModes.Length)
isAdditiveLayer = layerBlendModes[layer] == MixBlend.Add;
layerMixModes[layer] = isAdditiveLayer ? MixMode.AlwaysMix : MixMode.MixNext;
}
}
InitClipInfosForLayers();
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
GetStateUpdatesFromAnimator(layer);
}
// Clear Previous
if (autoReset) {
var previousAnimations = this.previousAnimations;
for (int i = 0, n = previousAnimations.Count; i < n; i++)
previousAnimations[i].SetKeyedItemsToSetupPose(skeleton);
previousAnimations.Clear();
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
if (layerWeight <= 0) continue;
AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
bool hasNext = nextStateInfo.fullPathHash != 0;
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
IList<AnimatorClipInfo> clipInfo, nextClipInfo, interruptingClipInfo;
bool isInterruptionActive, shallInterpolateWeightTo1;
GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1);
for (int c = 0; c < clipInfoCount; c++) {
var info = clipInfo[c];
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
var clip = GetAnimation(info.clip);
if (clip != null)
previousAnimations.Add(clip);
}
if (hasNext) {
for (int c = 0; c < nextClipInfoCount; c++) {
var info = nextClipInfo[c];
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
var clip = GetAnimation(info.clip);
if (clip != null)
previousAnimations.Add(clip);
}
}
if (isInterruptionActive) {
for (int c = 0; c < interruptingClipInfoCount; c++)
{
var info = interruptingClipInfo[c];
float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
float weight = clipWeight * layerWeight; if (weight < WeightEpsilon) continue;
var clip = GetAnimation(info.clip);
if (clip != null)
previousAnimations.Add(clip);
}
}
}
}
// Apply
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
bool isInterruptionActive;
AnimatorStateInfo stateInfo;
AnimatorStateInfo nextStateInfo;
AnimatorStateInfo interruptingStateInfo;
float interruptingClipTimeAddition;
GetAnimatorStateInfos(layer, out isInterruptionActive, out stateInfo, out nextStateInfo, out interruptingStateInfo, out interruptingClipTimeAddition);
bool hasNext = nextStateInfo.fullPathHash != 0;
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
IList<AnimatorClipInfo> clipInfo, nextClipInfo, interruptingClipInfo;
bool interpolateWeightTo1;
GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1);
MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace;
MixMode mode = GetMixMode(layer, layerBlendMode);
if (mode == MixMode.AlwaysMix) {
// Always use Mix instead of Applying the first non-zero weighted clip.
for (int c = 0; c < clipInfoCount; c++) {
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode);
}
if (hasNext) {
for (int c = 0; c < nextClipInfoCount; c++) {
ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode);
}
}
if (isInterruptionActive) {
for (int c = 0; c < interruptingClipInfoCount; c++)
{
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
interruptingClipInfo[c], interruptingStateInfo,
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition);
}
}
} else { // case MixNext || Hard
// Apply first non-zero weighted clip
int c = 0;
for (; c < clipInfoCount; c++) {
if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true))
continue;
++c; break;
}
// Mix the rest
for (; c < clipInfoCount; c++) {
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode);
}
c = 0;
if (hasNext) {
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if (mode == MixMode.Hard) {
for (; c < nextClipInfoCount; c++) {
if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true))
continue;
++c; break;
}
}
// Mix the rest
for (; c < nextClipInfoCount; c++) {
if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode))
continue;
}
}
c = 0;
if (isInterruptionActive) {
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if (mode == MixMode.Hard) {
for (; c < interruptingClipInfoCount; c++) {
if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
interruptingClipInfo[c], interruptingStateInfo,
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useClipWeight1:true)) {
++c; break;
}
}
}
// Mix the rest
for (; c < interruptingClipInfoCount; c++) {
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
interruptingClipInfo[c], interruptingStateInfo,
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition);
}
}
}
}
}
public KeyValuePair<Spine.Animation, float> GetActiveAnimationAndTime (int layer) {
if (layer >= layerClipInfos.Length)
return new KeyValuePair<Spine.Animation, float>(null, 0);
var layerInfos = layerClipInfos[layer];
bool isInterruptionActive = layerInfos.isInterruptionActive;
AnimationClip clip = null;
Spine.Animation animation = null;
AnimatorStateInfo stateInfo;
if (isInterruptionActive && layerInfos.interruptingClipInfoCount > 0) {
clip = layerInfos.interruptingClipInfos[0].clip;
stateInfo = layerInfos.interruptingStateInfo;
}
else {
clip = layerInfos.clipInfos[0].clip;
stateInfo = layerInfos.stateInfo;
}
animation = GetAnimation(clip);
float time = AnimationTime(stateInfo.normalizedTime, clip.length,
clip.isLooping, stateInfo.speed < 0);
return new KeyValuePair<Animation, float>(animation, time);
}
static float AnimationTime (float normalizedTime, float clipLength, bool loop, bool reversed) {
float time = AnimationTime(normalizedTime, clipLength, reversed);
if (loop) return time;
const float EndSnapEpsilon = 1f / 30f; // Workaround for end-duration keys not being applied.
return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
}
static float AnimationTime (float normalizedTime, float clipLength, bool reversed) {
if (reversed)
normalizedTime = (1 - normalizedTime);
if (normalizedTime < 0.0f)
normalizedTime = (normalizedTime % 1.0f) + 1.0f;
return normalizedTime * clipLength;
}
void InitClipInfosForLayers () {
if (layerClipInfos.Length < animator.layerCount) {
System.Array.Resize<ClipInfos>(ref layerClipInfos, animator.layerCount);
for (int layer = 0, n = animator.layerCount; layer < n; ++layer) {
if (layerClipInfos[layer] == null)
layerClipInfos[layer] = new ClipInfos();
}
}
}
void ClearClipInfosForLayers () {
for (int layer = 0, n = layerClipInfos.Length; layer < n; ++layer) {
if (layerClipInfos[layer] == null)
layerClipInfos[layer] = new ClipInfos();
else {
layerClipInfos[layer].isInterruptionActive = false;
layerClipInfos[layer].isLastFrameOfInterruption = false;
layerClipInfos[layer].clipInfos.Clear();
layerClipInfos[layer].nextClipInfos.Clear();
layerClipInfos[layer].interruptingClipInfos.Clear();
}
}
}
private MixMode GetMixMode (int layer, MixBlend layerBlendMode) {
if (useCustomMixMode) {
MixMode mode = layerMixModes[layer];
// Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as
// with override layers, so we use AlwaysMix instead to use the proper weights.
// AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight).
if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) {
mode = MixMode.AlwaysMix;
layerMixModes[layer] = mode;
}
return mode;
}
else {
return layerBlendMode == MixBlend.Add ? MixMode.AlwaysMix : MixMode.MixNext;
}
}
#if UNITY_EDITOR
void GetLayerBlendModes() {
if (layerBlendModes.Length < animator.layerCount) {
System.Array.Resize<MixBlend>(ref layerBlendModes, animator.layerCount);
}
for (int layer = 0, n = animator.layerCount; layer < n; ++layer) {
var controller = animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController;
if (controller != null) {
layerBlendModes[layer] = MixBlend.First;
if (layer > 0) {
layerBlendModes[layer] = controller.layers[layer].blendingMode == UnityEditor.Animations.AnimatorLayerBlendingMode.Additive ?
MixBlend.Add : MixBlend.Replace;
}
}
}
}
#endif
void GetStateUpdatesFromAnimator (int layer) {
var layerInfos = layerClipInfos[layer];
int clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer);
int nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer);
var clipInfos = layerInfos.clipInfos;
var nextClipInfos = layerInfos.nextClipInfos;
var interruptingClipInfos = layerInfos.interruptingClipInfos;
layerInfos.isInterruptionActive = (clipInfoCount == 0 && clipInfos.Count != 0 &&
nextClipInfoCount == 0 && nextClipInfos.Count != 0);
// Note: during interruption, GetCurrentAnimatorClipInfoCount and GetNextAnimatorClipInfoCount
// are returning 0 in calls above. Therefore we keep previous clipInfos and nextClipInfos
// until the interruption is over.
if (layerInfos.isInterruptionActive) {
// Note: The last frame of a transition interruption
// will have fullPathHash set to 0, therefore we have to use previous
// frame's infos about interruption clips and correct some values
// accordingly (normalizedTime and weight).
var interruptingStateInfo = animator.GetNextAnimatorStateInfo(layer);
layerInfos.isLastFrameOfInterruption = interruptingStateInfo.fullPathHash == 0;
if (!layerInfos.isLastFrameOfInterruption) {
animator.GetNextAnimatorClipInfo(layer, interruptingClipInfos);
layerInfos.interruptingClipInfoCount = interruptingClipInfos.Count;
float oldTime = layerInfos.interruptingStateInfo.normalizedTime;
float newTime = interruptingStateInfo.normalizedTime;
layerInfos.interruptingClipTimeAddition = newTime - oldTime;
layerInfos.interruptingStateInfo = interruptingStateInfo;
}
} else {
layerInfos.clipInfoCount = clipInfoCount;
layerInfos.nextClipInfoCount = nextClipInfoCount;
layerInfos.interruptingClipInfoCount = 0;
layerInfos.isLastFrameOfInterruption = false;
if (clipInfos.Capacity < clipInfoCount) clipInfos.Capacity = clipInfoCount;
if (nextClipInfos.Capacity < nextClipInfoCount) nextClipInfos.Capacity = nextClipInfoCount;
animator.GetCurrentAnimatorClipInfo(layer, clipInfos);
animator.GetNextAnimatorClipInfo(layer, nextClipInfos);
layerInfos.stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
layerInfos.nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
}
}
void GetAnimatorClipInfos (
int layer,
out bool isInterruptionActive,
out int clipInfoCount,
out int nextClipInfoCount,
out int interruptingClipInfoCount,
out IList<AnimatorClipInfo> clipInfo,
out IList<AnimatorClipInfo> nextClipInfo,
out IList<AnimatorClipInfo> interruptingClipInfo,
out bool shallInterpolateWeightTo1) {
var layerInfos = layerClipInfos[layer];
isInterruptionActive = layerInfos.isInterruptionActive;
clipInfoCount = layerInfos.clipInfoCount;
nextClipInfoCount = layerInfos.nextClipInfoCount;
interruptingClipInfoCount = layerInfos.interruptingClipInfoCount;
clipInfo = layerInfos.clipInfos;
nextClipInfo = layerInfos.nextClipInfos;
interruptingClipInfo = isInterruptionActive ? layerInfos.interruptingClipInfos : null;
shallInterpolateWeightTo1 = layerInfos.isLastFrameOfInterruption;
}
void GetAnimatorStateInfos (
int layer,
out bool isInterruptionActive,
out AnimatorStateInfo stateInfo,
out AnimatorStateInfo nextStateInfo,
out AnimatorStateInfo interruptingStateInfo,
out float interruptingClipTimeAddition) {
var layerInfos = layerClipInfos[layer];
isInterruptionActive = layerInfos.isInterruptionActive;
stateInfo = layerInfos.stateInfo;
nextStateInfo = layerInfos.nextStateInfo;
interruptingStateInfo = layerInfos.interruptingStateInfo;
interruptingClipTimeAddition = layerInfos.isLastFrameOfInterruption ? layerInfos.interruptingClipTimeAddition : 0;
}
Spine.Animation GetAnimation (AnimationClip clip) {
int clipNameHashCode;
if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
clipNameHashCode = clip.name.GetHashCode();
clipNameHashCodeTable.Add(clip, clipNameHashCode);
}
Spine.Animation animation;
animationTable.TryGetValue(clipNameHashCode, out animation);
return animation;
}
class AnimationClipEqualityComparer : IEqualityComparer<AnimationClip> {
internal static readonly IEqualityComparer<AnimationClip> Instance = new AnimationClipEqualityComparer();
public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
}
class IntEqualityComparer : IEqualityComparer<int> {
internal static readonly IEqualityComparer<int> Instance = new IntEqualityComparer();
public bool Equals (int x, int y) { return x == y; }
public int GetHashCode(int o) { return o; }
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f9db98c60740638449864eb028fbe7ad
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 3a361f5ac799a5149b340f9e20da27d1
folderAsset: yes
timeCreated: 1457405502
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,151 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
namespace Spine.Unity {
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderSeparator")]
public class SkeletonPartsRenderer : MonoBehaviour {
#region Properties
MeshGenerator meshGenerator;
public MeshGenerator MeshGenerator {
get {
LazyIntialize();
return meshGenerator;
}
}
MeshRenderer meshRenderer;
public MeshRenderer MeshRenderer {
get {
LazyIntialize();
return meshRenderer;
}
}
MeshFilter meshFilter;
public MeshFilter MeshFilter {
get {
LazyIntialize();
return meshFilter;
}
}
#endregion
#region Callback Delegates
public delegate void SkeletonPartsRendererDelegate (SkeletonPartsRenderer skeletonPartsRenderer);
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
/// all materials have been updated.</summary>
public event SkeletonPartsRendererDelegate OnMeshAndMaterialsUpdated;
#endregion
MeshRendererBuffers buffers;
SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
void LazyIntialize () {
if (buffers == null) {
buffers = new MeshRendererBuffers();
buffers.Initialize();
if (meshGenerator != null) return;
meshGenerator = new MeshGenerator();
meshFilter = GetComponent<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();
currentInstructions.Clear();
}
}
public void ClearMesh () {
LazyIntialize();
meshFilter.sharedMesh = null;
}
public void RenderParts (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
LazyIntialize();
// STEP 1: Create instruction
var smartMesh = buffers.GetNextMesh();
currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh);
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
// STEP 2: Generate mesh buffers.
var currentInstructionsSubmeshesItems = currentInstructions.submeshInstructions.Items;
meshGenerator.Begin();
if (currentInstructions.hasActiveClipping) {
for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++)
meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i], updateTriangles);
} else {
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
}
buffers.UpdateSharedMaterials(currentInstructions.submeshInstructions);
// STEP 3: modify mesh.
var mesh = smartMesh.mesh;
if (meshGenerator.VertexCount <= 0) { // Clear an empty mesh
updateTriangles = false;
mesh.Clear();
} else {
meshGenerator.FillVertexData(mesh);
if (updateTriangles) {
meshGenerator.FillTriangles(mesh);
meshRenderer.sharedMaterials = buffers.GetUpdatedSharedMaterialsArray();
} else if (buffers.MaterialsChangedInLastUpdate()) {
meshRenderer.sharedMaterials = buffers.GetUpdatedSharedMaterialsArray();
}
meshGenerator.FillLateVertexData(mesh);
}
meshFilter.sharedMesh = mesh;
smartMesh.instructionUsed.Set(currentInstructions);
if (OnMeshAndMaterialsUpdated != null)
OnMeshAndMaterialsUpdated(this);
}
public void SetPropertyBlock (MaterialPropertyBlock block) {
LazyIntialize();
meshRenderer.SetPropertyBlock(block);
}
public static SkeletonPartsRenderer NewPartsRendererGameObject (Transform parent, string name, int sortingOrder = 0) {
var go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
go.transform.SetParent(parent, false);
var returnComponent = go.AddComponent<SkeletonPartsRenderer>();
returnComponent.MeshRenderer.sortingOrder = sortingOrder;
return returnComponent;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1c0b968d1e7333b499e347acb644f1c1
timeCreated: 1458045480
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,269 @@
/******************************************************************************
* 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<Spine.Unity.SkeletonPartsRenderer> partsRenderers = new List<SkeletonPartsRenderer>();
#if UNITY_EDITOR
void Reset () {
if (skeletonRenderer == null)
skeletonRenderer = GetComponent<SkeletonRenderer>();
}
#endif
#endregion
#region Callback Delegates
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
/// all materials have been updated.</summary>
public event SkeletonRenderer.SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
#endregion
#region Runtime Instantiation
/// <summary>Adds a SkeletonRenderSeparator and child SkeletonPartsRenderer GameObjects to a given SkeletonRenderer.</summary>
/// <returns>The to skeleton renderer.</returns>
/// <param name="skeletonRenderer">The target SkeletonRenderer or SkeletonAnimation.</param>
/// <param name="sortingLayerID">Sorting layer to be used for the parts renderers.</param>
/// <param name="extraPartsRenderers">Number of additional SkeletonPartsRenderers on top of the ones determined by counting the number of separator slots.</param>
/// <param name="sortingOrderIncrement">The integer to increment the sorting order per SkeletonPartsRenderer to separate them.</param>
/// <param name="baseSortingOrder">The sorting order value of the first SkeletonPartsRenderer.</param>
/// <param name="addMinimumPartsRenderers">If set to <c>true</c>, a minimum number of SkeletonPartsRenderer GameObjects (determined by separatorSlots.Count + 1) will be added.</param>
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<SkeletonRenderSeparator>();
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;
}
/// <summary>Add a child SkeletonPartsRenderer GameObject to this SkeletonRenderSeparator.</summary>
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<MeshRenderer>();
#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();
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 5c70a5b35f6ff2541aed8e8346b7e4d5
timeCreated: 1457405791
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,6 @@
SkeletonRenderSeparator
=======================
Dependencies:
- SkeletonPartsRenderer uses the `ArraysMeshGenerator` class in `Spine.Unity.MeshGeneration`
- It requires `SPINE_OPTIONAL_RENDEROVERRIDE` to be #defined in `SkeletonRenderer.cs`.
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f0e413eeb00eabc46bde6dbd7aaaa76c
timeCreated: 1469110129
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,711 @@
/******************************************************************************
* 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
#if UNITY_2018_1_OR_NEWER
#define PER_MATERIAL_PROPERTY_BLOCKS
#endif
#if UNITY_2017_1_OR_NEWER
#define BUILT_IN_SPRITE_MASK_COMPONENT
#endif
#if UNITY_2019_3_OR_NEWER
#define CONFIGURABLE_ENTER_PLAY_MODE
#endif
#define SPINE_OPTIONAL_RENDEROVERRIDE
#define SPINE_OPTIONAL_MATERIALOVERRIDE
using System.Collections.Generic;
using UnityEngine;
namespace Spine.Unity {
/// <summary>Base class of animated Spine skeleton components. This component manages and renders a skeleton.</summary>
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderer-Component")]
public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
public SkeletonDataAsset skeletonDataAsset;
#region Initialization settings
/// <summary>Skin name to use when the Skeleton is initialized.</summary>
[SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
/// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
/// Otherwise any changes will be overwritten by the next inspector update.</summary>
#if UNITY_EDITOR
public bool EditorSkipSkinSync {
get { return editorSkipSkinSync; }
set { editorSkipSkinSync = value; }
}
protected bool editorSkipSkinSync = false;
#endif
/// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
public bool initialFlipX, initialFlipY;
#endregion
#region Advanced Render Settings
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
protected UpdateMode updateMode = UpdateMode.FullUpdate;
/// <summary>Update mode used when the MeshRenderer becomes invisible
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
// Submesh Separation
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")][SerializeField][SpineSlot] protected string[] separatorSlotNames = new string[0];
/// <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>
[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
// Render Settings
[Range(-0.1f, 0f)] public float zSpacing;
/// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
public bool useClipping = true;
/// <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>
public bool immutableTriangles = false;
/// <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>
public bool pmaVertexColors = true;
/// <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>
public bool clearStateOnDisable = false;
/// <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>
public bool tintBlack = false;
/// <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>
/// <remarks>This disables SkeletonRenderSeparator functionality.</remarks>
public bool singleSubmesh = false;
#if PER_MATERIAL_PROPERTY_BLOCKS
/// <summary> Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. "A B A").
/// If true, GPU instancing is disabled at all materials and MaterialPropertyBlocks are assigned at each
/// material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect
/// draw order (e.g. "A1 B A2" changed to "A1A2 B").
/// You can disable this parameter when everything is drawn correctly to save the additional performance cost.
/// </summary>
public bool fixDrawOrder = false;
#endif
/// <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>
[UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] public bool addNormals = false;
/// <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>
public bool calculateTangents = false;
#if BUILT_IN_SPRITE_MASK_COMPONENT
/// <summary>This enum controls the mode under which the sprite will interact with the masking system.</summary>
/// <remarks>Interaction modes with <see cref="UnityEngine.SpriteMask"/> components are identical to Unity's <see cref="UnityEngine.SpriteRenderer"/>,
/// see https://docs.unity3d.com/ScriptReference/SpriteMaskInteraction.html. </remarks>
public SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
[System.Serializable]
public class SpriteMaskInteractionMaterials {
public bool AnyMaterialCreated {
get {
return materialsMaskDisabled.Length > 0 ||
materialsInsideMask.Length > 0 ||
materialsOutsideMask.Length > 0;
}
}
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.None"/>.</summary>
public Material[] materialsMaskDisabled = new Material[0];
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
public Material[] materialsInsideMask = new Material[0];
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
public Material[] materialsOutsideMask = new Material[0];
}
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes.</summary>
public SpriteMaskInteractionMaterials maskMaterials = new SpriteMaskInteractionMaterials();
/// <summary>Shader property ID used for the Stencil comparison function.</summary>
public static readonly int STENCIL_COMP_PARAM_ID = Shader.PropertyToID("_StencilComp");
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.None"/>.</summary>
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_NONE = UnityEngine.Rendering.CompareFunction.Always;
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE = UnityEngine.Rendering.CompareFunction.LessEqual;
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE = UnityEngine.Rendering.CompareFunction.Greater;
#if UNITY_EDITOR
private static bool haveStencilParametersBeenFixed = false;
#endif
#endif // #if BUILT_IN_SPRITE_MASK_COMPONENT
#endregion
#region Overrides
#if SPINE_OPTIONAL_RENDEROVERRIDE
// These are API for anything that wants to take over rendering for a SkeletonRenderer.
public bool disableRenderingOnOverride = true;
public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
event InstructionDelegate generateMeshOverride;
/// <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>
public event InstructionDelegate GenerateMeshOverride {
add {
generateMeshOverride += value;
if (disableRenderingOnOverride && generateMeshOverride != null) {
Initialize(false);
if (meshRenderer)
meshRenderer.enabled = false;
}
}
remove {
generateMeshOverride -= value;
if (disableRenderingOnOverride && generateMeshOverride == null) {
Initialize(false);
if (meshRenderer)
meshRenderer.enabled = true;
}
}
}
/// <summary> Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.</summary>
public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
#endif
#if SPINE_OPTIONAL_MATERIALOVERRIDE
[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
/// <summary>Use this Dictionary to override a Material with a different Material.</summary>
public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
#endif
[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
/// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
#endregion
#region Mesh Generator
[System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
readonly MeshGenerator meshGenerator = new MeshGenerator();
[System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
#endregion
#region Cached component references
MeshRenderer meshRenderer;
MeshFilter meshFilter;
#endregion
#region Skeleton
[System.NonSerialized] public bool valid;
[System.NonSerialized] public Skeleton skeleton;
public Skeleton Skeleton {
get {
Initialize(false);
return skeleton;
}
}
#endregion
public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
public event SkeletonRendererDelegate OnRebuild;
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
/// all materials have been updated.</summary>
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
#region Runtime Instantiation
public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset, quiet);
}
/// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
/// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
var c = gameObject.AddComponent<T>();
if (skeletonDataAsset != null) {
c.skeletonDataAsset = skeletonDataAsset;
c.Initialize(false, quiet);
}
return c;
}
/// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
public void SetMeshSettings (MeshGenerator.Settings settings) {
this.calculateTangents = settings.calculateTangents;
this.immutableTriangles = settings.immutableTriangles;
this.pmaVertexColors = settings.pmaVertexColors;
this.tintBlack = settings.tintBlack;
this.useClipping = settings.useClipping;
this.zSpacing = settings.zSpacing;
this.meshGenerator.settings = settings;
}
#endregion
public virtual void Awake () {
Initialize(false);
updateMode = updateWhenInvisible;
}
#if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE
public virtual void Start () {
Initialize(false);
}
#endif
void OnDisable () {
if (clearStateOnDisable && valid)
ClearState();
}
void OnDestroy () {
rendererBuffers.Dispose();
valid = false;
}
/// <summary>
/// Clears the previously generated mesh and resets the skeleton's pose.</summary>
public virtual void ClearState () {
var meshFilter = GetComponent<MeshFilter>();
if (meshFilter != null) meshFilter.sharedMesh = null;
currentInstructions.Clear();
if (skeleton != null) skeleton.SetToSetupPose();
}
/// <summary>
/// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
/// </summary>
public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
meshGenerator.EnsureVertexCapacity(minimumVertexCount);
}
/// <summary>
/// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
/// <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>
public virtual void Initialize (bool overwrite, bool quiet = false) {
if (valid && !overwrite)
return;
// Clear
{
// Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
// otherwise constant reloading will be triggered at prefabs.
currentInstructions.Clear();
rendererBuffers.Clear();
meshGenerator.Begin();
skeleton = null;
valid = false;
}
if (skeletonDataAsset == null)
return;
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
if (skeletonData == null) return;
valid = true;
meshFilter = GetComponent<MeshFilter>();
meshRenderer = GetComponent<MeshRenderer>();
rendererBuffers.Initialize();
skeleton = new Skeleton(skeletonData) {
ScaleX = initialFlipX ? -1 : 1,
ScaleY = initialFlipY ? -1 : 1
};
if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
skeleton.SetSkin(initialSkinName);
separatorSlots.Clear();
for (int i = 0; i < separatorSlotNames.Length; i++)
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
LateUpdate(); // Generate mesh for the first frame it exists.
if (OnRebuild != null)
OnRebuild(this);
#if UNITY_EDITOR
if (!Application.isPlaying) {
string errorMessage = null;
if (!quiet && MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage))
Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage);
}
#endif
}
/// <summary>
/// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
public virtual void LateUpdate () {
if (!valid) return;
#if UNITY_EDITOR && NEW_PREFAB_SYSTEM
// Don't store mesh or material at the prefab, otherwise it will permanently reload
var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
(prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
return;
}
#endif
if (updateMode != UpdateMode.FullUpdate) return;
#if SPINE_OPTIONAL_RENDEROVERRIDE
bool doMeshOverride = generateMeshOverride != null;
if ((!meshRenderer.enabled) && !doMeshOverride) return;
#else
const bool doMeshOverride = false;
if (!meshRenderer.enabled) return;
#endif
var currentInstructions = this.currentInstructions;
var workingSubmeshInstructions = currentInstructions.submeshInstructions;
var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
bool updateTriangles;
if (this.singleSubmesh) {
// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
// STEP 1.9. Post-process workingInstructions. ==================================================================================
#if SPINE_OPTIONAL_MATERIALOVERRIDE
if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
#endif
// STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
meshGenerator.settings = new MeshGenerator.Settings {
pmaVertexColors = this.pmaVertexColors,
zSpacing = this.zSpacing,
useClipping = this.useClipping,
tintBlack = this.tintBlack,
calculateTangents = this.calculateTangents,
addNormals = this.addNormals
};
meshGenerator.Begin();
updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
if (currentInstructions.hasActiveClipping) {
meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
} else {
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
}
} else {
// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
// STEP 1.9. Post-process workingInstructions. ==================================================================================
#if SPINE_OPTIONAL_MATERIALOVERRIDE
if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
#endif
#if SPINE_OPTIONAL_RENDEROVERRIDE
if (doMeshOverride) {
this.generateMeshOverride(currentInstructions);
if (disableRenderingOnOverride) return;
}
#endif
updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
// STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
meshGenerator.settings = new MeshGenerator.Settings {
pmaVertexColors = this.pmaVertexColors,
zSpacing = this.zSpacing,
useClipping = this.useClipping,
tintBlack = this.tintBlack,
calculateTangents = this.calculateTangents,
addNormals = this.addNormals
};
meshGenerator.Begin();
if (currentInstructions.hasActiveClipping)
meshGenerator.BuildMesh(currentInstructions, updateTriangles);
else
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
}
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
// STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
var currentMesh = currentSmartMesh.mesh;
meshGenerator.FillVertexData(currentMesh);
rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
bool materialsChanged = rendererBuffers.MaterialsChangedInLastUpdate();
if (updateTriangles) { // Check if the triangles should also be updated.
meshGenerator.FillTriangles(currentMesh);
meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
} else if (materialsChanged) {
meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
}
if (materialsChanged && (this.maskMaterials.AnyMaterialCreated)) {
this.maskMaterials = new SpriteMaskInteractionMaterials();
}
meshGenerator.FillLateVertexData(currentMesh);
// STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
meshFilter.sharedMesh = currentMesh;
currentSmartMesh.instructionUsed.Set(currentInstructions);
#if BUILT_IN_SPRITE_MASK_COMPONENT
if (meshRenderer != null) {
AssignSpriteMaskMaterials();
}
#endif
#if PER_MATERIAL_PROPERTY_BLOCKS
if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
SetMaterialSettingsToFixDrawOrder();
}
#endif
if (OnMeshAndMaterialsUpdated != null)
OnMeshAndMaterialsUpdated(this);
}
public void OnBecameVisible () {
UpdateMode previousUpdateMode = updateMode;
updateMode = UpdateMode.FullUpdate;
if (previousUpdateMode != UpdateMode.FullUpdate)
LateUpdate(); // OnBecameVisible is called after LateUpdate()
}
public void OnBecameInvisible () {
updateMode = updateWhenInvisible;
}
public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
if (string.IsNullOrEmpty(startsWith)) return;
FindAndApplySeparatorSlots(
(slotName) => slotName.StartsWith(startsWith),
clearExistingSeparators,
updateStringArray
);
}
public void FindAndApplySeparatorSlots (System.Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false) {
if (slotNamePredicate == null) return;
if (!valid) return;
if (clearExistingSeparators)
separatorSlots.Clear();
var slots = skeleton.slots;
foreach (var slot in slots) {
if (slotNamePredicate.Invoke(slot.data.name))
separatorSlots.Add(slot);
}
if (updateStringArray) {
var detectedSeparatorNames = new List<string>();
foreach (var slot in skeleton.slots) {
string slotName = slot.data.name;
if (slotNamePredicate.Invoke(slotName))
detectedSeparatorNames.Add(slotName);
}
if (!clearExistingSeparators) {
string[] originalNames = this.separatorSlotNames;
foreach (string originalName in originalNames)
detectedSeparatorNames.Add(originalName);
}
this.separatorSlotNames = detectedSeparatorNames.ToArray();
}
}
public void ReapplySeparatorSlotNames () {
if (!valid)
return;
separatorSlots.Clear();
for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
var slot = skeleton.FindSlot(separatorSlotNames[i]);
if (slot != null) {
separatorSlots.Add(slot);
}
#if UNITY_EDITOR
else if (!string.IsNullOrEmpty(separatorSlotNames[i]))
{
Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
}
#endif
}
}
#if BUILT_IN_SPRITE_MASK_COMPONENT
private void AssignSpriteMaskMaterials()
{
#if UNITY_EDITOR
if (!Application.isPlaying && !UnityEditor.EditorApplication.isUpdating) {
EditorFixStencilCompParameters();
}
#endif
if (Application.isPlaying) {
if (maskInteraction != SpriteMaskInteraction.None && maskMaterials.materialsMaskDisabled.Length == 0)
maskMaterials.materialsMaskDisabled = meshRenderer.sharedMaterials;
}
if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
maskInteraction == SpriteMaskInteraction.None) {
this.meshRenderer.materials = maskMaterials.materialsMaskDisabled;
}
else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null) {
if (!InitSpriteMaskMaterialsInsideMask())
return;
}
this.meshRenderer.materials = maskMaterials.materialsInsideMask;
}
else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null) {
if (!InitSpriteMaskMaterialsOutsideMask())
return;
}
this.meshRenderer.materials = maskMaterials.materialsOutsideMask;
}
}
private bool InitSpriteMaskMaterialsInsideMask()
{
return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE, ref maskMaterials.materialsInsideMask);
}
private bool InitSpriteMaskMaterialsOutsideMask()
{
return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE, ref maskMaterials.materialsOutsideMask);
}
private bool InitSpriteMaskMaterialsForMaskType(UnityEngine.Rendering.CompareFunction maskFunction, ref Material[] materialsToFill)
{
#if UNITY_EDITOR
if (!Application.isPlaying) {
return false;
}
#endif
var originalMaterials = maskMaterials.materialsMaskDisabled;
materialsToFill = new Material[originalMaterials.Length];
for (int i = 0; i < originalMaterials.Length; i++) {
Material newMaterial = new Material(originalMaterials[i]);
newMaterial.SetFloat(STENCIL_COMP_PARAM_ID, (int)maskFunction);
materialsToFill[i] = newMaterial;
}
return true;
}
#if UNITY_EDITOR
private void EditorFixStencilCompParameters() {
if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
haveStencilParametersBeenFixed = true;
FixAllProjectMaterialsStencilCompParameters();
}
}
private void FixAllProjectMaterialsStencilCompParameters() {
string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
foreach (var guid in materialGUIDS) {
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path)) {
var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
if (mat.HasProperty(STENCIL_COMP_PARAM_ID) && mat.GetFloat(STENCIL_COMP_PARAM_ID) == 0) {
mat.SetFloat(STENCIL_COMP_PARAM_ID, (int)STENCIL_COMP_MASKINTERACTION_NONE);
}
}
}
UnityEditor.AssetDatabase.Refresh();
UnityEditor.AssetDatabase.SaveAssets();
}
private bool HasAnyStencilComp0Material() {
if (meshRenderer == null)
return false;
foreach (var mat in meshRenderer.sharedMaterials) {
if (mat != null && mat.HasProperty(STENCIL_COMP_PARAM_ID)) {
float currentCompValue = mat.GetFloat(STENCIL_COMP_PARAM_ID);
if (currentCompValue == 0)
return true;
}
}
return false;
}
#endif // UNITY_EDITOR
#endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
#if PER_MATERIAL_PROPERTY_BLOCKS
private MaterialPropertyBlock reusedPropertyBlock;
public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
/// <summary>
/// This method was introduced as a workaround for too aggressive submesh draw call batching,
/// leading to incorrect draw order when 3+ materials are used at submeshes in alternating order.
/// Otherwise, e.g. when using Lightweight Render Pipeline, deliberately separated draw calls
/// "A1 B A2" are reordered to "A1A2 B", regardless of batching-related project settings.
/// </summary>
private void SetMaterialSettingsToFixDrawOrder() {
if (reusedPropertyBlock == null) reusedPropertyBlock = new MaterialPropertyBlock();
bool hasPerRendererBlock = meshRenderer.HasPropertyBlock();
if (hasPerRendererBlock) {
meshRenderer.GetPropertyBlock(reusedPropertyBlock);
}
for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) {
if (!meshRenderer.sharedMaterials[i])
continue;
if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i);
// Note: this parameter shall not exist at any shader, then Unity will create separate
// material instances (not in terms of memory cost or leakage).
reusedPropertyBlock.SetFloat(SUBMESH_DUMMY_PARAM_ID, i);
meshRenderer.SetPropertyBlock(reusedPropertyBlock, i);
meshRenderer.sharedMaterials[i].enableInstancing = false;
}
}
#endif
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e075b9a3e08e2f74fbd651c858ab16ed
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a7236dbdc6a4e5a4989483dac97aee0b
folderAsset: yes
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,211 @@
/******************************************************************************
* 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
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonGraphicCustomMaterials")]
public class SkeletonGraphicCustomMaterials : MonoBehaviour {
#region Inspector
public SkeletonGraphic skeletonGraphic;
[SerializeField] protected List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
[SerializeField] protected List<AtlasTextureOverride> customTextureOverrides = new List<AtlasTextureOverride>();
#if UNITY_EDITOR
void Reset () {
skeletonGraphic = GetComponent<SkeletonGraphic>();
// Populate material list
if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
foreach (Material atlasMaterial in atlasAsset.Materials) {
var atlasMaterialOverride = new AtlasMaterialOverride {
overrideEnabled = false,
originalTexture = atlasMaterial.mainTexture
};
initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
}
}
customMaterialOverrides = initialAtlasMaterialOverrides;
}
// Populate texture list
if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
var initialAtlasTextureOverrides = new List<AtlasTextureOverride>();
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
foreach (Material atlasMaterial in atlasAsset.Materials) {
var atlasTextureOverride = new AtlasTextureOverride {
overrideEnabled = false,
originalTexture = atlasMaterial.mainTexture
};
initialAtlasTextureOverrides.Add(atlasTextureOverride);
}
}
customTextureOverrides = initialAtlasTextureOverrides;
}
}
#endif
#endregion
void SetCustomMaterialOverrides () {
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
for (int i = 0; i < customMaterialOverrides.Count; i++) {
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
if (atlasMaterialOverride.overrideEnabled)
skeletonGraphic.CustomMaterialOverride[atlasMaterialOverride.originalTexture] = atlasMaterialOverride.replacementMaterial;
}
}
void RemoveCustomMaterialOverrides () {
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
for (int i = 0; i < customMaterialOverrides.Count; i++) {
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
Material currentMaterial;
if (!skeletonGraphic.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalTexture, out currentMaterial))
continue;
// Do not revert the material if it was changed by something else
if (currentMaterial != atlasMaterialOverride.replacementMaterial)
continue;
skeletonGraphic.CustomMaterialOverride.Remove(atlasMaterialOverride.originalTexture);
}
}
void SetCustomTextureOverrides () {
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
for (int i = 0; i < customTextureOverrides.Count; i++) {
AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i];
if (atlasTextureOverride.overrideEnabled)
skeletonGraphic.CustomTextureOverride[atlasTextureOverride.originalTexture] = atlasTextureOverride.replacementTexture;
}
}
void RemoveCustomTextureOverrides () {
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
for (int i = 0; i < customTextureOverrides.Count; i++) {
AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i];
Texture currentTexture;
if (!skeletonGraphic.CustomTextureOverride.TryGetValue(atlasTextureOverride.originalTexture, out currentTexture))
continue;
// Do not revert the material if it was changed by something else
if (currentTexture != atlasTextureOverride.replacementTexture)
continue;
skeletonGraphic.CustomTextureOverride.Remove(atlasTextureOverride.originalTexture);
}
}
// OnEnable applies the overrides at runtime, and when the editor loads.
void OnEnable () {
if (skeletonGraphic == null)
skeletonGraphic = GetComponent<SkeletonGraphic>();
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
skeletonGraphic.Initialize(false);
SetCustomMaterialOverrides();
SetCustomTextureOverrides();
}
// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
void OnDisable () {
if (skeletonGraphic == null) {
Debug.LogError("skeletonGraphic == null");
return;
}
RemoveCustomMaterialOverrides();
RemoveCustomTextureOverrides();
}
[Serializable]
public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
public bool overrideEnabled;
public Texture originalTexture;
public Material replacementMaterial;
public bool Equals (AtlasMaterialOverride other) {
return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementMaterial == other.replacementMaterial;
}
}
[Serializable]
public struct AtlasTextureOverride : IEquatable<AtlasTextureOverride> {
public bool overrideEnabled;
public Texture originalTexture;
public Texture replacementTexture;
public bool Equals (AtlasTextureOverride other) {
return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementTexture == other.replacementTexture;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6c8717e10b272bf42b05d363ac2679a6
timeCreated: 1588789074
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,212 @@
/******************************************************************************
* 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_MATERIALOVERRIDE
// Contributed by: Lost Polygon
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRendererCustomMaterials")]
public class SkeletonRendererCustomMaterials : MonoBehaviour {
#region Inspector
public SkeletonRenderer skeletonRenderer;
[SerializeField] protected List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
[SerializeField] protected List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
#if UNITY_EDITOR
void Reset () {
skeletonRenderer = GetComponent<SkeletonRenderer>();
// Populate atlas list
if (skeletonRenderer != null && skeletonRenderer.skeletonDataAsset != null) {
var atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
foreach (Material atlasMaterial in atlasAsset.Materials) {
var atlasMaterialOverride = new AtlasMaterialOverride {
overrideDisabled = true,
originalMaterial = atlasMaterial
};
initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
}
}
customMaterialOverrides = initialAtlasMaterialOverrides;
}
}
#endif
#endregion
void SetCustomSlotMaterials () {
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
for (int i = 0; i < customSlotMaterials.Count; i++) {
SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
if (slotMaterialOverride.overrideDisabled || string.IsNullOrEmpty(slotMaterialOverride.slotName))
continue;
Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
skeletonRenderer.CustomSlotMaterials[slotObject] = slotMaterialOverride.material;
}
}
void RemoveCustomSlotMaterials () {
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
for (int i = 0; i < customSlotMaterials.Count; i++) {
SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
if (string.IsNullOrEmpty(slotMaterialOverride.slotName))
continue;
Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
Material currentMaterial;
if (!skeletonRenderer.CustomSlotMaterials.TryGetValue(slotObject, out currentMaterial))
continue;
// Do not revert the material if it was changed by something else
if (currentMaterial != slotMaterialOverride.material)
continue;
skeletonRenderer.CustomSlotMaterials.Remove(slotObject);
}
}
void SetCustomMaterialOverrides () {
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
#if SPINE_OPTIONAL_MATERIALOVERRIDE
for (int i = 0; i < customMaterialOverrides.Count; i++) {
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
if (atlasMaterialOverride.overrideDisabled)
continue;
skeletonRenderer.CustomMaterialOverride[atlasMaterialOverride.originalMaterial] = atlasMaterialOverride.replacementMaterial;
}
#endif
}
void RemoveCustomMaterialOverrides () {
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
#if SPINE_OPTIONAL_MATERIALOVERRIDE
for (int i = 0; i < customMaterialOverrides.Count; i++) {
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
Material currentMaterial;
if (!skeletonRenderer.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalMaterial, out currentMaterial))
continue;
// Do not revert the material if it was changed by something else
if (currentMaterial != atlasMaterialOverride.replacementMaterial)
continue;
skeletonRenderer.CustomMaterialOverride.Remove(atlasMaterialOverride.originalMaterial);
}
#endif
}
// OnEnable applies the overrides at runtime, and when the editor loads.
void OnEnable () {
if (skeletonRenderer == null)
skeletonRenderer = GetComponent<SkeletonRenderer>();
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
skeletonRenderer.Initialize(false);
SetCustomMaterialOverrides();
SetCustomSlotMaterials();
}
// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
void OnDisable () {
if (skeletonRenderer == null) {
Debug.LogError("skeletonRenderer == null");
return;
}
RemoveCustomMaterialOverrides();
RemoveCustomSlotMaterials();
}
[Serializable]
public struct SlotMaterialOverride : IEquatable<SlotMaterialOverride> {
public bool overrideDisabled;
[SpineSlot]
public string slotName;
public Material material;
public bool Equals (SlotMaterialOverride other) {
return overrideDisabled == other.overrideDisabled && slotName == other.slotName && material == other.material;
}
}
[Serializable]
public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
public bool overrideDisabled;
public Material originalMaterial;
public Material replacementMaterial;
public bool Equals (AtlasMaterialOverride other) {
return overrideDisabled == other.overrideDisabled && originalMaterial == other.originalMaterial && replacementMaterial == other.replacementMaterial;
}
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 26947ae098a8447408d80c0c86e35b48
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
SkeletonRendererCustomMaterials by LostPolygon
===============================
This is a basic serialization and inspector implementation for custom material overrides for SkeletonRenderer and its derived classes (SkeletonAnimation, SkeletonAnimator).
## How to use
Right-click on your SkeletonRenderer and select "Add Basic Serialized Custom Materials". This will add and initialize the SkeletonRendererCustomMaterials to the same object.
You can use this to store material override settings for SkeletonRenderer instances/prefabs so they will be applied automatically when your scene starts or when the prefab is instantiated.
This script is not intended for use with code.
To dynamically set materials for your SkeletonRenderer through code, you can directly access `SkeletonRenderer.CustomMaterialOverride` for material array overrides and `SkeletonRenderer.CustomSlotMaterials` for slot material overrides.
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3d4db6c367e463c4cb5566afc490163c
timeCreated: 1460572571
licenseType: Free
TextScriptImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
fileFormatVersion: 2
guid: f6e0caaafe294de48af468a6a9321473
folderAsset: yes
DefaultImporter:
userData:
@@ -0,0 +1,92 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
namespace Spine.Unity {
/// <summary>
/// Utility component to support flipping of 2D hinge chains (chains of HingeJoint2D objects) along
/// with the parent skeleton by activating the respective mirrored versions of the hinge chain.
/// Note: This component is automatically attached when calling "Create Hinge Chain 2D" at <see cref="SkeletonUtilityBone"/>,
/// do not attempt to use this component for other purposes.
/// </summary>
public class ActivateBasedOnFlipDirection : MonoBehaviour {
public SkeletonRenderer skeletonRenderer;
public SkeletonGraphic skeletonGraphic;
public GameObject activeOnNormalX;
public GameObject activeOnFlippedX;
HingeJoint2D[] jointsNormalX;
HingeJoint2D[] jointsFlippedX;
ISkeletonComponent skeletonComponent;
bool wasFlippedXBefore = false;
private void Start () {
jointsNormalX = activeOnNormalX.GetComponentsInChildren<HingeJoint2D>();
jointsFlippedX = activeOnFlippedX.GetComponentsInChildren<HingeJoint2D>();
skeletonComponent = skeletonRenderer != null ? (ISkeletonComponent)skeletonRenderer : (ISkeletonComponent)skeletonGraphic;
}
private void FixedUpdate () {
bool isFlippedX = (skeletonComponent.Skeleton.ScaleX < 0);
if (isFlippedX != wasFlippedXBefore) {
HandleFlip(isFlippedX);
}
wasFlippedXBefore = isFlippedX;
}
void HandleFlip (bool isFlippedX) {
GameObject gameObjectToActivate = isFlippedX ? activeOnFlippedX : activeOnNormalX;
GameObject gameObjectToDeactivate = isFlippedX ? activeOnNormalX : activeOnFlippedX;
gameObjectToActivate.SetActive(true);
gameObjectToDeactivate.SetActive(false);
ResetJointPositions(isFlippedX ? jointsFlippedX : jointsNormalX);
ResetJointPositions(isFlippedX ? jointsNormalX : jointsFlippedX);
CompensateMovementAfterFlipX(gameObjectToActivate.transform, gameObjectToDeactivate.transform);
}
void ResetJointPositions (HingeJoint2D[] joints) {
for (int i = 0; i < joints.Length; ++i) {
var joint = joints[i];
var parent = joint.connectedBody.transform;
joint.transform.position = parent.TransformPoint(joint.connectedAnchor);
}
}
void CompensateMovementAfterFlipX (Transform toActivate, Transform toDeactivate) {
Transform targetLocation = toDeactivate.GetChild(0);
Transform currentLocation = toActivate.GetChild(0);
toActivate.position += targetLocation.position - currentLocation.position;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70ae96e4f2feb654681a2f16e4effeec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
namespace Spine.Unity {
/// <summary>
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
///
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
public class FollowLocationRigidbody : MonoBehaviour {
public Transform reference;
Rigidbody ownRigidbody;
private void Awake () {
ownRigidbody = this.GetComponent<Rigidbody>();
}
void FixedUpdate () {
ownRigidbody.rotation = reference.rotation;
ownRigidbody.position = reference.position;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 9fc20d5e917562341a5007777a9d0db2
timeCreated: 1571763023
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,59 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
namespace Spine.Unity {
/// <summary>
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
///
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
public class FollowLocationRigidbody2D : MonoBehaviour {
public Transform reference;
public bool followFlippedX;
Rigidbody2D ownRigidbody;
private void Awake () {
ownRigidbody = this.GetComponent<Rigidbody2D>();
}
void FixedUpdate () {
if (followFlippedX) {
ownRigidbody.rotation = ((-reference.rotation.eulerAngles.z + 270f) % 360f) - 90f;
}
else
ownRigidbody.rotation = reference.rotation.eulerAngles.z;
ownRigidbody.position = reference.position;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 02aae87c39b869548a9051fbdb1975e6
timeCreated: 1572012493
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,86 @@
/******************************************************************************
* 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.
*****************************************************************************/
using UnityEngine;
namespace Spine.Unity {
/// <summary>
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
/// Note that flipping needs to be performed by 180 degree rotation at <see cref="SkeletonUtility"/>,
/// by setting <see cref="SkeletonUtility.flipBy180DegreeRotation"/> to true, not via negative scale.
///
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>,
/// do not attempt to use this component for other purposes.
/// </summary>
public class FollowSkeletonUtilityRootRotation : MonoBehaviour {
const float FLIP_ANGLE_THRESHOLD = 100.0f;
public Transform reference;
Vector3 prevLocalEulerAngles;
private void Start () {
prevLocalEulerAngles = this.transform.localEulerAngles;
}
void FixedUpdate () {
this.transform.rotation = reference.rotation;
bool wasFlippedAroundY = Mathf.Abs(this.transform.localEulerAngles.y - prevLocalEulerAngles.y) > FLIP_ANGLE_THRESHOLD;
bool wasFlippedAroundX = Mathf.Abs(this.transform.localEulerAngles.x - prevLocalEulerAngles.x) > FLIP_ANGLE_THRESHOLD;
if (wasFlippedAroundY)
CompensatePositionToYRotation();
if (wasFlippedAroundX)
CompensatePositionToXRotation();
prevLocalEulerAngles = this.transform.localEulerAngles;
}
/// <summary>
/// Compensates the position so that a child at the reference position remains in the same place,
/// to counter any movement that occurred by rotation.
/// </summary>
void CompensatePositionToYRotation () {
Vector3 newPosition = reference.position + (reference.position - this.transform.position);
newPosition.y = this.transform.position.y;
this.transform.position = newPosition;
}
/// <summary>
/// Compensates the position so that a child at the reference position remains in the same place,
/// to counter any movement that occurred by rotation.
/// </summary>
void CompensatePositionToXRotation () {
Vector3 newPosition = reference.position + (reference.position - this.transform.position);
newPosition.x = this.transform.position.x;
this.transform.position = newPosition;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 456a736ebb92ebf4b959fa9c4b704427
timeCreated: 1571763206
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,469 @@
/******************************************************************************
* 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
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[RequireComponent(typeof(ISkeletonAnimation))]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtility")]
public sealed class SkeletonUtility : MonoBehaviour {
#region BoundingBoxAttachment
public static PolygonCollider2D AddBoundingBoxGameObject (Skeleton skeleton, string skinName, string slotName, string attachmentName, Transform parent, bool isTrigger = true) {
Skin skin = string.IsNullOrEmpty(skinName) ? skeleton.data.defaultSkin : skeleton.data.FindSkin(skinName);
if (skin == null) {
Debug.LogError("Skin " + skinName + " not found!");
return null;
}
var attachment = skin.GetAttachment(skeleton.FindSlotIndex(slotName), attachmentName);
if (attachment == null) {
Debug.LogFormat("Attachment in slot '{0}' named '{1}' not found in skin '{2}'.", slotName, attachmentName, skin.name);
return null;
}
var box = attachment as BoundingBoxAttachment;
if (box != null) {
var slot = skeleton.FindSlot(slotName);
return AddBoundingBoxGameObject(box.Name, box, slot, parent, isTrigger);
} else {
Debug.LogFormat("Attachment '{0}' was not a Bounding Box.", attachmentName);
return null;
}
}
public static PolygonCollider2D AddBoundingBoxGameObject (string name, BoundingBoxAttachment box, Slot slot, Transform parent, bool isTrigger = true) {
var go = new GameObject("[BoundingBox]" + (string.IsNullOrEmpty(name) ? box.Name : name));
#if UNITY_EDITOR
if (!Application.isPlaying)
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Spawn BoundingBox");
# endif
var got = go.transform;
got.parent = parent;
got.localPosition = Vector3.zero;
got.localRotation = Quaternion.identity;
got.localScale = Vector3.one;
return AddBoundingBoxAsComponent(box, slot, go, isTrigger);
}
public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Slot slot, GameObject gameObject, bool isTrigger = true) {
if (box == null) return null;
var collider = gameObject.AddComponent<PolygonCollider2D>();
collider.isTrigger = isTrigger;
SetColliderPointsLocal(collider, slot, box);
return collider;
}
public static void SetColliderPointsLocal (PolygonCollider2D collider, Slot slot, BoundingBoxAttachment box, float scale = 1.0f) {
if (box == null) return;
if (box.IsWeighted()) Debug.LogWarning("UnityEngine.PolygonCollider2D does not support weighted or animated points. Collider points will not be animated and may have incorrect orientation. If you want to use it as a collider, please remove weights and animations from the bounding box in Spine editor.");
var verts = box.GetLocalVertices(slot, null);
if (scale != 1.0f) {
for (int i = 0, n = verts.Length; i < n; ++i)
verts[i] *= scale;
}
collider.SetPath(0, verts);
}
public static Bounds GetBoundingBoxBounds (BoundingBoxAttachment boundingBox, float depth = 0) {
float[] floats = boundingBox.Vertices;
int floatCount = floats.Length;
Bounds bounds = new Bounds();
bounds.center = new Vector3(floats[0], floats[1], 0);
for (int i = 2; i < floatCount; i += 2)
bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
Vector3 size = bounds.size;
size.z = depth;
bounds.size = size;
return bounds;
}
public static Rigidbody2D AddBoneRigidbody2D (GameObject gameObject, bool isKinematic = true, float gravityScale = 0f) {
var rb = gameObject.GetComponent<Rigidbody2D>();
if (rb == null) {
rb = gameObject.AddComponent<Rigidbody2D>();
rb.isKinematic = isKinematic;
rb.gravityScale = gravityScale;
}
return rb;
}
#endregion
public delegate void SkeletonUtilityDelegate ();
public event SkeletonUtilityDelegate OnReset;
public Transform boneRoot;
/// <summary>
/// If true, <see cref="Skeleton.ScaleX"/> and <see cref="Skeleton.ScaleY"/> are followed
/// by 180 degree rotation. If false, negative Transform scale is used.
/// Note that using negative scale is consistent with previous behaviour (hence the default),
/// however causes serious problems with rigidbodies and physics. Therefore, it is recommended to
/// enable this parameter where possible. When creating hinge chains for a chain of skeleton bones
/// via <see cref="SkeletonUtilityBone"/>, it is mandatory to have <c>flipBy180DegreeRotation</c> enabled.
/// </summary>
public bool flipBy180DegreeRotation = false;
void Update () {
var skeleton = skeletonComponent.Skeleton;
if (skeleton != null && boneRoot != null) {
if (flipBy180DegreeRotation) {
boneRoot.localScale = new Vector3(Mathf.Abs(skeleton.ScaleX), Mathf.Abs(skeleton.ScaleY), 1f);
boneRoot.eulerAngles = new Vector3(skeleton.ScaleY > 0 ? 0 : 180,
skeleton.ScaleX > 0 ? 0 : 180,
0);
}
else {
boneRoot.localScale = new Vector3(skeleton.ScaleX, skeleton.ScaleY, 1f);
}
}
if (canvas != null) {
positionScale = canvas.referencePixelsPerUnit;
}
}
[HideInInspector] public SkeletonRenderer skeletonRenderer;
[HideInInspector] public SkeletonGraphic skeletonGraphic;
private Canvas canvas;
[System.NonSerialized] public ISkeletonAnimation skeletonAnimation;
private ISkeletonComponent skeletonComponent;
[System.NonSerialized] public List<SkeletonUtilityBone> boneComponents = new List<SkeletonUtilityBone>();
[System.NonSerialized] public List<SkeletonUtilityConstraint> constraintComponents = new List<SkeletonUtilityConstraint>();
public ISkeletonComponent SkeletonComponent {
get {
if (skeletonComponent == null) {
skeletonComponent = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonComponent>() :
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonComponent>() :
GetComponent<ISkeletonComponent>();
}
return skeletonComponent;
}
}
public Skeleton Skeleton {
get {
if (SkeletonComponent == null)
return null;
return skeletonComponent.Skeleton;
}
}
public bool IsValid {
get {
return (skeletonRenderer != null && skeletonRenderer.valid) ||
(skeletonGraphic != null && skeletonGraphic.IsValid);
}
}
public float PositionScale { get { return positionScale; } }
float positionScale = 1.0f;
bool hasOverrideBones;
bool hasConstraints;
bool needToReprocessBones;
public void ResubscribeEvents () {
OnDisable();
OnEnable();
}
void OnEnable () {
if (skeletonRenderer == null) {
skeletonRenderer = GetComponent<SkeletonRenderer>();
}
if (skeletonGraphic == null) {
skeletonGraphic = GetComponent<SkeletonGraphic>();
}
if (skeletonAnimation == null) {
skeletonAnimation = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonAnimation>() :
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonAnimation>() :
GetComponent<ISkeletonAnimation>();
}
if (skeletonComponent == null) {
skeletonComponent = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonComponent>() :
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonComponent>() :
GetComponent<ISkeletonComponent>();
}
if (skeletonRenderer != null) {
skeletonRenderer.OnRebuild -= HandleRendererReset;
skeletonRenderer.OnRebuild += HandleRendererReset;
}
else if (skeletonGraphic != null) {
skeletonGraphic.OnRebuild -= HandleRendererReset;
skeletonGraphic.OnRebuild += HandleRendererReset;
canvas = skeletonGraphic.canvas;
if (canvas == null)
canvas = skeletonGraphic.GetComponentInParent<Canvas>();
if (canvas == null)
positionScale = 100.0f;
}
if (skeletonAnimation != null) {
skeletonAnimation.UpdateLocal -= UpdateLocal;
skeletonAnimation.UpdateLocal += UpdateLocal;
}
CollectBones();
}
void Start () {
//recollect because order of operations failure when switching between game mode and edit mode...
CollectBones();
}
void OnDisable () {
if (skeletonRenderer != null)
skeletonRenderer.OnRebuild -= HandleRendererReset;
if (skeletonGraphic != null)
skeletonGraphic.OnRebuild -= HandleRendererReset;
if (skeletonAnimation != null) {
skeletonAnimation.UpdateLocal -= UpdateLocal;
skeletonAnimation.UpdateWorld -= UpdateWorld;
skeletonAnimation.UpdateComplete -= UpdateComplete;
}
}
void HandleRendererReset (SkeletonRenderer r) {
if (OnReset != null) OnReset();
CollectBones();
}
void HandleRendererReset (SkeletonGraphic g) {
if (OnReset != null) OnReset();
CollectBones();
}
public void RegisterBone (SkeletonUtilityBone bone) {
if (boneComponents.Contains(bone)) {
return;
} else {
boneComponents.Add(bone);
needToReprocessBones = true;
}
}
public void UnregisterBone (SkeletonUtilityBone bone) {
boneComponents.Remove(bone);
}
public void RegisterConstraint (SkeletonUtilityConstraint constraint) {
if (constraintComponents.Contains(constraint))
return;
else {
constraintComponents.Add(constraint);
needToReprocessBones = true;
}
}
public void UnregisterConstraint (SkeletonUtilityConstraint constraint) {
constraintComponents.Remove(constraint);
}
public void CollectBones () {
var skeleton = skeletonComponent.Skeleton;
if (skeleton == null) return;
if (boneRoot != null) {
var constraintTargets = new List<System.Object>();
var ikConstraints = skeleton.IkConstraints;
for (int i = 0, n = ikConstraints.Count; i < n; i++)
constraintTargets.Add(ikConstraints.Items[i].target);
var transformConstraints = skeleton.TransformConstraints;
for (int i = 0, n = transformConstraints.Count; i < n; i++)
constraintTargets.Add(transformConstraints.Items[i].target);
var boneComponents = this.boneComponents;
for (int i = 0, n = boneComponents.Count; i < n; i++) {
var b = boneComponents[i];
if (b.bone == null) {
b.DoUpdate(SkeletonUtilityBone.UpdatePhase.Local);
if (b.bone == null) continue;
}
hasOverrideBones |= (b.mode == SkeletonUtilityBone.Mode.Override);
hasConstraints |= constraintTargets.Contains(b.bone);
}
hasConstraints |= constraintComponents.Count > 0;
if (skeletonAnimation != null) {
skeletonAnimation.UpdateWorld -= UpdateWorld;
skeletonAnimation.UpdateComplete -= UpdateComplete;
if (hasOverrideBones || hasConstraints)
skeletonAnimation.UpdateWorld += UpdateWorld;
if (hasConstraints)
skeletonAnimation.UpdateComplete += UpdateComplete;
}
needToReprocessBones = false;
} else {
boneComponents.Clear();
constraintComponents.Clear();
}
}
void UpdateLocal (ISkeletonAnimation anim) {
if (needToReprocessBones)
CollectBones();
var boneComponents = this.boneComponents;
if (boneComponents == null) return;
for (int i = 0, n = boneComponents.Count; i < n; i++)
boneComponents[i].transformLerpComplete = false;
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Local);
}
void UpdateWorld (ISkeletonAnimation anim) {
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.World);
for (int i = 0, n = constraintComponents.Count; i < n; i++)
constraintComponents[i].DoUpdate();
}
void UpdateComplete (ISkeletonAnimation anim) {
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Complete);
}
void UpdateAllBones (SkeletonUtilityBone.UpdatePhase phase) {
if (boneRoot == null)
CollectBones();
var boneComponents = this.boneComponents;
if (boneComponents == null) return;
for (int i = 0, n = boneComponents.Count; i < n; i++)
boneComponents[i].DoUpdate(phase);
}
public Transform GetBoneRoot () {
if (boneRoot != null)
return boneRoot;
var boneRootObject = new GameObject("SkeletonUtility-SkeletonRoot");
#if UNITY_EDITOR
if (!Application.isPlaying)
UnityEditor.Undo.RegisterCreatedObjectUndo(boneRootObject, "Spawn Bone");
#endif
if (skeletonGraphic != null)
boneRootObject.AddComponent<RectTransform>();
boneRoot = boneRootObject.transform;
boneRoot.SetParent(transform);
boneRoot.localPosition = Vector3.zero;
boneRoot.localRotation = Quaternion.identity;
boneRoot.localScale = Vector3.one;
return boneRoot;
}
public GameObject SpawnRoot (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
GetBoneRoot();
Skeleton skeleton = this.skeletonComponent.Skeleton;
GameObject go = SpawnBone(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
CollectBones();
return go;
}
public GameObject SpawnHierarchy (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
GetBoneRoot();
Skeleton skeleton = this.skeletonComponent.Skeleton;
GameObject go = SpawnBoneRecursively(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
CollectBones();
return go;
}
public GameObject SpawnBoneRecursively (Bone bone, Transform parent, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
GameObject go = SpawnBone(bone, parent, mode, pos, rot, sca);
ExposedList<Bone> childrenBones = bone.Children;
for (int i = 0, n = childrenBones.Count; i < n; i++) {
Bone child = childrenBones.Items[i];
SpawnBoneRecursively(child, go.transform, mode, pos, rot, sca);
}
return go;
}
public GameObject SpawnBone (Bone bone, Transform parent, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
GameObject go = new GameObject(bone.Data.Name);
#if UNITY_EDITOR
if (!Application.isPlaying)
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Spawn Bone");
#endif
if (skeletonGraphic != null)
go.AddComponent<RectTransform>();
var goTransform = go.transform;
goTransform.SetParent(parent);
SkeletonUtilityBone b = go.AddComponent<SkeletonUtilityBone>();
b.hierarchy = this;
b.position = pos;
b.rotation = rot;
b.scale = sca;
b.mode = mode;
b.zPosition = true;
b.Reset();
b.bone = bone;
b.boneName = bone.Data.Name;
b.valid = true;
if (mode == SkeletonUtilityBone.Mode.Override) {
if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, b.bone.AppliedRotation);
if (pos) goTransform.localPosition = new Vector3(b.bone.X * positionScale, b.bone.Y * positionScale, 0);
goTransform.localScale = new Vector3(b.bone.scaleX, b.bone.scaleY, 0);
}
return go;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7f726fb798ad621458c431cb9966d91d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,243 @@
/******************************************************************************
* 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
using UnityEngine;
namespace Spine.Unity {
/// <summary>Sets a GameObject's transform to match a bone on a Spine skeleton.</summary>
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[AddComponentMenu("Spine/SkeletonUtilityBone")]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtilityBone")]
public class SkeletonUtilityBone : MonoBehaviour {
public enum Mode {
Follow,
Override
}
public enum UpdatePhase {
Local,
World,
Complete
}
#region Inspector
/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
public string boneName;
public Transform parentReference;
public Mode mode;
public bool position, rotation, scale, zPosition = true;
[Range(0f, 1f)]
public float overrideAlpha = 1;
#endregion
public SkeletonUtility hierarchy;
[System.NonSerialized] public Bone bone;
[System.NonSerialized] public bool transformLerpComplete;
[System.NonSerialized] public bool valid;
Transform cachedTransform;
Transform skeletonTransform;
bool incompatibleTransformMode;
public bool IncompatibleTransformMode { get { return incompatibleTransformMode; } }
public void Reset () {
bone = null;
cachedTransform = transform;
valid = hierarchy != null && hierarchy.IsValid;
if (!valid)
return;
skeletonTransform = hierarchy.transform;
hierarchy.OnReset -= HandleOnReset;
hierarchy.OnReset += HandleOnReset;
DoUpdate(UpdatePhase.Local);
}
void OnEnable () {
if (hierarchy == null) hierarchy = transform.GetComponentInParent<SkeletonUtility>();
if (hierarchy == null) return;
hierarchy.RegisterBone(this);
hierarchy.OnReset += HandleOnReset;
}
void HandleOnReset () {
Reset();
}
void OnDisable () {
if (hierarchy != null) {
hierarchy.OnReset -= HandleOnReset;
hierarchy.UnregisterBone(this);
}
}
public void DoUpdate (UpdatePhase phase) {
if (!valid) {
Reset();
return;
}
var skeleton = hierarchy.Skeleton;
if (bone == null) {
if (string.IsNullOrEmpty(boneName)) return;
bone = skeleton.FindBone(boneName);
if (bone == null) {
Debug.LogError("Bone not found: " + boneName, this);
return;
}
}
if (!bone.Active) return;
float positionScale = hierarchy.PositionScale;
var thisTransform = cachedTransform;
float skeletonFlipRotation = Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY);
if (mode == Mode.Follow) {
switch (phase) {
case UpdatePhase.Local:
if (position)
thisTransform.localPosition = new Vector3(bone.x * positionScale, bone.y * positionScale, 0);
if (rotation) {
if (bone.data.transformMode.InheritsRotation()) {
thisTransform.localRotation = Quaternion.Euler(0, 0, bone.rotation);
} else {
Vector3 euler = skeletonTransform.rotation.eulerAngles;
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
}
}
if (scale) {
thisTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, 1f);
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
}
break;
case UpdatePhase.World:
case UpdatePhase.Complete:
// Use Applied transform values (ax, ay, AppliedRotation, ascale) if world values were modified by constraints.
if (!bone.appliedValid) {
bone.UpdateAppliedTransform();
}
if (position)
thisTransform.localPosition = new Vector3(bone.ax * positionScale, bone.ay * positionScale, 0);
if (rotation) {
if (bone.data.transformMode.InheritsRotation()) {
thisTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
} else {
Vector3 euler = skeletonTransform.rotation.eulerAngles;
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
}
}
if (scale) {
thisTransform.localScale = new Vector3(bone.ascaleX, bone.ascaleY, 1f);
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
}
break;
}
} else if (mode == Mode.Override) {
if (transformLerpComplete)
return;
if (parentReference == null) {
if (position) {
Vector3 clp = thisTransform.localPosition / positionScale;
bone.x = Mathf.Lerp(bone.x, clp.x, overrideAlpha);
bone.y = Mathf.Lerp(bone.y, clp.y, overrideAlpha);
}
if (rotation) {
float angle = Mathf.LerpAngle(bone.Rotation, thisTransform.localRotation.eulerAngles.z, overrideAlpha);
bone.Rotation = angle;
bone.AppliedRotation = angle;
}
if (scale) {
Vector3 cls = thisTransform.localScale;
bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
}
} else {
if (transformLerpComplete)
return;
if (position) {
Vector3 pos = parentReference.InverseTransformPoint(thisTransform.position) / positionScale;
bone.x = Mathf.Lerp(bone.x, pos.x, overrideAlpha);
bone.y = Mathf.Lerp(bone.y, pos.y, overrideAlpha);
}
if (rotation) {
float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(thisTransform.up)).eulerAngles.z, overrideAlpha);
bone.Rotation = angle;
bone.AppliedRotation = angle;
}
if (scale) {
Vector3 cls = thisTransform.localScale;
bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
}
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
}
transformLerpComplete = true;
}
}
public static bool BoneTransformModeIncompatible (Bone bone) {
return !bone.data.transformMode.InheritsScale();
}
public void AddBoundingBox (string skinName, string slotName, string attachmentName) {
SkeletonUtility.AddBoneRigidbody2D(transform.gameObject);
SkeletonUtility.AddBoundingBoxGameObject(bone.skeleton, skinName, slotName, attachmentName, transform);
}
#if UNITY_EDITOR
void OnDrawGizmos () {
if (IncompatibleTransformMode)
Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");
}
#endif
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b238dfcde8209044b97d23f62bcaadf6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,62 @@
/******************************************************************************
* 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
using UnityEngine;
namespace Spine.Unity {
#if NEW_PREFAB_SYSTEM
[ExecuteAlways]
#else
[ExecuteInEditMode]
#endif
[RequireComponent(typeof(SkeletonUtilityBone))]
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtilityConstraint")]
public abstract class SkeletonUtilityConstraint : MonoBehaviour {
protected SkeletonUtilityBone bone;
protected SkeletonUtility hierarchy;
protected virtual void OnEnable () {
bone = GetComponent<SkeletonUtilityBone>();
hierarchy = transform.GetComponentInParent<SkeletonUtility>();
hierarchy.RegisterConstraint(this);
}
protected virtual void OnDisable () {
hierarchy.UnregisterConstraint(this);
}
public abstract void DoUpdate ();
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 522dbfcc6c916df4396f14f35048d185
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: