Initial commit: Client Doc docs Server Tools
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dc4b7c23385e8c43ad19d01cbed78ce
|
||||
folderAsset: yes
|
||||
timeCreated: 1455489521
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,89 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
#define AUTOINIT_SPINEREFERENCE
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[CreateAssetMenu(menuName = "Spine/Animation Reference Asset", order = 100)]
|
||||
public class AnimationReferenceAsset : ScriptableObject, IHasSkeletonDataAsset {
|
||||
const bool QuietSkeletonData = true;
|
||||
|
||||
[SerializeField] protected SkeletonDataAsset skeletonDataAsset;
|
||||
[SerializeField, SpineAnimation] protected string animationName;
|
||||
private Animation animation;
|
||||
|
||||
public SkeletonDataAsset SkeletonDataAsset {
|
||||
get { return skeletonDataAsset; }
|
||||
set { skeletonDataAsset = value; }
|
||||
}
|
||||
|
||||
public string AnimationName {
|
||||
get {
|
||||
return animationName;
|
||||
}
|
||||
set {
|
||||
if (animationName == value)
|
||||
return;
|
||||
animationName = value;
|
||||
#if AUTOINIT_SPINEREFERENCE
|
||||
Initialize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Animation Animation {
|
||||
get {
|
||||
#if AUTOINIT_SPINEREFERENCE
|
||||
if (animation == null)
|
||||
Initialize();
|
||||
#endif
|
||||
return animation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Clears the cached animation corresponding to a loaded SkeletonData object.
|
||||
/// Use this to force a reload for the next time Animation is called.</summary>
|
||||
public void Clear () {
|
||||
animation = null;
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
if (skeletonDataAsset == null) return;
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(AnimationReferenceAsset.QuietSkeletonData);
|
||||
this.animation = skeletonData != null ? skeletonData.FindAnimation(animationName) : null;
|
||||
if (this.animation == null) Debug.LogWarningFormat("Animation '{0}' not found in SkeletonData : {1}.", animationName, skeletonDataAsset.name);
|
||||
}
|
||||
|
||||
public static implicit operator Animation (AnimationReferenceAsset asset) {
|
||||
return asset.Animation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e3e95a05e4c9774397eeeb7bdee8ccb
|
||||
timeCreated: 1523328498
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 52b12ec801461494185a4d3dc66f3d1d, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 {
|
||||
public class AnimationReferenceAssetContainer : ScriptableObject {
|
||||
[SerializeField] protected SkeletonDataAsset skeletonDataAsset;
|
||||
public SkeletonDataAsset SkeletonDataAsset {
|
||||
get { return skeletonDataAsset; }
|
||||
set { skeletonDataAsset = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f30f83b4b98ce44cbae81286c0efac1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 52b12ec801461494185a4d3dc66f3d1d, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
#define SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
public abstract class AtlasAssetBase : ScriptableObject {
|
||||
public abstract Material PrimaryMaterial { get; }
|
||||
public abstract IEnumerable<Material> Materials { get; }
|
||||
public abstract int MaterialCount { get; }
|
||||
|
||||
public abstract bool IsLoaded { get; }
|
||||
public abstract void Clear ();
|
||||
public abstract Atlas GetAtlas (bool onlyMetaData = false);
|
||||
|
||||
protected void OnEnable () {
|
||||
runtimeMaterialOverrides = null;
|
||||
InitializeRuntimeOverrides();
|
||||
}
|
||||
|
||||
public bool HasMaterialOverrideSets {
|
||||
get {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
return serializedMaterialOverrides.Count > 0;
|
||||
}
|
||||
#endif
|
||||
return serializedMaterialOverrides.Count > 0 ||
|
||||
(runtimeMaterialOverrides != null && runtimeMaterialOverrides.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
public MaterialOverrideSet GetMaterialOverrideSet (string name) {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
return serializedMaterialOverrides.Find(entry => (entry.name == name));
|
||||
}
|
||||
#endif
|
||||
InitializeRuntimeOverrides();
|
||||
return runtimeMaterialOverrides.Find(entry => (entry.name == name));
|
||||
}
|
||||
|
||||
public MaterialOverrideSet AddMaterialOverrideSet (string name) {
|
||||
|
||||
var overrideSet = new MaterialOverrideSet(name);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
serializedMaterialOverrides.Add(overrideSet);
|
||||
return overrideSet;
|
||||
}
|
||||
#endif
|
||||
InitializeRuntimeOverrides();
|
||||
runtimeMaterialOverrides.Add(overrideSet);
|
||||
return overrideSet;
|
||||
}
|
||||
|
||||
protected void InitializeRuntimeOverrides () {
|
||||
if (runtimeMaterialOverrides == null) {
|
||||
runtimeMaterialOverrides = new List<MaterialOverrideSet>(serializedMaterialOverrides.Count);
|
||||
foreach (var srcOverrideSet in serializedMaterialOverrides) {
|
||||
runtimeMaterialOverrides.Add(new MaterialOverrideSet(srcOverrideSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] protected List<MaterialOverrideSet> serializedMaterialOverrides = new List<MaterialOverrideSet>();
|
||||
protected List<MaterialOverrideSet> runtimeMaterialOverrides;
|
||||
|
||||
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
public enum LoadingMode {
|
||||
Normal = 0,
|
||||
OnDemand
|
||||
}
|
||||
public virtual LoadingMode TextureLoadingMode {
|
||||
get { return textureLoadingMode; }
|
||||
set { textureLoadingMode = value; }
|
||||
}
|
||||
public OnDemandTextureLoader OnDemandTextureLoader {
|
||||
get { return onDemandTextureLoader; }
|
||||
set { onDemandTextureLoader = value; }
|
||||
}
|
||||
|
||||
public virtual void BeginCustomTextureLoading () {
|
||||
if (onDemandTextureLoader)
|
||||
onDemandTextureLoader.BeginCustomTextureLoading();
|
||||
}
|
||||
|
||||
public virtual void EndCustomTextureLoading () {
|
||||
if (onDemandTextureLoader)
|
||||
onDemandTextureLoader.EndCustomTextureLoading();
|
||||
}
|
||||
|
||||
public virtual void RequireTexturesLoaded (Material material, ref Material overrideMaterial) {
|
||||
if (onDemandTextureLoader)
|
||||
onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial);
|
||||
}
|
||||
|
||||
public virtual void RequireTextureLoaded (Texture placeholderTexture, ref Texture replacementTexture, System.Action<Texture> onTextureLoaded) {
|
||||
if (onDemandTextureLoader)
|
||||
onDemandTextureLoader.RequestLoadTexture(placeholderTexture, ref replacementTexture, onTextureLoaded);
|
||||
}
|
||||
|
||||
[SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal;
|
||||
[SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 787a36933c1c6e14db2104c01ed92dcb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,296 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[System.Serializable]
|
||||
public class BlendModeMaterials {
|
||||
|
||||
public const string MATERIAL_SUFFIX_MULTIPLY = "-Multiply";
|
||||
public const string MATERIAL_SUFFIX_SCREEN = "-Screen";
|
||||
public const string MATERIAL_SUFFIX_ADDITIVE = "-Additive";
|
||||
|
||||
[System.Serializable]
|
||||
public class ReplacementMaterial {
|
||||
public string pageName;
|
||||
public Material material;
|
||||
}
|
||||
|
||||
[SerializeField, HideInInspector] protected bool requiresBlendModeMaterials = false;
|
||||
public bool applyAdditiveMaterial = false;
|
||||
|
||||
public List<ReplacementMaterial> additiveMaterials = new List<ReplacementMaterial>();
|
||||
public List<ReplacementMaterial> multiplyMaterials = new List<ReplacementMaterial>();
|
||||
public List<ReplacementMaterial> screenMaterials = new List<ReplacementMaterial>();
|
||||
|
||||
public bool RequiresBlendModeMaterials { get { return requiresBlendModeMaterials; } set { requiresBlendModeMaterials = value; } }
|
||||
|
||||
public BlendMode BlendModeForMaterial (Material material) {
|
||||
foreach (ReplacementMaterial pair in multiplyMaterials)
|
||||
if (pair.material == material || (pair.material != null && pair.material.shader == material.shader))
|
||||
return BlendMode.Multiply;
|
||||
foreach (ReplacementMaterial pair in additiveMaterials)
|
||||
if (pair.material == material || (pair.material != null && pair.material.shader == material.shader))
|
||||
return BlendMode.Additive;
|
||||
foreach (ReplacementMaterial pair in screenMaterials)
|
||||
if (pair.material == material || (pair.material != null && pair.material.shader == material.shader))
|
||||
return BlendMode.Screen;
|
||||
return BlendMode.Normal;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void TransferSettingsFrom (BlendModeMaterialsAsset modifierAsset) {
|
||||
applyAdditiveMaterial = modifierAsset.applyAdditiveMaterial;
|
||||
}
|
||||
#endif
|
||||
|
||||
public bool UpdateBlendmodeMaterialsRequiredState (SkeletonData skeletonData) {
|
||||
requiresBlendModeMaterials = false;
|
||||
|
||||
if (skeletonData == null) return false;
|
||||
|
||||
List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
|
||||
SlotData[] slotsItems = skeletonData.Slots.Items;
|
||||
for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
|
||||
SlotData slot = slotsItems[slotIndex];
|
||||
if (slot.BlendMode == BlendMode.Normal) continue;
|
||||
if (!applyAdditiveMaterial && slot.BlendMode == BlendMode.Additive) continue;
|
||||
|
||||
skinEntries.Clear();
|
||||
foreach (Skin skin in skeletonData.Skins)
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (Skin.SkinEntry entry in skinEntries) {
|
||||
if (entry.Attachment is IHasSequence) {
|
||||
requiresBlendModeMaterials = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class TemplateMaterials {
|
||||
public Material additiveTemplate;
|
||||
public Material multiplyTemplate;
|
||||
public Material screenTemplate;
|
||||
};
|
||||
|
||||
public delegate bool CreateForRegionDelegate (ref List<BlendModeMaterials.ReplacementMaterial> replacementMaterials,
|
||||
ref bool anyReplacementMaterialsChanged,
|
||||
AtlasRegion originalRegion, Material materialTemplate, string materialSuffix,
|
||||
SkeletonDataAsset skeletonDataAsset);
|
||||
|
||||
public static bool CreateAndAssignMaterials (SkeletonDataAsset skeletonDataAsset,
|
||||
TemplateMaterials templateMaterials, ref bool anyReplacementMaterialsChanged) {
|
||||
|
||||
return CreateAndAssignMaterials(skeletonDataAsset,
|
||||
templateMaterials, ref anyReplacementMaterialsChanged,
|
||||
(asset) => { asset.Clear(); }, null, CreateForRegion);
|
||||
}
|
||||
|
||||
public static bool CreateAndAssignMaterials (SkeletonDataAsset skeletonDataAsset,
|
||||
TemplateMaterials templateMaterials, ref bool anyReplacementMaterialsChanged,
|
||||
System.Action<SkeletonDataAsset> clearSkeletonDataAssetFunc,
|
||||
System.Action<SkeletonDataAsset> afterAssetModifiedFunc,
|
||||
CreateForRegionDelegate createForRegionFunc) {
|
||||
|
||||
bool anyCreationFailed = false;
|
||||
BlendModeMaterials blendModeMaterials = skeletonDataAsset.blendModeMaterials;
|
||||
bool applyAdditiveMaterial = blendModeMaterials.applyAdditiveMaterial;
|
||||
|
||||
List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
|
||||
|
||||
clearSkeletonDataAssetFunc(skeletonDataAsset);
|
||||
skeletonDataAsset.isUpgradingBlendModeMaterials = true;
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true);
|
||||
|
||||
SlotData[] slotsItems = skeletonData.Slots.Items;
|
||||
for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
|
||||
SlotData slot = slotsItems[slotIndex];
|
||||
if (slot.BlendMode == BlendMode.Normal) continue;
|
||||
if (!applyAdditiveMaterial && slot.BlendMode == BlendMode.Additive) continue;
|
||||
|
||||
List<BlendModeMaterials.ReplacementMaterial> replacementMaterials = null;
|
||||
Material materialTemplate = null;
|
||||
string materialSuffix = null;
|
||||
switch (slot.BlendMode) {
|
||||
case BlendMode.Multiply:
|
||||
replacementMaterials = blendModeMaterials.multiplyMaterials;
|
||||
materialTemplate = templateMaterials.multiplyTemplate;
|
||||
materialSuffix = MATERIAL_SUFFIX_MULTIPLY;
|
||||
break;
|
||||
case BlendMode.Screen:
|
||||
replacementMaterials = blendModeMaterials.screenMaterials;
|
||||
materialTemplate = templateMaterials.screenTemplate;
|
||||
materialSuffix = MATERIAL_SUFFIX_SCREEN;
|
||||
break;
|
||||
case BlendMode.Additive:
|
||||
replacementMaterials = blendModeMaterials.additiveMaterials;
|
||||
materialTemplate = templateMaterials.additiveTemplate;
|
||||
materialSuffix = MATERIAL_SUFFIX_ADDITIVE;
|
||||
break;
|
||||
}
|
||||
|
||||
skinEntries.Clear();
|
||||
foreach (Skin skin in skeletonData.Skins)
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (Skin.SkinEntry entry in skinEntries) {
|
||||
IHasSequence renderableAttachment = entry.Attachment as IHasSequence;
|
||||
if (renderableAttachment != null) {
|
||||
Sequence sequence = renderableAttachment.Sequence;
|
||||
if (sequence != null && sequence.Regions != null) {
|
||||
for (int i = 0, count = sequence.Regions.Length; i < count; ++i) {
|
||||
AtlasRegion originalRegion = (AtlasRegion)sequence.Regions[i];
|
||||
anyCreationFailed |= createForRegionFunc(
|
||||
ref replacementMaterials, ref anyReplacementMaterialsChanged,
|
||||
originalRegion, materialTemplate, materialSuffix, skeletonDataAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skeletonDataAsset.isUpgradingBlendModeMaterials = false;
|
||||
if (afterAssetModifiedFunc != null) afterAssetModifiedFunc(skeletonDataAsset);
|
||||
return !anyCreationFailed;
|
||||
}
|
||||
|
||||
protected static bool CreateForRegion (ref List<BlendModeMaterials.ReplacementMaterial> replacementMaterials,
|
||||
ref bool anyReplacementMaterialsChanged,
|
||||
AtlasRegion originalRegion, Material materialTemplate, string materialSuffix,
|
||||
SkeletonDataAsset skeletonDataAsset) {
|
||||
|
||||
bool anyCreationFailed = false;
|
||||
bool replacementExists = replacementMaterials.Exists(
|
||||
replacement => replacement.pageName == originalRegion.page.name);
|
||||
if (!replacementExists) {
|
||||
BlendModeMaterials.ReplacementMaterial replacement = CreateReplacementMaterial(originalRegion, materialTemplate, materialSuffix);
|
||||
if (replacement != null) {
|
||||
replacementMaterials.Add(replacement);
|
||||
anyReplacementMaterialsChanged = true;
|
||||
} else {
|
||||
Debug.LogError(string.Format("Failed creating blend mode Material for SkeletonData asset '{0}'," +
|
||||
" atlas page '{1}', template '{2}'.",
|
||||
skeletonDataAsset.name, originalRegion.page.name, materialTemplate.name),
|
||||
skeletonDataAsset);
|
||||
anyCreationFailed = true;
|
||||
}
|
||||
}
|
||||
return anyCreationFailed;
|
||||
}
|
||||
|
||||
protected static BlendModeMaterials.ReplacementMaterial CreateReplacementMaterial (
|
||||
AtlasRegion originalRegion, Material materialTemplate, string materialSuffix) {
|
||||
|
||||
BlendModeMaterials.ReplacementMaterial newReplacement = new BlendModeMaterials.ReplacementMaterial();
|
||||
AtlasPage originalPage = originalRegion.page;
|
||||
Material originalMaterial = originalPage.rendererObject as Material;
|
||||
|
||||
newReplacement.pageName = originalPage.name;
|
||||
|
||||
Material blendModeMaterial = new Material(materialTemplate) {
|
||||
name = originalMaterial.name + " " + materialTemplate.name,
|
||||
mainTexture = originalMaterial.mainTexture
|
||||
};
|
||||
newReplacement.material = blendModeMaterial;
|
||||
|
||||
if (newReplacement.material)
|
||||
return newReplacement;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public void ApplyMaterials (SkeletonData skeletonData) {
|
||||
if (skeletonData == null) throw new ArgumentNullException("skeletonData");
|
||||
if (!requiresBlendModeMaterials)
|
||||
return;
|
||||
|
||||
List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
|
||||
SlotData[] slotsItems = skeletonData.Slots.Items;
|
||||
for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
|
||||
SlotData slot = slotsItems[slotIndex];
|
||||
if (slot.BlendMode == BlendMode.Normal) continue;
|
||||
if (!applyAdditiveMaterial && slot.BlendMode == BlendMode.Additive) continue;
|
||||
|
||||
List<ReplacementMaterial> replacementMaterials = null;
|
||||
switch (slot.BlendMode) {
|
||||
case BlendMode.Multiply:
|
||||
replacementMaterials = multiplyMaterials;
|
||||
break;
|
||||
case BlendMode.Screen:
|
||||
replacementMaterials = screenMaterials;
|
||||
break;
|
||||
case BlendMode.Additive:
|
||||
replacementMaterials = additiveMaterials;
|
||||
break;
|
||||
}
|
||||
if (replacementMaterials == null)
|
||||
continue;
|
||||
|
||||
skinEntries.Clear();
|
||||
foreach (Skin skin in skeletonData.Skins)
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (Skin.SkinEntry entry in skinEntries) {
|
||||
IHasSequence renderableAttachment = entry.Attachment as IHasSequence;
|
||||
if (renderableAttachment != null) {
|
||||
TextureRegion[] regions = renderableAttachment.Sequence.Regions;
|
||||
for (int i = 0; i < regions.Length; ++i) {
|
||||
regions[i] = CloneAtlasRegionWithMaterial(
|
||||
(AtlasRegion)regions[i], replacementMaterials);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected AtlasRegion CloneAtlasRegionWithMaterial (AtlasRegion originalRegion, List<ReplacementMaterial> replacementMaterials) {
|
||||
AtlasRegion newRegion = originalRegion.Clone();
|
||||
Material material = null;
|
||||
foreach (ReplacementMaterial replacement in replacementMaterials) {
|
||||
if (replacement.pageName == originalRegion.page.name) {
|
||||
material = replacement.material;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AtlasPage originalPage = originalRegion.page;
|
||||
AtlasPage newPage = originalPage.Clone();
|
||||
newPage.rendererObject = material;
|
||||
newRegion.page = newPage;
|
||||
return newRegion;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d03ca55657e89b949a4c07bc9207beac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
#define AUTOINIT_SPINEREFERENCE
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[CreateAssetMenu(menuName = "Spine/EventData Reference Asset", order = 100)]
|
||||
public class EventDataReferenceAsset : ScriptableObject {
|
||||
const bool QuietSkeletonData = true;
|
||||
|
||||
[SerializeField] protected SkeletonDataAsset skeletonDataAsset;
|
||||
[SerializeField, SpineEvent(dataField: "skeletonDataAsset")] protected string eventName;
|
||||
|
||||
EventData eventData;
|
||||
public EventData EventData {
|
||||
get {
|
||||
#if AUTOINIT_SPINEREFERENCE
|
||||
if (eventData == null)
|
||||
Initialize();
|
||||
#endif
|
||||
return eventData;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
if (skeletonDataAsset == null)
|
||||
return;
|
||||
this.eventData = skeletonDataAsset.GetSkeletonData(EventDataReferenceAsset.QuietSkeletonData).FindEvent(eventName);
|
||||
if (this.eventData == null)
|
||||
Debug.LogWarningFormat("Event Data '{0}' not found in SkeletonData : {1}.", eventName, skeletonDataAsset.name);
|
||||
}
|
||||
|
||||
public static implicit operator EventData (EventDataReferenceAsset asset) {
|
||||
return asset.EventData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e60be42c1473144db0fd3337c25b500
|
||||
timeCreated: 1523330891
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: d226a80acc775714aa78b85e16a00e9b, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// A set of Material overrides to replace one material with another (e.g. an inside-mask variant).
|
||||
/// Used by <see cref="AtlasAssetBase"/> to hold all mask material overrides in a common location
|
||||
/// to avoid duplicate identical materials, and thus unnecessary draw calls.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class MaterialOverrideSet {
|
||||
public string name;
|
||||
|
||||
[SerializeField] protected List<Material> dictionaryKeys = new List<Material>();
|
||||
[SerializeField] protected List<Material> dictionaryValues = new List<Material>();
|
||||
|
||||
public MaterialOverrideSet (string name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public MaterialOverrideSet (MaterialOverrideSet src) {
|
||||
this.name = src.name;
|
||||
this.dictionaryKeys = new List<Material>(src.dictionaryKeys);
|
||||
this.dictionaryValues = new List<Material>(src.dictionaryValues);
|
||||
}
|
||||
|
||||
/// <summary>Adds an override from <c>originalMaterial</c> to an <c>overrideMaterial</c>.
|
||||
/// The caller is responsible to ensure that an entry mapping from <c>originalMaterial</c>
|
||||
/// is not already present.</summary>
|
||||
public void AddOverride (Material originalMaterial, Material overrideMaterial) {
|
||||
dictionaryKeys.Add(originalMaterial);
|
||||
dictionaryValues.Add(overrideMaterial);
|
||||
}
|
||||
|
||||
/// <summary>Removes a previously added override from <c>originalMaterial</c>.</summary>
|
||||
public void RemoveOverride (Material originalMaterial) {
|
||||
int existingIndex = dictionaryKeys.IndexOf(originalMaterial);
|
||||
if (existingIndex >= 0) {
|
||||
dictionaryKeys.RemoveAt(existingIndex);
|
||||
dictionaryValues.RemoveAt(existingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets an existing override from <c>originalMaterial</c> to an <c>overrideMaterial</c>
|
||||
/// if an entry for <c>originalMaterial</c> already exists. Otherwise it adds the respective entry.
|
||||
/// </summary>
|
||||
public void SetOverride (Material originalMaterial, Material overrideMaterial) {
|
||||
int existingIndex = dictionaryKeys.IndexOf(originalMaterial);
|
||||
if (existingIndex < 0) {
|
||||
dictionaryKeys.Add(originalMaterial);
|
||||
dictionaryValues.Add(overrideMaterial);
|
||||
} else {
|
||||
dictionaryValues[existingIndex] = overrideMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies previously set overrides in-place to the given <c>materials</c> array,
|
||||
/// replacing the respective reference to an <c>originalMaterial</c> with the corresponding
|
||||
/// <c>overrideMaterial</c>.</summary>
|
||||
public void ApplyOverrideTo (Material[] materials) {
|
||||
if (dictionaryKeys.Count == 0) return;
|
||||
|
||||
for (int i = 0, count = materials.Length; i < count; ++i) {
|
||||
int dictIndex = dictionaryKeys.IndexOf(materials[i]);
|
||||
if (dictIndex >= 0)
|
||||
materials[i] = dictionaryValues[dictIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c19aeacc73140c14796901a28c27cdf9
|
||||
timeCreated: 1686127376
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,136 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
#define SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
namespace Spine.Unity {
|
||||
public abstract class OnDemandTextureLoader : ScriptableObject {
|
||||
public AtlasAssetBase atlasAsset;
|
||||
/// <summary>
|
||||
/// Additional <see cref="SkeletonDataAsset"/> reference, currently only used to cover blend mode materials
|
||||
/// which are not stored at <c>atlasAsset</c>.
|
||||
/// </summary>
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
|
||||
/// <param name="originalTextureName">Original texture name without extension.</param>
|
||||
/// <returns>The placeholder texture's name for a given original target texture name.</returns>
|
||||
public abstract string GetPlaceholderTextureName (string originalTextureName);
|
||||
/// <summary>
|
||||
/// Assigns previously setup placeholder textures at each Material of the associated AtlasAssetBase.</summary>
|
||||
/// <returns>True on success, false if the placeholder texture could not be assigned at any of the
|
||||
/// AtlasAssetBase's materials.</returns>
|
||||
public abstract bool AssignPlaceholderTextures (out IEnumerable<Material> modifiedMaterials);
|
||||
/// <summary>
|
||||
/// Returns whether any placeholder textures are assigned at the Material of the associated AtlasAssetBase.
|
||||
/// </summary>
|
||||
/// <param name="placeholderMaterials">A newly created list of materials which has a placeholder texture assigned.</param>
|
||||
/// <returns>True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase.</returns>
|
||||
public abstract bool HasPlaceholderTexturesAssigned (out List<Material> placeholderMaterials);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether any main texture is null at a Material of the associated AtlasAssetBase.
|
||||
/// </summary>
|
||||
/// <param name="nullTextureMaterials">A newly created list of materials which has a null main texture assigned.</param>
|
||||
/// <returns>True, if any null main texture is assigned at a Material of the associated AtlasAssetBase.</returns>
|
||||
public virtual bool HasNullMainTexturesAssigned (out List<Material> nullTextureMaterials) {
|
||||
nullTextureMaterials = null;
|
||||
if (!atlasAsset) return false;
|
||||
|
||||
bool anyNullTexture = false;
|
||||
foreach (Material material in atlasAsset.Materials) {
|
||||
if (material.mainTexture == null) {
|
||||
anyNullTexture = true;
|
||||
if (nullTextureMaterials == null) nullTextureMaterials = new List<Material>();
|
||||
nullTextureMaterials.Add(material);
|
||||
}
|
||||
}
|
||||
return anyNullTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns previously setup target textures at each Material where placeholder textures are setup.</summary>
|
||||
/// <returns>True on success, false if the target texture could not be assigned at any of the
|
||||
/// AtlasAssetBase's materials.</returns>
|
||||
public abstract bool AssignTargetTextures (out IEnumerable<Material> modifiedMaterials);
|
||||
public abstract void BeginCustomTextureLoading ();
|
||||
public abstract void EndCustomTextureLoading ();
|
||||
public abstract bool HasPlaceholderAssigned (Material material);
|
||||
public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial);
|
||||
public abstract void RequestLoadTexture (Texture placeholderTexture, ref Texture replacementTexture,
|
||||
System.Action<Texture> onTextureLoaded = null);
|
||||
public abstract void Clear (bool clearAtlasAsset = false);
|
||||
|
||||
#region Event delegates
|
||||
public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex);
|
||||
protected event TextureLoadDelegate onTextureRequested;
|
||||
protected event TextureLoadDelegate onTextureLoaded;
|
||||
protected event TextureLoadDelegate onTextureLoadFailed;
|
||||
protected event TextureLoadDelegate onTextureUnloaded;
|
||||
|
||||
public event TextureLoadDelegate TextureRequested {
|
||||
add { onTextureRequested += value; }
|
||||
remove { onTextureRequested -= value; }
|
||||
}
|
||||
public event TextureLoadDelegate TextureLoaded {
|
||||
add { onTextureLoaded += value; }
|
||||
remove { onTextureLoaded -= value; }
|
||||
}
|
||||
public event TextureLoadDelegate TextureLoadFailed {
|
||||
add { onTextureLoadFailed += value; }
|
||||
remove { onTextureLoadFailed -= value; }
|
||||
}
|
||||
public event TextureLoadDelegate TextureUnloaded {
|
||||
add { onTextureUnloaded += value; }
|
||||
remove { onTextureUnloaded -= value; }
|
||||
}
|
||||
|
||||
protected void OnTextureRequested (Material material, int textureIndex) {
|
||||
if (onTextureRequested != null)
|
||||
onTextureRequested(this, material, textureIndex);
|
||||
}
|
||||
protected void OnTextureLoaded (Material material, int textureIndex) {
|
||||
if (onTextureLoaded != null)
|
||||
onTextureLoaded(this, material, textureIndex);
|
||||
}
|
||||
protected void OnTextureLoadFailed (Material material, int textureIndex) {
|
||||
if (onTextureLoadFailed != null)
|
||||
onTextureLoadFailed(this, material, textureIndex);
|
||||
}
|
||||
protected void OnTextureUnloaded (Material material, int textureIndex) {
|
||||
if (onTextureUnloaded != null)
|
||||
onTextureUnloaded(this, material, textureIndex);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57d54a357da79654aad505112bb63b9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,85 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
public class RegionlessAttachmentLoader : AttachmentLoader {
|
||||
|
||||
static AtlasRegion emptyRegion;
|
||||
static AtlasRegion EmptyRegion {
|
||||
get {
|
||||
if (emptyRegion == null) {
|
||||
Shader hiddenShader = Shader.Find("Spine/Special/HiddenPass");
|
||||
if (hiddenShader == null) {
|
||||
Debug.LogError("Shader \"Spine/Special/HiddenPass\" not found while loading SkeletonDataAsset" +
|
||||
" with 0 Atlas Assets. Please add this shader to Project Settings - Graphics - Always" +
|
||||
" Included Shaders, or make sure your SkeletonDataAssets all have an AtlasAsset assigned.");
|
||||
}
|
||||
emptyRegion = new AtlasRegion {
|
||||
name = "Empty AtlasRegion",
|
||||
page = new AtlasPage {
|
||||
name = "Empty AtlasPage",
|
||||
rendererObject = new Material(hiddenShader) { name = "NoRender Material" }
|
||||
}
|
||||
};
|
||||
}
|
||||
return emptyRegion;
|
||||
}
|
||||
}
|
||||
|
||||
public RegionAttachment NewRegionAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence) {
|
||||
RegionAttachment attachment = new RegionAttachment(name, new Sequence(1, false));
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public MeshAttachment NewMeshAttachment (Skin skin, string placeholder, string name, string path, Sequence sequence) {
|
||||
MeshAttachment attachment = new MeshAttachment(name, new Sequence(1, false));
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string placeholder, string name) {
|
||||
return new BoundingBoxAttachment(name);
|
||||
}
|
||||
|
||||
public PathAttachment NewPathAttachment (Skin skin, string placeholder, string name) {
|
||||
return new PathAttachment(name);
|
||||
}
|
||||
|
||||
public PointAttachment NewPointAttachment (Skin skin, string placeholder, string name) {
|
||||
return new PointAttachment(name);
|
||||
}
|
||||
|
||||
public ClippingAttachment NewClippingAttachment (Skin skin, string placeholder, string name) {
|
||||
return new ClippingAttachment(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15f0f78b87720c047a320c5e0e3f91b7
|
||||
timeCreated: 1520505662
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,315 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
//#define SPINE_ALLOW_UNSAFE // note: this define can be set via Edit - Preferences - Spine.
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#endif
|
||||
|
||||
#if SPINE_ALLOW_UNSAFE && TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#define UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using Unity.Collections;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
public static class TextAssetExtensions {
|
||||
public static Stream GetStreamUnsafe (this TextAsset textAsset) {
|
||||
NativeArray<byte> dataNativeArray = textAsset.GetData<byte>();
|
||||
return dataNativeArray.GetUnmanagedMemoryStream();
|
||||
}
|
||||
|
||||
public static unsafe UnmanagedMemoryStream GetUnmanagedMemoryStream<T> (this NativeArray<T> nativeArray) where T : struct {
|
||||
return new UnmanagedMemoryStream((byte*)global::Unity.Collections.LowLevel.Unsafe.
|
||||
NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(nativeArray), nativeArray.Length);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[CreateAssetMenu(fileName = "New SkeletonDataAsset", menuName = "Spine/SkeletonData Asset")]
|
||||
public class SkeletonDataAsset : ScriptableObject {
|
||||
#region Inspector
|
||||
public AtlasAssetBase[] atlasAssets = new AtlasAssetBase[0];
|
||||
|
||||
public float scale = 0.01f;
|
||||
public TextAsset skeletonJSON;
|
||||
|
||||
public bool isUpgradingBlendModeMaterials = false;
|
||||
public BlendModeMaterials blendModeMaterials = new BlendModeMaterials();
|
||||
|
||||
[Tooltip("Use SkeletonDataModifierAssets to apply changes to the SkeletonData after being loaded, such as apply blend mode Materials to Attachments under slots with special blend modes.")]
|
||||
public List<SkeletonDataModifierAsset> skeletonDataModifiers = new List<SkeletonDataModifierAsset>();
|
||||
|
||||
[SpineAnimation(includeNone: false)]
|
||||
public string[] fromAnimation = new string[0];
|
||||
[SpineAnimation(includeNone: false)]
|
||||
public string[] toAnimation = new string[0];
|
||||
public float[] duration = new float[0];
|
||||
public float defaultMix;
|
||||
public RuntimeAnimatorController controller;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static bool errorIfSkeletonFileNullGlobal = true;
|
||||
#endif
|
||||
|
||||
public bool IsLoaded { get { return this.skeletonData != null; } }
|
||||
|
||||
void Reset () {
|
||||
Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
SkeletonData skeletonData;
|
||||
AnimationStateData stateData;
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>
|
||||
/// Creates a runtime SkeletonDataAsset.</summary>
|
||||
public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAssetBase atlasAsset, bool initialize, float scale = 0.01f) {
|
||||
return CreateRuntimeInstance(skeletonDataFile, new[] { atlasAsset }, initialize, scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a runtime SkeletonDataAsset.
|
||||
/// If you require blend mode materials, call <see cref="SetupRuntimeBlendModeMaterials"/> afterwards.</summary>
|
||||
public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAssetBase[] atlasAssets, bool initialize, float scale = 0.01f) {
|
||||
SkeletonDataAsset skeletonDataAsset = ScriptableObject.CreateInstance<SkeletonDataAsset>();
|
||||
skeletonDataAsset.Clear();
|
||||
skeletonDataAsset.skeletonJSON = skeletonDataFile;
|
||||
skeletonDataAsset.atlasAssets = atlasAssets;
|
||||
skeletonDataAsset.scale = scale;
|
||||
|
||||
if (initialize)
|
||||
skeletonDataAsset.GetSkeletonData(true);
|
||||
|
||||
return skeletonDataAsset;
|
||||
}
|
||||
|
||||
/// <summary>If this SkeletonDataAsset has been created via <see cref="CreateRuntimeInstance"/>,
|
||||
/// this method sets up blend mode materials for it.</summary>
|
||||
public void SetupRuntimeBlendModeMaterials (bool applyAdditiveMaterial,
|
||||
BlendModeMaterials.TemplateMaterials templateMaterials) {
|
||||
blendModeMaterials.applyAdditiveMaterial = applyAdditiveMaterial;
|
||||
blendModeMaterials.UpdateBlendmodeMaterialsRequiredState(GetSkeletonData(true));
|
||||
bool anyMaterialsChanged = false;
|
||||
BlendModeMaterials.CreateAndAssignMaterials(this, templateMaterials, ref anyMaterialsChanged);
|
||||
|
||||
Clear();
|
||||
GetSkeletonData(true);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>Clears the loaded SkeletonData and AnimationStateData. Use this to force a reload for the next time GetSkeletonData is called.</summary>
|
||||
public void Clear () {
|
||||
skeletonData = null;
|
||||
stateData = null;
|
||||
}
|
||||
|
||||
public AnimationStateData GetAnimationStateData () {
|
||||
if (stateData != null)
|
||||
return stateData;
|
||||
GetSkeletonData(false);
|
||||
return stateData;
|
||||
}
|
||||
|
||||
/// <summary>Loads, caches and returns the SkeletonData from the skeleton data file. Returns the cached SkeletonData after the first time it is called.</summary>
|
||||
/// <param name="quiet">Pass true to prevent direct errors from being logged.</param>
|
||||
public SkeletonData GetSkeletonData (bool quiet) {
|
||||
if (skeletonJSON == null) {
|
||||
#if UNITY_EDITOR
|
||||
if (!errorIfSkeletonFileNullGlobal) quiet = true;
|
||||
#endif
|
||||
if (!quiet)
|
||||
Debug.LogError("Skeleton JSON file not set for SkeletonData asset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Disabled to support attachmentless/skinless SkeletonData.
|
||||
// if (atlasAssets == null) {
|
||||
// atlasAssets = new AtlasAsset[0];
|
||||
// if (!quiet)
|
||||
// Debug.LogError("Atlas not set for SkeletonData asset: " + name, this);
|
||||
// Clear();
|
||||
// return null;
|
||||
// }
|
||||
// if (atlasAssets.Length == 0) {
|
||||
// Clear();
|
||||
// return null;
|
||||
// }
|
||||
|
||||
if (skeletonData != null)
|
||||
return skeletonData;
|
||||
|
||||
AttachmentLoader attachmentLoader;
|
||||
float skeletonDataScale;
|
||||
Atlas[] atlasArray = this.GetAtlasArray();
|
||||
|
||||
attachmentLoader = (atlasArray.Length == 0) ? (AttachmentLoader)new RegionlessAttachmentLoader() : (AttachmentLoader)new AtlasAttachmentLoader(atlasArray);
|
||||
skeletonDataScale = scale;
|
||||
|
||||
bool hasBinaryExtension = skeletonJSON.name.ToLower().Contains(".skel");
|
||||
SkeletonData loadedSkeletonData = null;
|
||||
|
||||
try {
|
||||
if (hasBinaryExtension) {
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using (Stream stream = skeletonJSON.GetStreamUnsafe()) {
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(stream, attachmentLoader, skeletonDataScale);
|
||||
}
|
||||
#else
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes, attachmentLoader, skeletonDataScale);
|
||||
#endif
|
||||
} else
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.text, attachmentLoader, skeletonDataScale);
|
||||
} catch (Exception ex) {
|
||||
if (!quiet) {
|
||||
string formatString = hasBinaryExtension ? ".skel" : "JSON";
|
||||
string innerExceptionLine = ex.InnerException != null ? ex.InnerException + "\n" : "";
|
||||
Debug.LogError(string.Format("Error reading skeleton {0} file for SkeletonData asset: {1}\n{2}\n{3}{4}",
|
||||
formatString, name, ex.Message, innerExceptionLine, ex.StackTrace), skeletonJSON);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (loadedSkeletonData == null && !quiet && skeletonJSON != null) {
|
||||
string problemDescription = null;
|
||||
bool isSpineSkeletonData;
|
||||
SkeletonDataCompatibility.VersionInfo fileVersion = SkeletonDataCompatibility.GetVersionInfo(skeletonJSON, out isSpineSkeletonData, ref problemDescription);
|
||||
if (problemDescription != null) {
|
||||
if (!quiet)
|
||||
Debug.LogError(problemDescription, skeletonJSON);
|
||||
return null;
|
||||
}
|
||||
CompatibilityProblemInfo compatibilityProblemInfo = SkeletonDataCompatibility.GetCompatibilityProblemInfo(fileVersion);
|
||||
if (compatibilityProblemInfo != null) {
|
||||
SkeletonDataCompatibility.DisplayCompatibilityProblem(compatibilityProblemInfo.DescriptionString(), skeletonJSON);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (loadedSkeletonData == null)
|
||||
return null;
|
||||
|
||||
if (skeletonDataModifiers != null) {
|
||||
foreach (SkeletonDataModifierAsset modifier in skeletonDataModifiers) {
|
||||
if (modifier != null && !(isUpgradingBlendModeMaterials && modifier is BlendModeMaterialsAsset)) {
|
||||
modifier.Apply(loadedSkeletonData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isUpgradingBlendModeMaterials)
|
||||
blendModeMaterials.ApplyMaterials(loadedSkeletonData);
|
||||
|
||||
this.InitializeWithData(loadedSkeletonData);
|
||||
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
internal void InitializeWithData (SkeletonData sd) {
|
||||
this.skeletonData = sd;
|
||||
this.stateData = new AnimationStateData(skeletonData);
|
||||
FillStateData();
|
||||
}
|
||||
|
||||
public void FillStateData (bool quiet = false) {
|
||||
if (stateData != null) {
|
||||
stateData.DefaultMix = defaultMix;
|
||||
|
||||
for (int i = 0, n = fromAnimation.Length; i < n; i++) {
|
||||
string fromAnimationName = fromAnimation[i];
|
||||
string toAnimationName = toAnimation[i];
|
||||
if (fromAnimationName.Length == 0 || toAnimationName.Length == 0)
|
||||
continue;
|
||||
#if UNITY_EDITOR
|
||||
if (skeletonData.FindAnimation(fromAnimationName) == null) {
|
||||
if (!quiet) Debug.LogError(
|
||||
string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?",
|
||||
fromAnimationName), this);
|
||||
continue;
|
||||
}
|
||||
if (skeletonData.FindAnimation(toAnimationName) == null) {
|
||||
if (!quiet) Debug.LogError(
|
||||
string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?",
|
||||
toAnimationName), this);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
stateData.SetMix(fromAnimationName, toAnimationName, duration[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Atlas[] GetAtlasArray () {
|
||||
List<Atlas> returnList = new System.Collections.Generic.List<Atlas>(atlasAssets.Length);
|
||||
for (int i = 0; i < atlasAssets.Length; i++) {
|
||||
AtlasAssetBase aa = atlasAssets[i];
|
||||
if (aa == null) continue;
|
||||
Atlas a = aa.GetAtlas();
|
||||
if (a == null) continue;
|
||||
returnList.Add(a);
|
||||
}
|
||||
return returnList.ToArray();
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (byte[] bytes, AttachmentLoader attachmentLoader, float scale) {
|
||||
using (MemoryStream input = new MemoryStream(bytes)) {
|
||||
SkeletonBinary binary = new SkeletonBinary(attachmentLoader) {
|
||||
Scale = scale
|
||||
};
|
||||
return binary.ReadSkeletonData(input);
|
||||
}
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (Stream assetStream, AttachmentLoader attachmentLoader, float scale) {
|
||||
SkeletonBinary binary = new SkeletonBinary(attachmentLoader) {
|
||||
Scale = scale
|
||||
};
|
||||
return binary.ReadSkeletonData(assetStream);
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (string text, AttachmentLoader attachmentLoader, float scale) {
|
||||
StringReader input = new StringReader(text);
|
||||
SkeletonJson json = new SkeletonJson(attachmentLoader) {
|
||||
Scale = scale
|
||||
};
|
||||
return json.ReadSkeletonData(input);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1b3b4b945939a54ea0b23d3396115fb
|
||||
timeCreated: 1536403985
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- multiplyMaterialTemplate: {fileID: 2100000, guid: 53bf0ab317d032d418cf1252d68f51df,
|
||||
type: 2}
|
||||
- screenMaterialTemplate: {fileID: 2100000, guid: 73f0f46d3177c614baf0fa48d646a9be,
|
||||
type: 2}
|
||||
- additiveMaterialTemplate: {fileID: 2100000, guid: 4deba332d47209e4780b3c5fcf0e3745,
|
||||
type: 2}
|
||||
- skeletonJSON: {instanceID: 0}
|
||||
- controller: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 68defdbc95b30a74a9ad396bfc9a2277, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,247 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
//#define SPINE_ALLOW_UNSAFE // note: this define can be set via Edit - Preferences - Spine.
|
||||
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
#define TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#endif
|
||||
|
||||
#if SPINE_ALLOW_UNSAFE && TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
#define UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
public static class SkeletonDataCompatibility {
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static readonly int[][] compatibleBinaryVersions = { new[] { 4, 3, 0 } };
|
||||
static readonly int[][] compatibleJsonVersions = { new[] { 4, 3, 0 } };
|
||||
|
||||
static bool wasVersionDialogShown = false;
|
||||
static readonly Regex jsonVersionRegex = new Regex(@"""spine""\s*:\s*""([^""]+)""", RegexOptions.CultureInvariant);
|
||||
#endif
|
||||
|
||||
public enum SourceType {
|
||||
Json,
|
||||
Binary
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class VersionInfo {
|
||||
public string rawVersion = null;
|
||||
public int[] version = null;
|
||||
public SourceType sourceType;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class CompatibilityProblemInfo {
|
||||
public VersionInfo actualVersion;
|
||||
public int[][] compatibleVersions;
|
||||
public string explicitProblemDescription = null;
|
||||
|
||||
public string DescriptionString () {
|
||||
if (!string.IsNullOrEmpty(explicitProblemDescription))
|
||||
return explicitProblemDescription;
|
||||
|
||||
string compatibleVersionString = "";
|
||||
string optionalOr = null;
|
||||
foreach (int[] version in compatibleVersions) {
|
||||
compatibleVersionString += string.Format("{0}{1}.{2}", optionalOr, version[0], version[1]);
|
||||
optionalOr = " or ";
|
||||
}
|
||||
return string.Format("Skeleton data could not be loaded. Data version: {0}. Required version: {1}.\nPlease re-export skeleton data with Spine {1} or change runtime to version {2}.{3}.",
|
||||
actualVersion.rawVersion, compatibleVersionString, actualVersion.version[0], actualVersion.version[1]);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static VersionInfo GetVersionInfo (TextAsset asset, out bool isSpineSkeletonData, ref string problemDescription) {
|
||||
isSpineSkeletonData = false;
|
||||
if (asset == null)
|
||||
return null;
|
||||
|
||||
VersionInfo fileVersion = new VersionInfo();
|
||||
bool hasBinaryExtension = asset.name.Contains(".skel");
|
||||
fileVersion.sourceType = hasBinaryExtension ? SourceType.Binary : SourceType.Json;
|
||||
|
||||
bool isJsonFileByContent = IsJsonFile(asset);
|
||||
if (hasBinaryExtension == isJsonFileByContent) {
|
||||
if (hasBinaryExtension) {
|
||||
problemDescription = string.Format("Failed to read '{0}'. Extension is '.skel.bytes' but content looks like a '.json' file.\n"
|
||||
+ "Did you choose the wrong extension upon export?\n", asset.name);
|
||||
} else {
|
||||
problemDescription = string.Format("Failed to read '{0}'. Extension is '.json' but content looks like binary 'skel.bytes' file.\n"
|
||||
+ "Did you choose the wrong extension upon export?\n", asset.name);
|
||||
}
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (fileVersion.sourceType == SourceType.Binary) {
|
||||
try {
|
||||
#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA
|
||||
using (Stream stream = asset.GetStreamUnsafe()) {
|
||||
#else
|
||||
using (MemoryStream stream = new MemoryStream(asset.bytes)) {
|
||||
#endif
|
||||
fileVersion.rawVersion = SkeletonBinary.GetVersionString(stream);
|
||||
}
|
||||
} catch (System.Exception e) {
|
||||
problemDescription = string.Format("Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}", asset.name, e);
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Match match = jsonVersionRegex.Match(asset.text);
|
||||
if (match != null) {
|
||||
fileVersion.rawVersion = match.Groups[1].Value;
|
||||
} else {
|
||||
object obj = Json.Deserialize(new StringReader(asset.text));
|
||||
if (obj == null) {
|
||||
problemDescription = string.Format("'{0}' is not valid JSON.", asset.name);
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
Dictionary<string, object> root = obj as Dictionary<string, object>;
|
||||
if (root == null) {
|
||||
problemDescription = string.Format("'{0}' is not compatible JSON. Parser returned an incorrect type while parsing version info.", asset.name);
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (root.ContainsKey("skeleton")) {
|
||||
Dictionary<string, object> skeletonInfo = (Dictionary<string, object>)root["skeleton"];
|
||||
object jv;
|
||||
skeletonInfo.TryGetValue("spine", out jv);
|
||||
fileVersion.rawVersion = jv as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileVersion.rawVersion)) {
|
||||
// very likely not a Spine skeleton json file at all. Could be another valid json file, don't report errors.
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
string[] versionSplit = fileVersion.rawVersion.Split('.');
|
||||
try {
|
||||
fileVersion.version = new[]{ int.Parse(versionSplit[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(versionSplit[1], CultureInfo.InvariantCulture) };
|
||||
} catch (System.Exception e) {
|
||||
if (fileVersion.rawVersion.Contains("-from-")) {
|
||||
problemDescription = string.Format("Failed to import skeleton downgrade asset '{0}'. " +
|
||||
"Lower-version exports like 4.2 from Spine 4.3 are only for project downgrades and can't be loaded by runtimes directly.\n" +
|
||||
"Open in the matching Spine version and re-export for runtime use.\n", asset.name);
|
||||
} else {
|
||||
problemDescription = string.Format("Failed to read version info at skeleton '{0}'. It is likely not a valid Spine SkeletonData file.\n{1}", asset.name, e);
|
||||
}
|
||||
|
||||
isSpineSkeletonData = false;
|
||||
return null;
|
||||
}
|
||||
isSpineSkeletonData = true;
|
||||
return fileVersion;
|
||||
}
|
||||
|
||||
public static bool IsJsonFile (TextAsset file) {
|
||||
#if TEXT_ASSET_HAS_GET_DATA_BYTES
|
||||
var content = file.GetData<byte>();
|
||||
#else
|
||||
byte[] content = file.bytes;
|
||||
#endif
|
||||
// check for binary skeleton version number string, starts after 8 byte hash
|
||||
char majorVersionChar = compatibleBinaryVersions[0][0].ToString()[0];
|
||||
if (content.Length > 10 && content[9] == majorVersionChar && content[10] == '.')
|
||||
return false;
|
||||
|
||||
const int maxCharsToCheck = 256;
|
||||
int numCharsToCheck = Math.Min(content.Length, maxCharsToCheck);
|
||||
int i = 0;
|
||||
if (content.Length >= 3 && content[0] == 0xEF && content[1] == 0xBB && content[2] == 0xBF) // skip potential BOM
|
||||
i = 3;
|
||||
bool openingBraceFound = false;
|
||||
for (; i < numCharsToCheck; ++i) {
|
||||
char c = (char)content[i];
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
if (!openingBraceFound) {
|
||||
if (c == '{' || c == '[') openingBraceFound = true;
|
||||
else return false;
|
||||
} else if (c == '{' || c == '[' || c == ']' || c == '}' || c == ',')
|
||||
continue;
|
||||
else
|
||||
return c == '"';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static CompatibilityProblemInfo GetCompatibilityProblemInfo (VersionInfo fileVersion) {
|
||||
if (fileVersion == null) {
|
||||
return null; // it's most likely not a Spine skeleton file, e.g. another json file. don't report problems.
|
||||
}
|
||||
|
||||
CompatibilityProblemInfo info = new CompatibilityProblemInfo();
|
||||
info.actualVersion = fileVersion;
|
||||
info.compatibleVersions = (fileVersion.sourceType == SourceType.Binary) ? compatibleBinaryVersions
|
||||
: compatibleJsonVersions;
|
||||
|
||||
foreach (int[] compatibleVersion in info.compatibleVersions) {
|
||||
bool majorMatch = fileVersion.version[0] == compatibleVersion[0];
|
||||
bool minorMatch = fileVersion.version[1] == compatibleVersion[1];
|
||||
if (majorMatch && minorMatch) {
|
||||
return null; // is compatible, thus no problem info returned
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public static void DisplayCompatibilityProblem (string descriptionString, TextAsset spineJson) {
|
||||
if (!wasVersionDialogShown) {
|
||||
wasVersionDialogShown = true;
|
||||
UnityEditor.EditorUtility.DisplayDialog("Version mismatch!", descriptionString, "OK");
|
||||
}
|
||||
Debug.LogError(string.Format("Error importing skeleton '{0}': {1}",
|
||||
spineJson.name, descriptionString), spineJson);
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4224df6e20549f0449154531ae080201
|
||||
timeCreated: 1567002861
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Can be stored by SkeletonDataAsset to automatically apply modifications to loaded SkeletonData.</summary>
|
||||
public abstract class SkeletonDataModifierAsset : ScriptableObject {
|
||||
public abstract void Apply (SkeletonData skeletonData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79a44aba1f342f440965874280b4c318
|
||||
timeCreated: 1536412736
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,295 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Loads and stores a Spine atlas and list of materials.</summary>
|
||||
[CreateAssetMenu(fileName = "New Spine Atlas Asset", menuName = "Spine/Spine Atlas Asset")]
|
||||
public class SpineAtlasAsset : AtlasAssetBase {
|
||||
public TextAsset atlasFile;
|
||||
public Material[] materials;
|
||||
public TextureLoader customTextureLoader;
|
||||
protected Atlas atlas;
|
||||
|
||||
public override bool IsLoaded { get { return this.atlas != null; } }
|
||||
|
||||
public override IEnumerable<Material> Materials { get { return materials; } }
|
||||
public override int MaterialCount { get { return materials == null ? 0 : materials.Length; } }
|
||||
public override Material PrimaryMaterial { get { return materials[0]; } }
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>
|
||||
/// Creates a runtime AtlasAsset</summary>
|
||||
/// <param name="newCustomTextureLoader">When not null, a function instantiating
|
||||
/// a custom <c>TextureLoader</c> with the newly created <c>SpineAtlasAsset</c> as argument
|
||||
/// is used instead of instantiating the default <c>MaterialsTextureLoader</c>.
|
||||
/// A valid parameter is e.g. <c>(a) => new CustomTextureLoader(a)</c></param>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText, Material[] materials, bool initialize,
|
||||
Func<SpineAtlasAsset, TextureLoader> newCustomTextureLoader = null) {
|
||||
|
||||
SpineAtlasAsset atlasAsset = ScriptableObject.CreateInstance<SpineAtlasAsset>();
|
||||
atlasAsset.Reset();
|
||||
atlasAsset.atlasFile = atlasText;
|
||||
atlasAsset.materials = materials;
|
||||
if (newCustomTextureLoader != null)
|
||||
atlasAsset.customTextureLoader = newCustomTextureLoader(atlasAsset);
|
||||
|
||||
if (initialize)
|
||||
atlasAsset.GetAtlas();
|
||||
|
||||
return atlasAsset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a runtime AtlasAsset. Only providing the textures is slower
|
||||
/// because it has to search for atlas page matches.
|
||||
/// </summary>
|
||||
/// <param name="textures">An array of all textures referenced in the provided <c>atlasText</c>
|
||||
/// atlas asset JSON file. When procedurally creating textures, each <c>Texture.name</c>
|
||||
/// needs to be set to the atlas page texture filename without the .png extension,
|
||||
/// e.g. 'my_skeleton' if the png filename listed in the atlas asset file is 'my_skeleton.png'.</param>
|
||||
/// <param name="renameMaterial">If true, newly created materials will be renamed to the atlas texture page name.
|
||||
/// If false, the materials keep the name of the <c>materialPropertySource</c> material they are copied from.</param>
|
||||
/// <seealso cref="SpineAtlasAsset.CreateRuntimeInstance(TextAsset, Material[], bool, Func{SpineAtlasAsset, TextureLoader})"/>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText, Texture2D[] textures,
|
||||
Material materialPropertySource, bool initialize,
|
||||
Func<SpineAtlasAsset, TextureLoader> newCustomTextureLoader = null,
|
||||
bool renameMaterial = false) {
|
||||
|
||||
// Get atlas page names.
|
||||
string atlasString = atlasText.text;
|
||||
atlasString = atlasString.Replace("\r", "");
|
||||
string[] atlasLines = atlasString.Split('\n');
|
||||
List<string> pages = new List<string>();
|
||||
for (int i = 0; i < atlasLines.Length - 1; i++) {
|
||||
string line = atlasLines[i].Trim();
|
||||
if (line.EndsWith(".png"))
|
||||
pages.Add(line.Replace(".png", ""));
|
||||
}
|
||||
|
||||
// Populate Materials[] by matching texture names with page names.
|
||||
Material[] materials = new Material[pages.Count];
|
||||
for (int i = 0, n = pages.Count; i < n; i++) {
|
||||
Material mat = null;
|
||||
|
||||
// Search for a match.
|
||||
string pageName = pages[i];
|
||||
for (int j = 0, m = textures.Length; j < m; j++) {
|
||||
if (string.Equals(pageName, textures[j].name, System.StringComparison.OrdinalIgnoreCase)) {
|
||||
// Match found.
|
||||
mat = new Material(materialPropertySource);
|
||||
mat.mainTexture = textures[j];
|
||||
if (renameMaterial)
|
||||
mat.name = pageName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mat != null)
|
||||
materials[i] = mat;
|
||||
else
|
||||
throw new ArgumentException("Could not find matching atlas page in the texture array.");
|
||||
}
|
||||
|
||||
// Create AtlasAsset normally
|
||||
return CreateRuntimeInstance(atlasText, materials, initialize, newCustomTextureLoader);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a runtime AtlasAsset. Only providing the textures is slower because
|
||||
/// it has to search for atlas page matches.
|
||||
/// <param name="textures">An array of all textures referenced in the provided <c>atlasText</c>
|
||||
/// atlas asset JSON file. When procedurally creating textures, each <c>Texture.name</c>
|
||||
/// needs to be set to the atlas page texture filename without the .png extension,
|
||||
/// e.g. 'my_skeleton' if the png filename listed in the atlas asset file is 'my_skeleton.png'.</param>
|
||||
/// <seealso cref="SpineAtlasAsset.CreateRuntimeInstance(TextAsset, Material[], bool, Func{SpineAtlasAsset, TextureLoader})"/>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText,
|
||||
Texture2D[] textures, Shader shader, bool initialize,
|
||||
Func<SpineAtlasAsset, TextureLoader> newCustomTextureLoader = null) {
|
||||
|
||||
if (shader == null)
|
||||
shader = Shader.Find("Spine/Skeleton");
|
||||
|
||||
Material materialProperySource = new Material(shader);
|
||||
return CreateRuntimeInstance(atlasText, textures, materialProperySource, initialize, newCustomTextureLoader);
|
||||
}
|
||||
#endregion
|
||||
|
||||
void Reset () {
|
||||
Clear();
|
||||
}
|
||||
|
||||
public override void Clear () {
|
||||
atlas = null;
|
||||
}
|
||||
|
||||
/// <returns>The atlas or null if it could not be loaded.</returns>
|
||||
public override Atlas GetAtlas (bool onlyMetaData = false) {
|
||||
if (atlasFile == null) {
|
||||
Debug.LogError("Atlas file not set for atlas asset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!onlyMetaData && (materials == null || materials.Length == 0)) {
|
||||
Debug.LogError("Materials not set for atlas asset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (atlas != null) return atlas;
|
||||
|
||||
try {
|
||||
TextureLoader loader;
|
||||
if (!onlyMetaData)
|
||||
loader = customTextureLoader == null ? new MaterialsTextureLoader(this) : customTextureLoader;
|
||||
else
|
||||
loader = new NoOpTextureLoader();
|
||||
atlas = new Atlas(new StringReader(atlasFile.text), "", loader);
|
||||
atlas.FlipV();
|
||||
return atlas;
|
||||
} catch (Exception ex) {
|
||||
Debug.LogError("Error reading atlas file for atlas asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Mesh GenerateMesh (string name, Mesh mesh, out Material material, float scale = 0.01f) {
|
||||
AtlasRegion region = atlas.FindRegion(name);
|
||||
material = null;
|
||||
if (region != null) {
|
||||
if (mesh == null) {
|
||||
mesh = new Mesh();
|
||||
mesh.name = name;
|
||||
}
|
||||
|
||||
Vector3[] verts = new Vector3[4];
|
||||
Vector2[] uvs = new Vector2[4];
|
||||
Color[] colors = { Color.white, Color.white, Color.white, Color.white };
|
||||
int[] triangles = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
float left, right, top, bottom;
|
||||
left = region.width / -2f;
|
||||
right = left * -1f;
|
||||
top = region.height / 2f;
|
||||
bottom = top * -1;
|
||||
|
||||
verts[0] = new Vector3(left, bottom, 0) * scale;
|
||||
verts[1] = new Vector3(left, top, 0) * scale;
|
||||
verts[2] = new Vector3(right, top, 0) * scale;
|
||||
verts[3] = new Vector3(right, bottom, 0) * scale;
|
||||
float u, v, u2, v2;
|
||||
u = region.u;
|
||||
v = region.v;
|
||||
u2 = region.u2;
|
||||
v2 = region.v2;
|
||||
|
||||
if (region.degrees == 90) {
|
||||
uvs[0] = new Vector2(u2, v2);
|
||||
uvs[1] = new Vector2(u, v2);
|
||||
uvs[2] = new Vector2(u, v);
|
||||
uvs[3] = new Vector2(u2, v);
|
||||
} else {
|
||||
uvs[0] = new Vector2(u, v2);
|
||||
uvs[1] = new Vector2(u, v);
|
||||
uvs[2] = new Vector2(u2, v);
|
||||
uvs[3] = new Vector2(u2, v2);
|
||||
}
|
||||
|
||||
mesh.triangles = new int[0];
|
||||
mesh.vertices = verts;
|
||||
mesh.uv = uvs;
|
||||
mesh.colors = colors;
|
||||
mesh.triangles = triangles;
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
material = (Material)region.page.rendererObject;
|
||||
} else {
|
||||
mesh = null;
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
|
||||
public class NoOpTextureLoader : TextureLoader {
|
||||
public void Load (AtlasPage page, string path) { }
|
||||
public void Unload (object texture) { }
|
||||
}
|
||||
|
||||
public class MaterialsTextureLoader : TextureLoader {
|
||||
SpineAtlasAsset atlasAsset;
|
||||
|
||||
public MaterialsTextureLoader (SpineAtlasAsset atlasAsset) {
|
||||
this.atlasAsset = atlasAsset;
|
||||
}
|
||||
|
||||
public void Load (AtlasPage page, string path) {
|
||||
#if UNITY_EDITOR
|
||||
if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing ||
|
||||
BuildUtilities.IsInSkeletonAssetBuildPostProcessing)
|
||||
return;
|
||||
#endif
|
||||
String name = Path.GetFileNameWithoutExtension(path);
|
||||
Material material = null;
|
||||
foreach (Material other in atlasAsset.materials) {
|
||||
if (other.mainTexture == null) {
|
||||
Debug.LogError("Material is missing texture: " + other.name, other);
|
||||
return;
|
||||
}
|
||||
string textureName = other.mainTexture.name;
|
||||
if (textureName == name ||
|
||||
(atlasAsset.OnDemandTextureLoader != null &&
|
||||
textureName == atlasAsset.OnDemandTextureLoader.GetPlaceholderTextureName(name))) {
|
||||
material = other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (material == null) {
|
||||
Debug.LogError("Material with texture name \"" + name + "\" not found for atlas asset: " + atlasAsset.name, atlasAsset);
|
||||
return;
|
||||
}
|
||||
page.rendererObject = material;
|
||||
|
||||
// Very old atlas files expected the texture's actual size to be used at runtime.
|
||||
if (page.width == 0 || page.height == 0) {
|
||||
page.width = material.mainTexture.width;
|
||||
page.height = material.mainTexture.height;
|
||||
}
|
||||
}
|
||||
|
||||
public void Unload (object texture) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6b194f808b1af6499c93410e504af42
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 3fc714a0dc1cf6b4b959e073fff2844e, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,391 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2_OR_NEWER
|
||||
#define EXPOSES_SPRITE_ATLAS_UTILITIES
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Loads and stores a Spine atlas and list of materials.</summary>
|
||||
[CreateAssetMenu(fileName = "New Spine SpriteAtlas Asset", menuName = "Spine/Spine SpriteAtlas Asset")]
|
||||
public class SpineSpriteAtlasAsset : AtlasAssetBase {
|
||||
public SpriteAtlas spriteAtlasFile;
|
||||
public Material[] materials;
|
||||
protected Atlas atlas;
|
||||
public bool updateRegionsInPlayMode;
|
||||
|
||||
[System.Serializable]
|
||||
protected class SavedRegionInfo {
|
||||
public float x, y, width, height;
|
||||
public SpritePackingRotation packingRotation;
|
||||
}
|
||||
[SerializeField] protected SavedRegionInfo[] savedRegions;
|
||||
|
||||
public override bool IsLoaded { get { return this.atlas != null; } }
|
||||
|
||||
public override IEnumerable<Material> Materials { get { return materials; } }
|
||||
public override int MaterialCount { get { return materials == null ? 0 : materials.Length; } }
|
||||
public override Material PrimaryMaterial { get { return materials[0]; } }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static MethodInfo GetPackedSpritesMethod, GetPreviewTexturesMethod;
|
||||
#if !EXPOSES_SPRITE_ATLAS_UTILITIES
|
||||
static MethodInfo PackAtlasesMethod;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>
|
||||
/// Creates a runtime AtlasAsset</summary>
|
||||
public static SpineSpriteAtlasAsset CreateRuntimeInstance (SpriteAtlas spriteAtlasFile, Material[] materials, bool initialize) {
|
||||
SpineSpriteAtlasAsset atlasAsset = ScriptableObject.CreateInstance<SpineSpriteAtlasAsset>();
|
||||
atlasAsset.Reset();
|
||||
atlasAsset.spriteAtlasFile = spriteAtlasFile;
|
||||
atlasAsset.materials = materials;
|
||||
|
||||
if (initialize)
|
||||
atlasAsset.GetAtlas();
|
||||
|
||||
return atlasAsset;
|
||||
}
|
||||
#endregion
|
||||
|
||||
void Reset () {
|
||||
Clear();
|
||||
}
|
||||
|
||||
public override void Clear () {
|
||||
atlas = null;
|
||||
}
|
||||
|
||||
/// <returns>The atlas or null if it could not be loaded.</returns>
|
||||
public override Atlas GetAtlas (bool onlyMetaData = false) {
|
||||
if (spriteAtlasFile == null) {
|
||||
Debug.LogError("SpriteAtlas file not set for SpineSpriteAtlasAsset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!onlyMetaData && (materials == null || materials.Length == 0)) {
|
||||
Debug.LogError("Materials not set for SpineSpriteAtlasAsset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (atlas != null) return atlas;
|
||||
|
||||
try {
|
||||
atlas = LoadAtlas(spriteAtlasFile);
|
||||
return atlas;
|
||||
} catch (Exception ex) {
|
||||
Debug.LogError("Error analyzing SpriteAtlas for SpineSpriteAtlasAsset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssignRegionsFromSavedRegions (Sprite[] sprites, Atlas usedAtlas) {
|
||||
|
||||
if (savedRegions == null || savedRegions.Length != sprites.Length)
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
foreach (AtlasRegion region in usedAtlas) {
|
||||
SavedRegionInfo savedRegion = savedRegions[i];
|
||||
AtlasPage page = region.page;
|
||||
|
||||
region.degrees = savedRegion.packingRotation == SpritePackingRotation.None ? 0 : 90;
|
||||
|
||||
float x = savedRegion.x;
|
||||
float y = savedRegion.y;
|
||||
float width = savedRegion.width;
|
||||
float height = savedRegion.height;
|
||||
|
||||
region.u = x / (float)page.width;
|
||||
region.v = y / (float)page.height;
|
||||
if (region.degrees == 90) {
|
||||
region.u2 = (x + height) / (float)page.width;
|
||||
region.v2 = (y + width) / (float)page.height;
|
||||
} else {
|
||||
region.u2 = (x + width) / (float)page.width;
|
||||
region.v2 = (y + height) / (float)page.height;
|
||||
}
|
||||
region.x = (int)x;
|
||||
region.y = (int)y;
|
||||
region.width = Math.Abs((int)width);
|
||||
region.height = Math.Abs((int)height);
|
||||
|
||||
// flip upside down
|
||||
float temp = region.v;
|
||||
region.v = region.v2;
|
||||
region.v2 = temp;
|
||||
|
||||
region.originalWidth = (int)width;
|
||||
region.originalHeight = (int)height;
|
||||
|
||||
// note: currently sprite pivot offsets are ignored.
|
||||
// Sprite sprite = sprites[i];
|
||||
region.offsetX = 0;//sprite.pivot.x;
|
||||
region.offsetY = 0;//sprite.pivot.y;
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
private Atlas LoadAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
|
||||
|
||||
List<AtlasPage> pages = new List<AtlasPage>();
|
||||
List<AtlasRegion> regions = new List<AtlasRegion>();
|
||||
|
||||
Sprite[] sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
|
||||
spriteAtlas.GetSprites(sprites);
|
||||
if (sprites.Length == 0)
|
||||
return new Atlas(pages, regions);
|
||||
|
||||
Texture2D texture = null;
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
texture = AccessPackedTextureEditor(spriteAtlas);
|
||||
else
|
||||
#endif
|
||||
texture = AccessPackedTexture(sprites);
|
||||
|
||||
Material material = materials[0];
|
||||
#if !UNITY_EDITOR
|
||||
material.mainTexture = texture;
|
||||
#endif
|
||||
|
||||
Spine.AtlasPage page = new AtlasPage();
|
||||
page.name = spriteAtlas.name;
|
||||
page.width = texture.width;
|
||||
page.height = texture.height;
|
||||
page.format = Spine.Format.RGBA8888;
|
||||
|
||||
page.minFilter = TextureFilter.Linear;
|
||||
page.magFilter = TextureFilter.Linear;
|
||||
page.uWrap = TextureWrap.ClampToEdge;
|
||||
page.vWrap = TextureWrap.ClampToEdge;
|
||||
page.rendererObject = material;
|
||||
pages.Add(page);
|
||||
|
||||
sprites = AccessPackedSprites(spriteAtlas);
|
||||
|
||||
int i = 0;
|
||||
for (; i < sprites.Length; ++i) {
|
||||
Sprite sprite = sprites[i];
|
||||
AtlasRegion region = new AtlasRegion();
|
||||
region.name = sprite.name.Replace("(Clone)", "");
|
||||
region.page = page;
|
||||
region.degrees = sprite.packingRotation == SpritePackingRotation.None ? 0 : 90;
|
||||
|
||||
region.u2 = 1;
|
||||
region.v2 = 1;
|
||||
region.width = page.width;
|
||||
region.height = page.height;
|
||||
region.originalWidth = page.width;
|
||||
region.originalHeight = page.height;
|
||||
|
||||
region.index = i;
|
||||
regions.Add(region);
|
||||
}
|
||||
|
||||
Atlas atlas = new Atlas(pages, regions);
|
||||
AssignRegionsFromSavedRegions(sprites, atlas);
|
||||
|
||||
return atlas;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public static void UpdateByStartingEditorPlayMode () {
|
||||
EditorApplication.isPlaying = true;
|
||||
}
|
||||
|
||||
public static bool AnySpriteAtlasNeedsRegionsLoaded () {
|
||||
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
|
||||
foreach (string guid in guids) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
SpineSpriteAtlasAsset atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<SpineSpriteAtlasAsset>(path);
|
||||
if (atlasAsset) {
|
||||
if (atlasAsset.RegionsNeedLoading)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void UpdateWhenEditorPlayModeStarted () {
|
||||
if (!EditorApplication.isPlaying)
|
||||
return;
|
||||
|
||||
EditorApplication.update -= UpdateWhenEditorPlayModeStarted;
|
||||
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
|
||||
if (guids.Length == 0)
|
||||
return;
|
||||
|
||||
Debug.Log("Updating SpineSpriteAtlasAssets");
|
||||
foreach (string guid in guids) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
SpineSpriteAtlasAsset atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<SpineSpriteAtlasAsset>(path);
|
||||
if (atlasAsset) {
|
||||
atlasAsset.atlas = atlasAsset.LoadAtlas(atlasAsset.spriteAtlasFile);
|
||||
atlasAsset.LoadRegionsInEditorPlayMode();
|
||||
Debug.Log(string.Format("Updated regions of '{0}'", atlasAsset.name), atlasAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorApplication.isPlaying = false;
|
||||
}
|
||||
|
||||
public bool RegionsNeedLoading {
|
||||
get { return savedRegions == null || savedRegions.Length == 0 || updateRegionsInPlayMode; }
|
||||
}
|
||||
|
||||
public void LoadRegionsInEditorPlayMode () {
|
||||
|
||||
Sprite[] sprites = null;
|
||||
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
||||
MethodInfo method = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (method != null) {
|
||||
object retval = method.Invoke(null, new object[] { spriteAtlasFile });
|
||||
Sprite[] spritesArray = retval as Sprite[];
|
||||
if (spritesArray != null && spritesArray.Length > 0) {
|
||||
sprites = spritesArray;
|
||||
}
|
||||
}
|
||||
if (sprites == null) {
|
||||
sprites = new UnityEngine.Sprite[spriteAtlasFile.spriteCount];
|
||||
spriteAtlasFile.GetSprites(sprites);
|
||||
}
|
||||
if (sprites.Length == 0) {
|
||||
Debug.LogWarning(string.Format("SpriteAtlas '{0}' contains no sprites. Please make sure all assigned images are set to import type 'Sprite'.", spriteAtlasFile.name), spriteAtlasFile);
|
||||
return;
|
||||
} else if (sprites[0].packingMode == SpritePackingMode.Tight) {
|
||||
Debug.LogError(string.Format("SpriteAtlas '{0}': Tight packing is not supported. Please disable 'Tight Packing' in the SpriteAtlas Inspector.", spriteAtlasFile.name), spriteAtlasFile);
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedRegions == null || savedRegions.Length != sprites.Length)
|
||||
savedRegions = new SavedRegionInfo[sprites.Length];
|
||||
|
||||
int i = 0;
|
||||
foreach (AtlasRegion region in atlas) {
|
||||
Sprite sprite = sprites[i];
|
||||
Rect rect = sprite.textureRect;
|
||||
float x = rect.min.x;
|
||||
float y = rect.min.y;
|
||||
float width = rect.width;
|
||||
float height = rect.height;
|
||||
|
||||
SavedRegionInfo savedRegion = new SavedRegionInfo();
|
||||
savedRegion.x = x;
|
||||
savedRegion.y = y;
|
||||
savedRegion.width = width;
|
||||
savedRegion.height = height;
|
||||
savedRegion.packingRotation = sprite.packingRotation;
|
||||
savedRegions[i] = savedRegion;
|
||||
|
||||
++i;
|
||||
}
|
||||
updateRegionsInPlayMode = false;
|
||||
AssignRegionsFromSavedRegions(sprites, atlas);
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
public static Texture2D AccessPackedTextureEditor (SpriteAtlas spriteAtlas) {
|
||||
#if EXPOSES_SPRITE_ATLAS_UTILITIES
|
||||
UnityEditor.U2D.SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget);
|
||||
#else
|
||||
/*if (PackAtlasesMethod == null) {
|
||||
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasUtility,UnityEditor");
|
||||
PackAtlasesMethod = T.GetMethod("PackAtlases", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
if (PackAtlasesMethod != null) {
|
||||
PackAtlasesMethod.Invoke(null, new object[] { new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget });
|
||||
}*/
|
||||
#endif
|
||||
if (GetPreviewTexturesMethod == null) {
|
||||
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
||||
GetPreviewTexturesMethod = T.GetMethod("GetPreviewTextures", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
if (GetPreviewTexturesMethod != null) {
|
||||
object retval = GetPreviewTexturesMethod.Invoke(null, new object[] { spriteAtlas });
|
||||
Texture2D[] textures = retval as Texture2D[];
|
||||
if (textures.Length > 0)
|
||||
return textures[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
public static Texture2D AccessPackedTexture (Sprite[] sprites) {
|
||||
return sprites[0].texture;
|
||||
}
|
||||
|
||||
|
||||
public static Sprite[] AccessPackedSprites (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
|
||||
Sprite[] sprites = null;
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
|
||||
if (GetPackedSpritesMethod == null) {
|
||||
System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
||||
GetPackedSpritesMethod = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
}
|
||||
if (GetPackedSpritesMethod != null) {
|
||||
object retval = GetPackedSpritesMethod.Invoke(null, new object[] { spriteAtlas });
|
||||
Sprite[] spritesArray = retval as Sprite[];
|
||||
if (spritesArray != null && spritesArray.Length > 0) {
|
||||
sprites = spritesArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (sprites == null) {
|
||||
sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
|
||||
spriteAtlas.GetSprites(sprites);
|
||||
if (sprites.Length == 0)
|
||||
return null;
|
||||
}
|
||||
return sprites;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce59897dd7e6cbc4690a05ebaf975dff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 954179821df28404683b8289f05d0c6f
|
||||
folderAsset: yes
|
||||
timeCreated: 1518344191
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c4e5bff8901a8a4fa1d2ab7f5d2bda6
|
||||
folderAsset: yes
|
||||
timeCreated: 1686835686
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,535 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated September 24, 2021. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2021, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
#define CONFIGURABLE_ENTER_PLAY_MODE
|
||||
#endif
|
||||
|
||||
#if !SPINE_DISABLE_THREADING
|
||||
#define USE_THREADED_ANIMATION_UPDATE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[System.Serializable]
|
||||
public abstract class SkeletonAnimationBase : MonoBehaviour,
|
||||
ISkeletonAnimation, ISkeletonComponent, IHasSkeletonRenderer,
|
||||
ISkeletonRendererEvents, IHasModifyableSkeletonDataAsset, IUpgradable {
|
||||
|
||||
[SerializeField] protected UpdateTiming updateTiming = UpdateTiming.InUpdate;
|
||||
protected int frameOfLastUpdate = -1;
|
||||
protected ISkeletonRenderer skeletonRenderer;
|
||||
protected bool skipUpdate = false;
|
||||
|
||||
public UpdateTiming UpdateTiming {
|
||||
get { return updateTiming; }
|
||||
set {
|
||||
if (updateTiming == value) return;
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (Application.isPlaying && UsesThreadedAnimation && isUpdatedExternally) {
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system) {
|
||||
// unregister from old update timing mode, register for new one.
|
||||
system.UnregisterFromUpdate(updateTiming, this);
|
||||
system.RegisterForUpdate(value, this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
updateTiming = value;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected bool requiresEditorUpdate = false;
|
||||
#endif
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
#region Threaded update system
|
||||
protected static int mainThreadID = -1;
|
||||
|
||||
protected static float externalDeltaTime = 0f;
|
||||
protected static float unscaledDeltaTime = 0f;
|
||||
|
||||
[SerializeField] protected SettingsTriState threadedAnimation = SettingsTriState.UseGlobalSetting;
|
||||
protected bool isUpdatedExternally = false;
|
||||
|
||||
public static float ExternalDeltaTime {
|
||||
get { return externalDeltaTime; }
|
||||
set { externalDeltaTime = value; }
|
||||
}
|
||||
public static float ExternalUnscaledDeltaTime {
|
||||
get { return unscaledDeltaTime; }
|
||||
set { unscaledDeltaTime = value; }
|
||||
}
|
||||
public virtual float UsedExternalDeltaTime { get { return ExternalDeltaTime; } }
|
||||
|
||||
public bool IsUpdatedExternally {
|
||||
get { return isUpdatedExternally; }
|
||||
set { isUpdatedExternally = value; }
|
||||
}
|
||||
|
||||
protected bool UsesThreadedAnimation {
|
||||
get { return threadedAnimation == SettingsTriState.Enable || RuntimeSettings.UseThreadedAnimation; }
|
||||
}
|
||||
|
||||
public SettingsTriState ThreadedAnimation {
|
||||
get { return threadedAnimation; }
|
||||
set {
|
||||
if (threadedAnimation == value) return;
|
||||
threadedAnimation = value;
|
||||
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system) {
|
||||
if (threadedAnimation == SettingsTriState.Enable || RuntimeSettings.UseThreadedAnimation)
|
||||
system.RegisterForUpdate(updateTiming, this);
|
||||
else
|
||||
system.UnregisterFromUpdate(updateTiming, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static bool applicationIsPlaying = false;
|
||||
// ApplicationIsPlaying for threaded access. Unfortunately Application.isPlaying throws
|
||||
// when called from worker thread.
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return applicationIsPlaying; }
|
||||
set { applicationIsPlaying = value; }
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
protected bool UseThreading {
|
||||
get { return false; }
|
||||
set { }
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return Application.isPlaying; }
|
||||
set { }
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#region Threading Asserts
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
protected void InitializeMainThreadID () {
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (mainThreadID == -1)
|
||||
mainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsMainThread () {
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId != mainThreadID)
|
||||
Debug.LogError("AssertIsMainThread failed: worker thread calling main thread code. Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsWorkerThread () {
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId == mainThreadID)
|
||||
Debug.LogError("AssertIsWorkerThread failed: main thread calling worker thread code! Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Interface Implementation
|
||||
public UnityEngine.MonoBehaviour Component { get { return this; } }
|
||||
|
||||
public ISkeletonRenderer Renderer {
|
||||
get {
|
||||
if (skeletonRenderer == null) {
|
||||
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
if (skeletonRenderer == null) {
|
||||
UpgradeTo43();
|
||||
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return skeletonRenderer;
|
||||
}
|
||||
}
|
||||
public Skeleton Skeleton {
|
||||
get { return Renderer.Skeleton; }
|
||||
set { Renderer.Skeleton = value; }
|
||||
}
|
||||
public Skeleton skeleton {
|
||||
get { return Renderer.Skeleton; }
|
||||
set { Renderer.Skeleton = value; }
|
||||
}
|
||||
public SkeletonDataAsset SkeletonDataAsset {
|
||||
get { return Renderer.SkeletonDataAsset; }
|
||||
set { Renderer.SkeletonDataAsset = value; }
|
||||
}
|
||||
public SkeletonDataAsset skeletonDataAsset {
|
||||
get { return Renderer.SkeletonDataAsset; }
|
||||
set { Renderer.SkeletonDataAsset = value; }
|
||||
}
|
||||
|
||||
protected event SkeletonAnimationDelegate _BeforeUpdate, _BeforeApply, _OnAnimationRebuild;
|
||||
|
||||
/// <summary>OnAnimationRebuild is raised after the SkeletonAnimation component is successfully initialized.</summary>
|
||||
public event SkeletonAnimationDelegate OnAnimationRebuild { add { _OnAnimationRebuild += value; } remove { _OnAnimationRebuild -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before the animation state is updated.
|
||||
/// Use this callback when you want to change the skeleton state before animation state is updated.
|
||||
/// </summary>
|
||||
public event SkeletonAnimationDelegate BeforeUpdate { add { _BeforeUpdate += value; } remove { _BeforeUpdate -= value; } }
|
||||
|
||||
/// <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 SkeletonAnimationDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
|
||||
|
||||
/// <summary>A compatibility wrapper for <see cref="SkeletonRenderer.UpdateLocal"/></summary>
|
||||
public event SkeletonRendererDelegate UpdateLocal { add { Renderer.UpdateLocal += value; } remove { Renderer.UpdateLocal -= value; } }
|
||||
/// <summary>A compatibility wrapper for <see cref="Renderer.UpdateWorld"/></summary>
|
||||
public event SkeletonRendererDelegate UpdateWorld { add { Renderer.UpdateWorld += value; } remove { Renderer.UpdateWorld -= value; } }
|
||||
/// <summary>A compatibility wrapper for <see cref="Renderer.UpdateComplete"/></summary>
|
||||
public event SkeletonRendererDelegate UpdateComplete { add { Renderer.UpdateComplete += value; } remove { Renderer.UpdateComplete -= value; } }
|
||||
/// <summary>A compatibility wrapper for <see cref="Renderer.OnRebuild"/></summary>
|
||||
public event SkeletonRendererDelegate OnRebuild { add { Renderer.OnRebuild += value; } remove { Renderer.OnRebuild -= value; } }
|
||||
/// <summary>A compatibility wrapper for <see cref="Renderer.OnMeshAndMaterialsUpdated"/></summary>
|
||||
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated { add { Renderer.OnMeshAndMaterialsUpdated += value; } remove { Renderer.OnMeshAndMaterialsUpdated -= value; } }
|
||||
|
||||
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
|
||||
public UpdateMode UpdateMode { get { return Renderer.UpdateMode; } set { Renderer.UpdateMode = value; } }
|
||||
|
||||
public abstract bool IsValid { get; }
|
||||
#endregion
|
||||
|
||||
public virtual void Awake () {
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
UpgradeTo43();
|
||||
#endif
|
||||
InitializeMainThreadID();
|
||||
#if UNITY_EDITOR
|
||||
SkeletonAnimationBase.ApplicationIsPlaying = Application.isPlaying;
|
||||
#endif
|
||||
EnsureRendererEventsSubscribed();
|
||||
}
|
||||
|
||||
public void EnsureRendererEventsSubscribed () {
|
||||
Renderer.OnRebuild -= OnRendererRebuild;
|
||||
Renderer.OnRebuild += OnRendererRebuild;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
// compatibility layer between 4.1 and 4.2, automatically transfer serialized attributes.
|
||||
public abstract void UpgradeTo43 ();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the associated renderer component and subsequently this animation component.
|
||||
/// 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 virtual void Initialize (bool overwrite, bool quiet = false, bool calledFromRendererCallback = false) {
|
||||
#if UNITY_EDITOR
|
||||
if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing)
|
||||
return;
|
||||
#endif
|
||||
if (!calledFromRendererCallback) {
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
|
||||
if (!skeletonRenderer.IsValid || !this.IsValid || overwrite)
|
||||
skeletonRenderer.Initialize(overwrite, quiet);
|
||||
}
|
||||
if (this.IsValid && !overwrite)
|
||||
return;
|
||||
InitializeAnimationComponent();
|
||||
|
||||
if (_OnAnimationRebuild != null)
|
||||
_OnAnimationRebuild(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually initializes just this animation component without initializing the associated renderer component.
|
||||
/// The renderer component has to be initialized before calling this method, which happens automatically via
|
||||
/// renderer component Awake, or when needed earlier by calling <see cref="Initialize(bool, bool, bool)"/> on
|
||||
/// this animation component which initializes both components in the correct order.
|
||||
/// </summary>
|
||||
public virtual void InitializeAnimationComponent () {
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = this.GetComponent<ISkeletonRenderer>();
|
||||
#if UNITY_EDITOR
|
||||
if (requiresEditorUpdate && skeletonRenderer != null && skeletonRenderer.Skeleton != null)
|
||||
skeletonRenderer.Skeleton.SetupPose();
|
||||
requiresEditorUpdate = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh, resets the skeleton's pose, and clears all previously active animations.</summary>
|
||||
public void ClearState () {
|
||||
skeletonRenderer.ClearSkeletonState();
|
||||
ClearAnimationState();
|
||||
}
|
||||
|
||||
public virtual void ClearAnimationState () { }
|
||||
|
||||
public void OnEnable () {
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = this.GetComponent<SkeletonRenderer>();
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (Application.isPlaying && UsesThreadedAnimation && !isUpdatedExternally) {
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system)
|
||||
system.RegisterForUpdate(updateTiming, this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
public void OnDisable () {
|
||||
if (Application.isPlaying && UsesThreadedAnimation) {
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system)
|
||||
system.UnregisterFromUpdate(updateTiming, this);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected void OnRendererRebuild (ISkeletonRenderer skeletonRenderer) {
|
||||
Initialize(overwrite: true, quiet: false, calledFromRendererCallback: true); //InitializeAnimationComponent();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected void OnValidate () {
|
||||
if (!Application.isPlaying) {
|
||||
Renderer.OnRebuild -= OnRendererRebuild;
|
||||
Renderer.OnRebuild += OnRendererRebuild;
|
||||
requiresEditorUpdate = true;
|
||||
} else if (Time.frameCount != 0) {
|
||||
// OnValidate is called once when starting play mode in the Editor, don't trigger re-init then.
|
||||
requiresEditorUpdate = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected virtual void Update () {
|
||||
#if UNITY_EDITOR
|
||||
if (requiresEditorUpdate)
|
||||
InitializeAnimationComponent();
|
||||
if (!ApplicationIsPlaying) {
|
||||
if (!IsValid)
|
||||
Initialize(false);
|
||||
Update(0f);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (isUpdatedExternally) return;
|
||||
#endif
|
||||
if (updateTiming != UpdateTiming.InUpdate) return;
|
||||
UpdateOncePerFrame(DeltaTime);
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate () {
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (isUpdatedExternally) return;
|
||||
#endif
|
||||
if (updateTiming != UpdateTiming.InFixedUpdate) return;
|
||||
UpdateOncePerFrame(DeltaTime);
|
||||
}
|
||||
|
||||
protected virtual void LateUpdate () {
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (isUpdatedExternally) return;
|
||||
#endif
|
||||
if (updateTiming != UpdateTiming.InLateUpdate) return;
|
||||
UpdateOncePerFrame(DeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>Calls <see cref="Update()"/> if it has not yet been called this frame.</summary>
|
||||
public virtual void UpdateOncePerFrame (float deltaTime) {
|
||||
if (frameOfLastUpdate != Time.frameCount) {
|
||||
MainThreadBeforeUpdateInternal();
|
||||
UpdateInternal(deltaTime, Time.frameCount, calledFromOnlyMainThread: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main thread update part preparing properties only accessible from main thread.
|
||||
/// To be followed by a potentially threaded call to <see cref="UpdateInternal"/>.
|
||||
/// </summary>
|
||||
public virtual void MainThreadBeforeUpdateInternal () {
|
||||
if (skeletonRenderer == null || !skeletonRenderer.IsValid || skeletonRenderer.Freeze || !this.IsValid
|
||||
|| skeletonRenderer.UpdateMode < UpdateMode.OnlyAnimationStatus) {
|
||||
skipUpdate = true;
|
||||
return;
|
||||
}
|
||||
skipUpdate = false;
|
||||
skeletonRenderer.GatherTransformMovementForPhysics();
|
||||
if (_BeforeUpdate != null)
|
||||
_BeforeUpdate(this);
|
||||
}
|
||||
|
||||
public virtual void MainThreadAfterUpdateInternal () { }
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
public virtual void UpdateExternal (int currentFrameCount, bool calledFromOnlyMainThread = true) {
|
||||
UpdateInternal(UsedExternalDeltaTime, currentFrameCount, calledFromOnlyMainThread);
|
||||
}
|
||||
#endif
|
||||
public virtual void UpdateInternal (float deltaTime, int currentFrameCount, bool calledFromOnlyMainThread = true) {
|
||||
if (skipUpdate)
|
||||
return;
|
||||
|
||||
frameOfLastUpdate = currentFrameCount;
|
||||
UpdateAnimationStatus(deltaTime);
|
||||
skeletonRenderer.ApplyTransformMovementToPhysics();
|
||||
|
||||
if (skeletonRenderer.UpdateMode == UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
|
||||
ApplyAnimation(calledFromOnlyMainThread);
|
||||
}
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
/// <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 virtual CoroutineIterator UpdateInternalSplit (CoroutineIterator coroutineIterator,
|
||||
int currentFrameCount) {
|
||||
|
||||
if (coroutineIterator.IsDone)
|
||||
return CoroutineIterator.Done;
|
||||
|
||||
const int StateBits = 1;
|
||||
const uint StateMask = (1 << StateBits) - 1;
|
||||
switch (coroutineIterator.State(StateMask)) {
|
||||
case 0:
|
||||
if (skipUpdate)
|
||||
return CoroutineIterator.Done;
|
||||
frameOfLastUpdate = currentFrameCount;
|
||||
UpdateAnimationStatus(UsedExternalDeltaTime);
|
||||
skeletonRenderer.ApplyTransformMovementToPhysics();
|
||||
|
||||
if (skeletonRenderer.UpdateMode == UpdateMode.OnlyAnimationStatus)
|
||||
return CoroutineIterator.Done;
|
||||
goto case 1;
|
||||
case 1:
|
||||
return ApplyAnimationSplit(coroutineIterator.ToNestedCall(StateBits))
|
||||
.FromNestedCall(1, StateBits);
|
||||
default:
|
||||
Debug.LogError(string.Format(
|
||||
"Internal coroutine logic error: SkeletonAnimationBase.UpdateInternalSplit state was {0}.",
|
||||
coroutineIterator.State(StateMask)), this);
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <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 virtual void Update (float deltaTime) {
|
||||
MainThreadBeforeUpdateInternal();
|
||||
UpdateInternal(deltaTime, Time.frameCount, calledFromOnlyMainThread: true);
|
||||
}
|
||||
|
||||
public virtual void ApplyAnimation (bool calledFromMainThread = true) {
|
||||
if (_BeforeApply != null)
|
||||
_BeforeApply(this);
|
||||
|
||||
ApplyStateToSkeleton(calledFromMainThread);
|
||||
skeletonRenderer.AfterAnimationApplied(calledFromMainThread);
|
||||
}
|
||||
|
||||
public virtual CoroutineIterator ApplyAnimationSplit (CoroutineIterator coroutineIterator) {
|
||||
if (coroutineIterator.IsDone)
|
||||
return CoroutineIterator.Done;
|
||||
|
||||
const int StateBits = 2;
|
||||
const uint StateMask = (1 << StateBits) - 1;
|
||||
switch (coroutineIterator.State(StateMask)) {
|
||||
case 0:
|
||||
if (_BeforeApply != null) {
|
||||
AssertIsWorkerThread();
|
||||
return coroutineIterator.YieldReturnAtState(1, StateMask);
|
||||
} else {
|
||||
goto case 2;
|
||||
}
|
||||
case 1:
|
||||
AssertIsMainThread();
|
||||
_BeforeApply(this);
|
||||
return coroutineIterator.YieldReturnAtState(2, StateMask);
|
||||
case 2:
|
||||
AssertIsWorkerThread();
|
||||
ApplyStateToSkeleton(calledFromMainThread: false);
|
||||
goto case 3;
|
||||
case 3:
|
||||
return skeletonRenderer.AfterAnimationAppliedSplit(coroutineIterator.ToNestedCall(StateBits))
|
||||
.FromNestedCall(3, StateBits);
|
||||
default:
|
||||
Debug.LogError(string.Format(
|
||||
"Internal coroutine logic error: SkeletonAnimationBase.ApplyAnimationSplit state was {0}.",
|
||||
coroutineIterator.State(StateMask)), this);
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBecameVisibleFromMode (UpdateMode previousUpdateMode) {
|
||||
// OnBecameVisible is called after Update and LateUpdate(),
|
||||
// so update if previousUpdateMode didn't already update this frame.
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate &&
|
||||
previousUpdateMode != UpdateMode.EverythingExceptMesh)
|
||||
Update(0);
|
||||
}
|
||||
|
||||
protected virtual float DeltaTime { get { return Time.deltaTime; } }
|
||||
|
||||
protected abstract void UpdateAnimationStatus (float deltaTime);
|
||||
protected abstract void ApplyStateToSkeleton (bool calledFromMainThread = true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70671e8c557db624b9ba695f42aa543e
|
||||
timeCreated: 1622830533
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2022_2_OR_NEWER
|
||||
#define USE_FIND_OBJECTS_BY_TYPE
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Experimental Editor Skeleton Player component enabling Editor playback of the
|
||||
/// selected animation outside of Play mode for SkeletonAnimation and SkeletonGraphic.
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("Spine/EditorSkeletonPlayer")]
|
||||
[RequireComponent(typeof(SkeletonAnimation))]
|
||||
public class EditorSkeletonPlayer : MonoBehaviour {
|
||||
public bool playWhenSelected = true;
|
||||
public bool playWhenDeselected = true;
|
||||
public float fixedTrackTime = 0.0f;
|
||||
private SkeletonAnimation skeletonAnimation;
|
||||
private TrackEntry trackEntry;
|
||||
private string oldAnimationName;
|
||||
private bool oldLoop;
|
||||
private double oldTime;
|
||||
|
||||
[DidReloadScripts]
|
||||
private static void OnReloaded () {
|
||||
// Force start when scripts are reloaded
|
||||
#if USE_FIND_OBJECTS_BY_TYPE
|
||||
EditorSkeletonPlayer[] editorSpineAnimations = FindObjectsByType<EditorSkeletonPlayer>(FindObjectsSortMode.None);
|
||||
#else
|
||||
EditorSkeletonPlayer[] editorSpineAnimations = FindObjectsOfType<EditorSkeletonPlayer>();
|
||||
#endif
|
||||
|
||||
foreach (EditorSkeletonPlayer editorSpineAnimation in editorSpineAnimations)
|
||||
editorSpineAnimation.Start();
|
||||
}
|
||||
|
||||
private void Reset () {
|
||||
// Note: when a skeleton has a varying number of active materials,
|
||||
// we're moving this component first in the hierarchy to still be
|
||||
// able to disable this component.
|
||||
for (int i = 0; i < 10; ++i)
|
||||
UnityEditorInternal.ComponentUtility.MoveComponentUp(this);
|
||||
}
|
||||
|
||||
private void Start () {
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
if (skeletonAnimation == null) {
|
||||
skeletonAnimation = this.GetComponent<SkeletonAnimation>();
|
||||
}
|
||||
|
||||
oldTime = EditorApplication.timeSinceStartup;
|
||||
EditorApplication.update += EditorUpdate;
|
||||
}
|
||||
|
||||
private void OnDestroy () {
|
||||
EditorApplication.update -= EditorUpdate;
|
||||
}
|
||||
|
||||
private void Update () {
|
||||
if (enabled == false || Application.isPlaying) return;
|
||||
if (skeletonAnimation == null) return;
|
||||
AnimationState animationState = skeletonAnimation.AnimationState;
|
||||
if (animationState == null || animationState.Tracks.Count == 0) return;
|
||||
|
||||
TrackEntry currentEntry = animationState.Tracks.Items[0];
|
||||
if (currentEntry != null && fixedTrackTime != 0) {
|
||||
currentEntry.TrackTime = fixedTrackTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void EditorUpdate () {
|
||||
if (enabled == false || Application.isPlaying) return;
|
||||
if (skeletonAnimation == null) return;
|
||||
AnimationState animationState = skeletonAnimation.AnimationState;
|
||||
if (animationState == null) return;
|
||||
bool isSelected = Selection.Contains(this.gameObject);
|
||||
if (!this.playWhenSelected && isSelected) return;
|
||||
if (!this.playWhenDeselected && !isSelected) return;
|
||||
if (fixedTrackTime != 0) return;
|
||||
|
||||
// Update animation
|
||||
string animationName = skeletonAnimation.AnimationName;
|
||||
bool loop = skeletonAnimation.loop;
|
||||
if (oldAnimationName != animationName || oldLoop != loop) {
|
||||
SkeletonData skeletonData = skeletonAnimation.Skeleton.Data;
|
||||
Spine.Animation animation = (skeletonData == null || animationName == null) ?
|
||||
null : skeletonData.FindAnimation(animationName);
|
||||
if (animation != null)
|
||||
trackEntry = animationState.SetAnimation(0, animationName, loop);
|
||||
else
|
||||
trackEntry = animationState.SetEmptyAnimation(0, 0);
|
||||
oldAnimationName = animationName;
|
||||
oldLoop = loop;
|
||||
}
|
||||
|
||||
// Update speed
|
||||
if (trackEntry != null)
|
||||
trackEntry.TimeScale = skeletonAnimation.timeScale;
|
||||
|
||||
float deltaTime = (float)(EditorApplication.timeSinceStartup - oldTime);
|
||||
skeletonAnimation.Update(deltaTime);
|
||||
skeletonAnimation.Renderer.UpdateMesh();
|
||||
oldTime = EditorApplication.timeSinceStartup;
|
||||
|
||||
// Force repaint to update animation smoothly
|
||||
#if UNITY_2017_2_OR_NEWER
|
||||
EditorApplication.QueuePlayerLoopUpdate();
|
||||
#else
|
||||
SceneView.RepaintAll();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b473abb659158442b906826a22f18bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0fd98019ca00c74f929c6d1f7ee3544
|
||||
folderAsset: yes
|
||||
timeCreated: 1563290418
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,279 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
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("https://esotericsoftware.com/spine-unity-utility-components#BoneFollower")]
|
||||
public class BoneFollower : MonoBehaviour, IUpgradable {
|
||||
|
||||
#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 followAttachmentZSpacing = false;
|
||||
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.")]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("followScale")]
|
||||
public bool followLocalScale = false;
|
||||
[Tooltip("Includes the parent bone's lossy world scale. BoneFollower cannot inherit rotated/skewed scale because of UnityEngine.Transform property limitations.")]
|
||||
public bool followParentWorldScale = 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 UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
#endif
|
||||
if (initializeOnAwake) Initialize();
|
||||
}
|
||||
|
||||
public void HandleRebuildRenderer (ISkeletonRenderer skeletonRenderer) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public virtual 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 virtual void LateUpdate () {
|
||||
if (!valid) {
|
||||
Initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
#endif
|
||||
Skeleton skeleton = skeletonRenderer.skeleton;
|
||||
if (bone == null) {
|
||||
if (string.IsNullOrEmpty(boneName)) return;
|
||||
bone = skeleton.FindBone(boneName);
|
||||
if (!SetBone(boneName)) return;
|
||||
}
|
||||
|
||||
Transform thisTransform = this.transform;
|
||||
float additionalFlipScale = 1;
|
||||
|
||||
float scaleSignX = 1;
|
||||
float scaleSignY = 1;
|
||||
Bone parentBone = bone.Parent;
|
||||
if (followParentWorldScale || followLocalScale || followSkeletonFlip) {
|
||||
Vector3 localScale = new Vector3(1f, 1f, 1f);
|
||||
if (followParentWorldScale && parentBone != null) {
|
||||
float cumulativeScaleX = 1.0f;
|
||||
float cumulativeScaleY = 1.0f;
|
||||
Bone p = parentBone;
|
||||
while (p != null) {
|
||||
cumulativeScaleX *= p.AppliedPose.ScaleX;
|
||||
cumulativeScaleY *= p.AppliedPose.ScaleY;
|
||||
p = p.Parent;
|
||||
};
|
||||
scaleSignX = Mathf.Sign(cumulativeScaleX);
|
||||
scaleSignY = Mathf.Sign(cumulativeScaleY);
|
||||
localScale = new Vector3(parentBone.AppliedPose.WorldScaleX * scaleSignX, parentBone.AppliedPose.WorldScaleY * scaleSignY, 1f);
|
||||
}
|
||||
if (followLocalScale)
|
||||
localScale.Scale(new Vector3(bone.AppliedPose.ScaleX, bone.AppliedPose.ScaleY, 1f));
|
||||
if (followSkeletonFlip)
|
||||
localScale.y *= Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY) * additionalFlipScale;
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
|
||||
if (skeletonTransformIsParent) {
|
||||
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
|
||||
thisTransform.localPosition = new Vector3(
|
||||
followXYPosition ? bone.AppliedPose.WorldX : thisTransform.localPosition.x,
|
||||
followXYPosition ? bone.AppliedPose.WorldY : thisTransform.localPosition.y,
|
||||
followZPosition ? (followAttachmentZSpacing ? GetAttachmentZPosition() : 0f) : thisTransform.localPosition.z);
|
||||
if (followBoneRotation) {
|
||||
float halfRotation = Mathf.Atan2(bone.AppliedPose.C, bone.AppliedPose.A) * 0.5f;
|
||||
if (followLocalScale && bone.AppliedPose.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;
|
||||
if (followParentWorldScale && scaleSignX < 0)
|
||||
halfRotation += Mathf.PI * 0.5f;
|
||||
|
||||
Quaternion 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
|
||||
if (!skeletonTransform) return;
|
||||
|
||||
float z0Position = (followZPosition && followAttachmentZSpacing) ? GetAttachmentZPosition() : 0f;
|
||||
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.AppliedPose.WorldX, bone.AppliedPose.WorldY, z0Position));
|
||||
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.AppliedPose.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;
|
||||
}
|
||||
if (followParentWorldScale && scaleSignX < 0)
|
||||
boneWorldRotation += 180f;
|
||||
|
||||
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
|
||||
if (followLocalScale && bone.AppliedPose.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);
|
||||
}
|
||||
}
|
||||
|
||||
float GetAttachmentZPosition () {
|
||||
var drawOrderPose = skeletonRenderer.Skeleton.DrawOrder.Pose;
|
||||
int boneIndex = drawOrderPose.FindIndex(slot => slot.Bone == bone);
|
||||
if (boneIndex < 0) return 0f;
|
||||
return skeletonRenderer.zSpacing * boneIndex;
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at BoneFollower, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1fd8daaed7b64148a34acb96ba14ce1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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("https://esotericsoftware.com/spine-unity-utility-components#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.")]
|
||||
public bool followLocalScale = false;
|
||||
[Tooltip("Includes the parent bone's lossy world scale. BoneFollower cannot inherit rotated/skewed scale because of UnityEngine.Transform property limitations.")]
|
||||
public bool followParentWorldScale = false;
|
||||
public bool followXYPosition = true;
|
||||
public bool followZPosition = true;
|
||||
public bool followAttachmentZSpacing = false;
|
||||
[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 virtual 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 virtual void LateUpdate () {
|
||||
if (!valid) {
|
||||
Initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
#endif
|
||||
Skeleton skeleton = skeletonGraphic.Skeleton;
|
||||
if (bone == null) {
|
||||
if (string.IsNullOrEmpty(boneName)) return;
|
||||
bone = skeleton.FindBone(boneName);
|
||||
if (!SetBone(boneName)) return;
|
||||
}
|
||||
|
||||
RectTransform thisTransform = this.transform as RectTransform;
|
||||
if (thisTransform == null) return;
|
||||
|
||||
float scale = skeletonGraphic.MeshScale;
|
||||
Vector2 offset = skeletonGraphic.MeshOffset;
|
||||
float additionalFlipScale = 1;
|
||||
|
||||
float scaleSignX = 1;
|
||||
float scaleSignY = 1;
|
||||
Bone parentBone = bone.Parent;
|
||||
if (followParentWorldScale || followLocalScale || followSkeletonFlip) {
|
||||
Vector3 localScale = new Vector3(1f, 1f, 1f);
|
||||
if (followParentWorldScale && parentBone != null) {
|
||||
float cumulativeScaleX = 1.0f;
|
||||
float cumulativeScaleY = 1.0f;
|
||||
Bone p = parentBone;
|
||||
while (p != null) {
|
||||
cumulativeScaleX *= p.AppliedPose.ScaleX;
|
||||
cumulativeScaleY *= p.AppliedPose.ScaleY;
|
||||
p = p.Parent;
|
||||
};
|
||||
scaleSignX = Mathf.Sign(cumulativeScaleX);
|
||||
scaleSignY = Mathf.Sign(cumulativeScaleY);
|
||||
localScale = new Vector3(parentBone.AppliedPose.WorldScaleX * scaleSignX, parentBone.AppliedPose.WorldScaleY * scaleSignY, 1f);
|
||||
}
|
||||
if (followLocalScale)
|
||||
localScale.Scale(new Vector3(bone.AppliedPose.ScaleX, bone.AppliedPose.ScaleY, 1f));
|
||||
if (followSkeletonFlip)
|
||||
localScale.y *= Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY) * additionalFlipScale;
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
|
||||
if (skeletonTransformIsParent) {
|
||||
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
|
||||
thisTransform.localPosition = new Vector3(
|
||||
followXYPosition ? bone.AppliedPose.WorldX * scale + offset.x : thisTransform.localPosition.x,
|
||||
followXYPosition ? bone.AppliedPose.WorldY * scale + offset.y : thisTransform.localPosition.y,
|
||||
followZPosition ? (followAttachmentZSpacing ? GetAttachmentZPosition() : 0f) : thisTransform.localPosition.z);
|
||||
if (followBoneRotation) thisTransform.localRotation = bone.GetQuaternion();
|
||||
} else { // For special cases: Use transform world properties if transform relationship is complicated
|
||||
if (!skeletonTransform) return;
|
||||
|
||||
float z0Position = (followZPosition && followAttachmentZSpacing) ? GetAttachmentZPosition() : 0f;
|
||||
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(
|
||||
new Vector3(bone.AppliedPose.WorldX * scale + offset.x, bone.AppliedPose.WorldY * scale + offset.y, z0Position));
|
||||
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.AppliedPose.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;
|
||||
}
|
||||
if (followParentWorldScale && scaleSignX < 0)
|
||||
boneWorldRotation += 180f;
|
||||
|
||||
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
|
||||
if (followLocalScale && bone.AppliedPose.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);
|
||||
}
|
||||
}
|
||||
|
||||
float GetAttachmentZPosition () {
|
||||
var drawOrderPose = skeletonGraphic.Skeleton.DrawOrder.Pose;
|
||||
int boneIndex = drawOrderPose.FindIndex(slot => slot.Bone == bone);
|
||||
if (boneIndex < 0) return 0f;
|
||||
return skeletonGraphic.MeshSettings.zSpacing * skeletonGraphic.MeshScale * boneIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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,291 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity-utility-components#BoundingBoxFollower")]
|
||||
public class BoundingBoxFollower : MonoBehaviour, IUpgradable {
|
||||
internal static bool DebugMessages = true;
|
||||
|
||||
#region Inspector
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
|
||||
public string slotName;
|
||||
public bool isTrigger, usedByEffector, usedByComposite;
|
||||
public bool clearStateOnDisable = true;
|
||||
#endregion
|
||||
|
||||
Slot slot;
|
||||
BoundingBoxAttachment currentAttachment;
|
||||
string currentAttachmentName;
|
||||
PolygonCollider2D currentCollider;
|
||||
bool skinBoneEnabled = true;
|
||||
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; } }
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
protected void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Start () {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.OnRebuild -= HandleRebuild;
|
||||
skeletonRenderer.OnRebuild += HandleRebuild;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void HandleRebuild (ISkeletonRenderer 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.
|
||||
slotName == slot.Data.Name // Slot object did not change.
|
||||
)
|
||||
return;
|
||||
|
||||
slot = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
colliderTable.Clear();
|
||||
nameTable.Clear();
|
||||
|
||||
Skeleton skeleton = skeletonRenderer.skeleton;
|
||||
if (skeleton == null)
|
||||
return;
|
||||
slot = skeleton.FindSlot(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 slotIndex = slot.Data.Index;
|
||||
|
||||
int requiredCollidersCount = 0;
|
||||
PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
|
||||
if (this.gameObject.activeInHierarchy) {
|
||||
foreach (Skin skin in skeleton.Data.Skins)
|
||||
AddCollidersForSkin(skin, skeleton, slotIndex, colliders, ref requiredCollidersCount);
|
||||
|
||||
if (skeleton.Skin != null)
|
||||
AddCollidersForSkin(skeleton.Skin, skeleton, slotIndex, colliders, ref requiredCollidersCount);
|
||||
}
|
||||
DisposeExcessCollidersAfter(requiredCollidersCount);
|
||||
skinBoneEnabled = slot.Bone.Active;
|
||||
|
||||
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, Skeleton skeleton, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) {
|
||||
if (skin == null) return;
|
||||
List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (Skin.SkinEntry entry in skinEntries) {
|
||||
Attachment attachment = skin.GetAttachment(slotIndex, entry.Placeholder);
|
||||
BoundingBoxAttachment 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)) {
|
||||
PolygonCollider2D bbCollider = collidersCount < previousColliders.Length ?
|
||||
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
|
||||
++collidersCount;
|
||||
SkeletonUtility.SetColliderPointsLocal(bbCollider, skeleton, slot, boundingBoxAttachment);
|
||||
bbCollider.isTrigger = isTrigger;
|
||||
bbCollider.usedByEffector = usedByEffector;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
bbCollider.compositeOperation = usedByComposite ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
bbCollider.usedByComposite = usedByComposite;
|
||||
#endif
|
||||
bbCollider.enabled = false;
|
||||
bbCollider.hideFlags = HideFlags.NotEditable;
|
||||
colliderTable.Add(boundingBoxAttachment, bbCollider);
|
||||
nameTable.Add(boundingBoxAttachment, entry.Placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (clearStateOnDisable)
|
||||
ClearState();
|
||||
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.OnRebuild -= HandleRebuild;
|
||||
}
|
||||
|
||||
public void ClearState () {
|
||||
if (colliderTable != null)
|
||||
foreach (PolygonCollider2D col in colliderTable.Values)
|
||||
col.enabled = false;
|
||||
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
}
|
||||
|
||||
void DisposeExcessCollidersAfter (int requiredCount) {
|
||||
PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
|
||||
if (colliders.Length == 0) return;
|
||||
|
||||
for (int i = requiredCount; i < colliders.Length; ++i) {
|
||||
PolygonCollider2D collider = colliders[i];
|
||||
if (collider != null) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
DestroyImmediate(collider);
|
||||
else
|
||||
#endif
|
||||
Destroy(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
var slotPose = slot.AppliedPose;
|
||||
if (slot != null && (slotPose.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) {
|
||||
skinBoneEnabled = slot.Bone.Active;
|
||||
MatchAttachment(slotPose.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) {
|
||||
BoundingBoxAttachment 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 || !skinBoneEnabled) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at BoundingBoxFollower, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0317ee9ba6e1b1e49a030268e026d372
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2023_1_OR_NEWER
|
||||
#define USE_COLLIDER_COMPOSITE_OPERATION
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity-utility-components#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, usedByEffector, usedByComposite;
|
||||
public bool clearStateOnDisable = true;
|
||||
#endregion
|
||||
|
||||
Slot slot;
|
||||
BoundingBoxAttachment currentAttachment;
|
||||
string currentAttachmentName;
|
||||
PolygonCollider2D currentCollider;
|
||||
bool skinBoneEnabled = true;
|
||||
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 (ISkeletonRenderer 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.
|
||||
slotName == slot.Data.Name // Slot object did not change.
|
||||
)
|
||||
return;
|
||||
|
||||
slot = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
colliderTable.Clear();
|
||||
nameTable.Clear();
|
||||
|
||||
Skeleton skeleton = skeletonGraphic.Skeleton;
|
||||
if (skeleton == null)
|
||||
return;
|
||||
slot = skeleton.FindSlot(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 slotIndex = slot.Data.Index;
|
||||
|
||||
int requiredCollidersCount = 0;
|
||||
PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
|
||||
if (this.gameObject.activeInHierarchy) {
|
||||
float scale = skeletonGraphic.MeshScale;
|
||||
foreach (Skin skin in skeleton.Data.Skins)
|
||||
AddCollidersForSkin(skin, skeleton, slotIndex, colliders, scale, ref requiredCollidersCount);
|
||||
|
||||
if (skeleton.Skin != null)
|
||||
AddCollidersForSkin(skeleton.Skin, skeleton, slotIndex, colliders, scale, ref requiredCollidersCount);
|
||||
}
|
||||
DisposeExcessCollidersAfter(requiredCollidersCount);
|
||||
skinBoneEnabled = slot.Bone.Active;
|
||||
|
||||
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, Skeleton skeleton, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) {
|
||||
if (skin == null) return;
|
||||
List<Skin.SkinEntry> skinEntries = new List<Skin.SkinEntry>();
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (Skin.SkinEntry entry in skinEntries) {
|
||||
Attachment attachment = skin.GetAttachment(slotIndex, entry.Placeholder);
|
||||
BoundingBoxAttachment 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)) {
|
||||
PolygonCollider2D bbCollider = collidersCount < previousColliders.Length ?
|
||||
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
|
||||
++collidersCount;
|
||||
SkeletonUtility.SetColliderPointsLocal(bbCollider, skeleton, slot, boundingBoxAttachment, scale);
|
||||
bbCollider.isTrigger = isTrigger;
|
||||
bbCollider.usedByEffector = usedByEffector;
|
||||
#if USE_COLLIDER_COMPOSITE_OPERATION
|
||||
bbCollider.compositeOperation = usedByComposite ?
|
||||
Collider2D.CompositeOperation.Merge : Collider2D.CompositeOperation.None;
|
||||
#else
|
||||
bbCollider.usedByComposite = usedByComposite;
|
||||
#endif
|
||||
bbCollider.enabled = false;
|
||||
bbCollider.hideFlags = HideFlags.NotEditable;
|
||||
colliderTable.Add(boundingBoxAttachment, bbCollider);
|
||||
nameTable.Add(boundingBoxAttachment, entry.Placeholder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (clearStateOnDisable)
|
||||
ClearState();
|
||||
|
||||
if (skeletonGraphic != null)
|
||||
skeletonGraphic.OnRebuild -= HandleRebuild;
|
||||
}
|
||||
|
||||
public void ClearState () {
|
||||
if (colliderTable != null)
|
||||
foreach (PolygonCollider2D col in colliderTable.Values)
|
||||
col.enabled = false;
|
||||
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
}
|
||||
|
||||
void DisposeExcessCollidersAfter (int requiredCount) {
|
||||
PolygonCollider2D[] colliders = GetComponents<PolygonCollider2D>();
|
||||
if (colliders.Length == 0) return;
|
||||
|
||||
for (int i = requiredCount; i < colliders.Length; ++i) {
|
||||
PolygonCollider2D collider = colliders[i];
|
||||
if (collider != null) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
DestroyImmediate(collider);
|
||||
else
|
||||
#endif
|
||||
Destroy(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
var slotPose = slot.AppliedPose;
|
||||
if (slot != null && (slotPose.Attachment != currentAttachment || skinBoneEnabled != slot.Bone.Active)) {
|
||||
skinBoneEnabled = slot.Bone.Active;
|
||||
MatchAttachment(slotPose.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) {
|
||||
BoundingBoxAttachment 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0bf7b497af9f74280040d96cdf88da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[AddComponentMenu("Spine/Point Follower")]
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-utility-components#PointFollower")]
|
||||
public class PointFollower : MonoBehaviour, IHasSkeletonRenderer, IHasSkeletonComponent, IUpgradable {
|
||||
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
public ISkeletonRenderer SkeletonRenderer { get { return this.skeletonRenderer; } }
|
||||
public ISkeletonRenderer Renderer { 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; } }
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
protected void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Initialize () {
|
||||
valid = skeletonRenderer != null && skeletonRenderer.valid;
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
UpdateReferences();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor) LateUpdate();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleRebuildRenderer (ISkeletonRenderer 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)) {
|
||||
Skeleton skeleton = skeletonRenderer.Skeleton;
|
||||
|
||||
Slot slot = skeleton.FindSlot(slotName);
|
||||
if (slot != null) {
|
||||
int slotIndex = slot.Data.Index;
|
||||
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;
|
||||
var bonePose = bone.AppliedPose;
|
||||
point.ComputeWorldPosition(bonePose, out worldPos.x, out worldPos.y);
|
||||
float rotation = point.ComputeWorldRotation(bonePose);
|
||||
|
||||
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;
|
||||
|
||||
Quaternion 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;
|
||||
Skeleton skeleton = skeletonRenderer.Skeleton;
|
||||
localScale.y = Mathf.Abs(localScale.y) * Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY);
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at PointFollower, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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: 8459fe8c08b88a84484f2b7ba5f36517
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2021_1_OR_NEWER
|
||||
#define HAS_LIST_POOL
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if HAS_LIST_POOL
|
||||
using UnityEngine.Pool;
|
||||
#endif
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// A minimal MaskableGraphic subclass for rendering multiple submeshes
|
||||
/// at a <see cref="SkeletonGraphic"/>.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(CanvasRenderer))]
|
||||
public class SkeletonSubmeshGraphic : MaskableGraphic {
|
||||
public override void SetMaterialDirty () { }
|
||||
public override void SetVerticesDirty () { }
|
||||
protected override void OnPopulateMesh (VertexHelper vh) {
|
||||
vh.Clear();
|
||||
}
|
||||
|
||||
protected override void OnDisable () {
|
||||
base.OnDisable();
|
||||
this.canvasRenderer.cull = true;
|
||||
}
|
||||
|
||||
protected override void OnEnable () {
|
||||
base.OnEnable();
|
||||
this.canvasRenderer.cull = false;
|
||||
}
|
||||
|
||||
#if HAS_LIST_POOL
|
||||
public Material UpdateModifiedMaterial (Material baseMaterial) {
|
||||
List<IMaterialModifier> modifierComponents = ListPool<IMaterialModifier>.Get();
|
||||
GetComponents<IMaterialModifier>(modifierComponents);
|
||||
|
||||
Material currentMaterial = baseMaterial;
|
||||
for (int i = 0; i < modifierComponents.Count; i++)
|
||||
currentMaterial = modifierComponents[i].GetModifiedMaterial(currentMaterial);
|
||||
ListPool<IMaterialModifier>.Release(modifierComponents);
|
||||
return currentMaterial;
|
||||
}
|
||||
#else
|
||||
public Material UpdateModifiedMaterial (Material baseMaterial) {
|
||||
return GetModifiedMaterial(baseMaterial);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: deeb12332c062954093c24a3fab10b83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b957aa69dae9f948bacdeec549d28ea
|
||||
folderAsset: yes
|
||||
timeCreated: 1593173800
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 Spine.Unity.AnimationTools;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
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("https://esotericsoftware.com/spine-unity-utility-components#SkeletonMecanimRootMotion")]
|
||||
public class SkeletonMecanimRootMotion : SkeletonRootMotionBase {
|
||||
#region Inspector
|
||||
const int DefaultMecanimLayerFlags = -1;
|
||||
public int mecanimLayerFlags = DefaultMecanimLayerFlags;
|
||||
#endregion
|
||||
|
||||
protected Vector2 movementDelta;
|
||||
protected float rotationDelta;
|
||||
|
||||
SkeletonMecanim skeletonMecanim;
|
||||
public SkeletonMecanim SkeletonMecanim {
|
||||
get {
|
||||
return skeletonMecanim ? skeletonMecanim : skeletonMecanim = GetComponent<SkeletonMecanim>();
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 GetRemainingRootMotion (int layerIndex) {
|
||||
KeyValuePair<Animation, float> pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
|
||||
Animation animation = pair.Key;
|
||||
float 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) {
|
||||
KeyValuePair<Animation, float> pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
|
||||
Animation animation = pair.Key;
|
||||
float time = pair.Value;
|
||||
if (animation == null)
|
||||
return new RootMotionInfo();
|
||||
return GetAnimationRootMotionInfo(animation, time);
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
mecanimLayerFlags = DefaultMecanimLayerFlags;
|
||||
}
|
||||
|
||||
public override void Initialize () {
|
||||
base.Initialize();
|
||||
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);
|
||||
}
|
||||
if (transformRotation) {
|
||||
if (!playsBackward) {
|
||||
rotationDelta += weight * GetAnimationRootMotionRotation(lastTime, time, animation);
|
||||
} else {
|
||||
rotationDelta -= weight * GetAnimationRootMotionRotation(time, lastTime, animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateAnimationsMovementDelta () {
|
||||
// Note: movement delta is not gathered after animation but
|
||||
// in OnClipApplied after every applied animation.
|
||||
Vector2 result = movementDelta;
|
||||
movementDelta = Vector2.zero;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override float CalculateAnimationsRotationDelta () {
|
||||
// Note: movement delta is not gathered after animation but
|
||||
// in OnClipApplied after every applied animation.
|
||||
float result = rotationDelta;
|
||||
rotationDelta = 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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,185 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 Spine.Unity.AnimationTools;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
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("https://esotericsoftware.com/spine-unity-utility-components#SkeletonRootMotion")]
|
||||
public class SkeletonRootMotion : SkeletonRootMotionBase {
|
||||
#region Inspector
|
||||
const int DefaultAnimationTrackFlags = -1;
|
||||
public int animationTrackFlags = DefaultAnimationTrackFlags;
|
||||
#endregion
|
||||
|
||||
AnimationState animationState;
|
||||
SkeletonGraphic skeletonGraphic;
|
||||
|
||||
public override Vector2 GetRemainingRootMotion (int trackIndex) {
|
||||
TrackEntry entry = animationState.GetTrack(trackIndex);
|
||||
if (entry == null)
|
||||
return Vector2.zero;
|
||||
|
||||
Animation animation = entry.Animation;
|
||||
float start = entry.AnimationTime;
|
||||
float end = animation.Duration;
|
||||
return GetAnimationRootMotion(start, end, animation);
|
||||
}
|
||||
|
||||
public override RootMotionInfo GetRootMotionInfo (int trackIndex) {
|
||||
TrackEntry entry = animationState.GetTrack(trackIndex);
|
||||
if (entry == null)
|
||||
return new RootMotionInfo();
|
||||
|
||||
Animation animation = entry.Animation;
|
||||
float time = entry.AnimationTime;
|
||||
return GetAnimationRootMotionInfo(entry.Animation, time);
|
||||
}
|
||||
|
||||
protected override float AdditionalScale {
|
||||
get {
|
||||
return skeletonGraphic ? skeletonGraphic.MeshScale : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
animationTrackFlags = DefaultAnimationTrackFlags;
|
||||
}
|
||||
|
||||
public override void Initialize () {
|
||||
base.Initialize();
|
||||
IAnimationStateComponent animstateComponent = animationComponent as IAnimationStateComponent;
|
||||
this.animationState = (animstateComponent != null) ? animstateComponent.AnimationState : null;
|
||||
|
||||
skeletonGraphic = this.GetComponent<SkeletonGraphic>();
|
||||
}
|
||||
|
||||
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 entry = animationState.GetTrack(trackIndex);
|
||||
TrackEntry next = null;
|
||||
while (entry != null) {
|
||||
Animation animation = entry.Animation;
|
||||
float start = entry.AnimationLast;
|
||||
float end = entry.AnimationTime;
|
||||
Vector2 currentDelta = GetAnimationRootMotion(start, end, animation);
|
||||
if (currentDelta != Vector2.zero) {
|
||||
ApplyMixAlphaToDelta(ref currentDelta, next, entry);
|
||||
localDelta += currentDelta;
|
||||
}
|
||||
|
||||
// Traverse mixingFrom chain.
|
||||
next = entry;
|
||||
entry = entry.MixingFrom;
|
||||
}
|
||||
}
|
||||
return localDelta;
|
||||
}
|
||||
|
||||
protected override float CalculateAnimationsRotationDelta () {
|
||||
float localDelta = 0;
|
||||
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 entry = animationState.GetTrack(trackIndex);
|
||||
TrackEntry next = null;
|
||||
while (entry != null) {
|
||||
Animation animation = entry.Animation;
|
||||
float start = entry.AnimationLast;
|
||||
float end = entry.AnimationTime;
|
||||
float currentDelta = GetAnimationRootMotionRotation(start, end, animation);
|
||||
if (currentDelta != 0) {
|
||||
ApplyMixAlphaToDelta(ref currentDelta, next, entry);
|
||||
localDelta += currentDelta;
|
||||
}
|
||||
|
||||
// Traverse mixingFrom chain.
|
||||
next = entry;
|
||||
entry = entry.MixingFrom;
|
||||
}
|
||||
}
|
||||
return localDelta;
|
||||
}
|
||||
|
||||
void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry entry) {
|
||||
float mixAlpha = 1;
|
||||
GetMixAlpha(ref mixAlpha, next, entry);
|
||||
currentDelta *= mixAlpha;
|
||||
}
|
||||
|
||||
void ApplyMixAlphaToDelta (ref float currentDelta, TrackEntry next, TrackEntry entry) {
|
||||
float mixAlpha = 1;
|
||||
GetMixAlpha(ref mixAlpha, next, entry);
|
||||
currentDelta *= mixAlpha;
|
||||
}
|
||||
|
||||
void GetMixAlpha (ref float cumulatedMixAlpha, TrackEntry next, TrackEntry entry) {
|
||||
// code below based on AnimationState.cs
|
||||
if (next != null) {
|
||||
float mix = next.MixDuration == 0 ? 1 : Mathf.Min(1, next.MixTime / next.MixDuration);
|
||||
float fromMix = (entry.MixingFrom == null || entry.MixDuration == 0) ?
|
||||
1 : Mathf.Min(1, entry.MixTime / entry.MixDuration);
|
||||
float mixAndAlpha = entry.Alpha * fromMix * (1 - mix);
|
||||
cumulatedMixAlpha *= mixAndAlpha;
|
||||
} else {
|
||||
float mix = entry.MixDuration == 0 ? 1 : Mathf.Min(1, entry.Alpha * (entry.MixTime / entry.MixDuration));
|
||||
cumulatedMixAlpha *= mix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f21c9538588898a45a3da22bf4779ab3
|
||||
timeCreated: 1591121072
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+766
@@ -0,0 +1,766 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_6000_0_OR_NEWER
|
||||
#define RIGIDBODY2D_USES_LINEAR_VELOCITY
|
||||
#endif
|
||||
|
||||
// In order to respect TransformConstraints modifying the scale of parent bones,
|
||||
// GetScaleAffectingRootMotion() now uses parentBone.AScaleX and AScaleY instead
|
||||
// of previously used ScaleX and ScaleY. If you require the previous behaviour,
|
||||
// comment out the define below.
|
||||
#define USE_APPLIED_PARENT_SCALE
|
||||
|
||||
using Spine.Unity.AnimationTools;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for skeleton root motion components.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(1)]
|
||||
abstract public class SkeletonRootMotionBase : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
[SpineBone]
|
||||
public string rootMotionBoneName = "root";
|
||||
public bool transformPositionX = true;
|
||||
public bool transformPositionY = true;
|
||||
public bool transformRotation = false;
|
||||
|
||||
public float rootMotionScaleX = 1;
|
||||
public float rootMotionScaleY = 1;
|
||||
public float rootMotionScaleRotation = 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 bool applyRigidbody2DGravity = false;
|
||||
public Rigidbody rigidBody;
|
||||
|
||||
/// <summary>Delegate type for customizing application of rootmotion.
|
||||
public delegate void RootMotionDelegate (SkeletonRootMotionBase component, Vector2 translation, float rotation);
|
||||
/// <summary>This callback can be used to apply root-motion in a custom way. It is raised after evaluating
|
||||
/// this animation frame's root-motion, before it is potentially applied (see <see cref="disableOnOverride"/>)
|
||||
/// to either Transform or Rigidbody.
|
||||
/// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
|
||||
/// animation frames might take place before <c>FixedUpdate</c> is called once.
|
||||
/// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with
|
||||
/// this animation frame's skeleton-space root-motion (not cumulated). You can use
|
||||
/// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
|
||||
/// </summary>
|
||||
/// <seealso cref="PhysicsUpdateRootMotionOverride"/>
|
||||
public event RootMotionDelegate ProcessRootMotionOverride;
|
||||
/// <summary>This callback can be used to apply root-motion in a custom way. It is raised in FixedUpdate
|
||||
/// after (when <see cref="disableOnOverride"/> is set to false) or instead of when root-motion
|
||||
/// would be applied at the Rigidbody.
|
||||
/// When <see cref="SkeletonAnimation.UpdateTiming"/> is set to <see cref="UpdateTiming.InUpdate"/>, multiple
|
||||
/// animation frames might take place before before <c>FixedUpdate</c> is called once.
|
||||
/// The callback parameters <c>translation</c> and <c>rotation</c> are filled out with the
|
||||
/// (cumulated) skeleton-space root-motion since the the last <c>FixedUpdate</c> call. You can use
|
||||
/// e.g. <c>transform.TransformVector()</c> to transform skeleton-space root-motion to world space.
|
||||
/// </summary>
|
||||
/// <seealso cref="ProcessRootMotionOverride"/>
|
||||
public event RootMotionDelegate PhysicsUpdateRootMotionOverride;
|
||||
/// <summary>When true, root-motion is not applied to the Transform or Rigidbody.
|
||||
/// Otherwise the delegate callbacks are issued additionally.</summary>
|
||||
public bool disableOnOverride = true;
|
||||
|
||||
public Bone RootMotionBone { get { return rootMotionBone; } }
|
||||
|
||||
public bool UsesRigidbody {
|
||||
get { return rigidBody != null || rigidBody2D != null; }
|
||||
}
|
||||
|
||||
/// <summary>Root motion translation that has been applied in the preceding <c>FixedUpdate</c> call
|
||||
/// if a rigidbody is assigned at either <c>rigidbody</c> or <c>rigidbody2D</c>.
|
||||
/// Returns <c>Vector2.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.
|
||||
/// This can be necessary when multiple scripts call <c>Rigidbody2D.MovePosition</c>,
|
||||
/// where the last call overwrites the effect of preceding ones.</summary>
|
||||
public Vector2 PreviousRigidbodyRootMotion2D {
|
||||
get { return new Vector2(previousRigidbodyRootMotion.x, previousRigidbodyRootMotion.y); }
|
||||
}
|
||||
|
||||
/// <summary>Root motion translation that has been applied in the preceding <c>FixedUpdate</c> call
|
||||
/// if a rigidbody is assigned at either <c>rigidbody</c> or <c>rigidbody2D</c>.
|
||||
/// Returns <c>Vector3.zero</c> when <c>rigidbody</c> and <c>rigidbody2D</c> are null.</summary>
|
||||
public Vector3 PreviousRigidbodyRootMotion3D {
|
||||
get { return previousRigidbodyRootMotion; }
|
||||
}
|
||||
|
||||
/// <summary>Additional translation to add to <c>Rigidbody2D.MovePosition</c>
|
||||
/// called in FixedUpdate. This can be necessary when multiple scripts call
|
||||
/// <c>MovePosition</c>, where the last call overwrites the effect of preceding ones.
|
||||
/// Has no effect if <c>rigidBody2D</c> is null.</summary>
|
||||
public Vector2 AdditionalRigidbody2DMovement {
|
||||
get { return additionalRigidbody2DMovement; }
|
||||
set { additionalRigidbody2DMovement = value; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected bool SkeletonAnimationUsesFixedUpdate {
|
||||
get {
|
||||
if (animationComponent != null) {
|
||||
return animationComponent.UpdateTiming == UpdateTiming.InFixedUpdate;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected SkeletonAnimationBase animationComponent;
|
||||
protected Bone rootMotionBone;
|
||||
protected int rootMotionBoneIndex;
|
||||
protected List<int> transformConstraintIndices = new List<int>();
|
||||
protected List<Vector2> transformConstraintLastPos = new List<Vector2>();
|
||||
protected List<float> transformConstraintLastRotation = new List<float>();
|
||||
protected List<Bone> topLevelBones = new List<Bone>();
|
||||
protected Vector2 initialOffset = Vector2.zero;
|
||||
protected bool accumulatedUntilFixedUpdate = false;
|
||||
protected Vector2 tempSkeletonDisplacement;
|
||||
protected Vector3 rigidbodyDisplacement;
|
||||
protected Vector3 previousRigidbodyRootMotion = Vector2.zero;
|
||||
protected Vector2 additionalRigidbody2DMovement = Vector2.zero;
|
||||
|
||||
protected Quaternion rigidbodyLocalRotation = Quaternion.identity;
|
||||
protected float rigidbody2DRotation;
|
||||
protected float initialOffsetRotation;
|
||||
protected float tempSkeletonRotation;
|
||||
|
||||
protected virtual void Reset () {
|
||||
FindRigidbodyComponent();
|
||||
}
|
||||
|
||||
protected virtual void Start () {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected void InitializeOnRebuild (ISkeletonAnimation animatedSkeletonComponent) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public virtual void Initialize () {
|
||||
animationComponent = GetComponent<SkeletonAnimationBase>();
|
||||
GatherTopLevelBones();
|
||||
SetRootMotionBone(rootMotionBoneName);
|
||||
if (rootMotionBone != null) {
|
||||
initialOffset = new Vector2(rootMotionBone.Pose.X, rootMotionBone.Pose.Y);
|
||||
initialOffsetRotation = rootMotionBone.Pose.Rotation;
|
||||
}
|
||||
|
||||
if (animationComponent != null) {
|
||||
animationComponent.UpdateLocal -= HandleUpdateLocal;
|
||||
animationComponent.UpdateLocal += HandleUpdateLocal;
|
||||
|
||||
animationComponent.OnAnimationRebuild -= InitializeOnRebuild;
|
||||
animationComponent.OnAnimationRebuild += InitializeOnRebuild;
|
||||
|
||||
SkeletonUtility skeletonUtility = GetComponent<SkeletonUtility>();
|
||||
if (skeletonUtility != null) {
|
||||
// SkeletonUtilityBone shall receive UpdateLocal callbacks for bone-following after root motion
|
||||
// clears the root-bone position.
|
||||
skeletonUtility.ResubscribeEvents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate () {
|
||||
// Root motion is only applied when component is enabled.
|
||||
if (!this.isActiveAndEnabled)
|
||||
return;
|
||||
// When SkeletonAnimation component uses UpdateTiming.InFixedUpdate,
|
||||
// we directly call PhysicsUpdate in HandleUpdateLocal instead of here.
|
||||
if (!SkeletonAnimationUsesFixedUpdate)
|
||||
PhysicsUpdate(false);
|
||||
}
|
||||
|
||||
protected virtual void PhysicsUpdate (bool skeletonAnimationUsesFixedUpdate) {
|
||||
Vector2 callbackDisplacement = tempSkeletonDisplacement;
|
||||
float callbackRotation = tempSkeletonRotation;
|
||||
|
||||
bool isApplyAtRigidbodyAllowed = PhysicsUpdateRootMotionOverride == null || !disableOnOverride;
|
||||
if (isApplyAtRigidbodyAllowed) {
|
||||
if (rigidBody2D != null) {
|
||||
Vector2 gravityAndVelocityMovement = Vector2.zero;
|
||||
if (applyRigidbody2DGravity) {
|
||||
float deltaTime = Time.fixedDeltaTime;
|
||||
float deltaTimeSquared = (deltaTime * deltaTime);
|
||||
|
||||
#if RIGIDBODY2D_USES_LINEAR_VELOCITY
|
||||
rigidBody2D.linearVelocity += rigidBody2D.gravityScale * Physics2D.gravity * deltaTime;
|
||||
gravityAndVelocityMovement = 0.5f * rigidBody2D.gravityScale * Physics2D.gravity * deltaTimeSquared +
|
||||
rigidBody2D.linearVelocity * deltaTime;
|
||||
#else
|
||||
rigidBody2D.velocity += rigidBody2D.gravityScale * Physics2D.gravity * deltaTime;
|
||||
gravityAndVelocityMovement = 0.5f * rigidBody2D.gravityScale * Physics2D.gravity * deltaTimeSquared +
|
||||
rigidBody2D.velocity * deltaTime;
|
||||
#endif
|
||||
}
|
||||
|
||||
Vector2 rigidbodyDisplacement2D = new Vector2(rigidbodyDisplacement.x, rigidbodyDisplacement.y);
|
||||
// Note: MovePosition seems to be the only precise and reliable way to set movement delta,
|
||||
// for both 2D and 3D rigidbodies.
|
||||
// Setting velocity like "rigidBody2D.velocity = movement/deltaTime" works perfectly in mid-air
|
||||
// without gravity and ground collision, unfortunately when on the ground, friction causes severe
|
||||
// slowdown. Using a zero-friction PhysicsMaterial leads to sliding endlessly along the ground as
|
||||
// soon as forces are applied. Additionally, there is no rigidBody2D.isGrounded, requiring our own
|
||||
// checks.
|
||||
rigidBody2D.MovePosition(gravityAndVelocityMovement + new Vector2(rigidBody2D.position.x, rigidBody2D.position.y)
|
||||
+ rigidbodyDisplacement2D + additionalRigidbody2DMovement);
|
||||
rigidBody2D.MoveRotation(rigidbody2DRotation + rigidBody2D.rotation);
|
||||
} else if (rigidBody != null) {
|
||||
rigidBody.MovePosition(rigidBody.position
|
||||
+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, rigidbodyDisplacement.z));
|
||||
rigidBody.MoveRotation(rigidBody.rotation * rigidbodyLocalRotation);
|
||||
}
|
||||
}
|
||||
|
||||
previousRigidbodyRootMotion = rigidbodyDisplacement;
|
||||
if (accumulatedUntilFixedUpdate) {
|
||||
Vector2 parentBoneScale;
|
||||
GetScaleAffectingRootMotion(out parentBoneScale);
|
||||
ClearEffectiveBoneOffsets(parentBoneScale);
|
||||
animationComponent.Skeleton.UpdateWorldTransform(Physics.Pose);
|
||||
}
|
||||
ClearRigidbodyTempMovement();
|
||||
|
||||
if (PhysicsUpdateRootMotionOverride != null)
|
||||
PhysicsUpdateRootMotionOverride(this, callbackDisplacement, callbackRotation);
|
||||
}
|
||||
|
||||
protected virtual void OnDisable () {
|
||||
ClearRigidbodyTempMovement();
|
||||
}
|
||||
|
||||
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 ();
|
||||
protected virtual float CalculateAnimationsRotationDelta () { return 0; }
|
||||
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 ISkeletonComponent TargetSkeletonComponent {
|
||||
get {
|
||||
return TargetSkeletonAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
public ISkeletonAnimation TargetSkeletonAnimationComponent {
|
||||
get {
|
||||
return TargetSkeletonAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonAnimationBase TargetSkeletonAnimation {
|
||||
get {
|
||||
if (animationComponent == null)
|
||||
animationComponent = GetComponent<SkeletonAnimationBase>();
|
||||
return animationComponent;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRootMotionBone (string name) {
|
||||
Skeleton skeleton = animationComponent.Skeleton;
|
||||
Bone bone = skeleton.FindBone(name);
|
||||
if (bone != null) {
|
||||
this.rootMotionBoneIndex = bone.Data.Index;
|
||||
this.rootMotionBone = bone;
|
||||
FindTransformConstraintsAffectingBone();
|
||||
} else {
|
||||
Debug.Log("Bone named \"" + name + "\" could not be found. " +
|
||||
"Set 'skeletonRootMotion.rootMotionBoneName' before calling 'skeletonAnimation.Initialize(true)'.");
|
||||
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) {
|
||||
|
||||
if (startTime == endTime)
|
||||
return Vector2.zero;
|
||||
|
||||
TranslateTimeline translateTimeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
|
||||
TranslateXTimeline xTimeline = animation.FindTimelineForBone<TranslateXTimeline>(rootMotionBoneIndex);
|
||||
TranslateYTimeline yTimeline = animation.FindTimelineForBone<TranslateYTimeline>(rootMotionBoneIndex);
|
||||
|
||||
// Non-looped base
|
||||
Vector2 endPos = Vector2.zero;
|
||||
Vector2 startPos = Vector2.zero;
|
||||
if (translateTimeline != null) {
|
||||
endPos = translateTimeline.Evaluate(endTime);
|
||||
startPos = translateTimeline.Evaluate(startTime);
|
||||
} else if (xTimeline != null || yTimeline != null) {
|
||||
endPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime);
|
||||
startPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime);
|
||||
}
|
||||
ExposedList<IConstraint> constraints = animationComponent.Skeleton.Constraints;
|
||||
IConstraint[] constraintsItems = constraints.Items;
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint;
|
||||
if (constraint == null) continue;
|
||||
ApplyConstraintToPos(animation, constraint, constraintIndex, endTime, false, ref endPos);
|
||||
ApplyConstraintToPos(animation, constraint, constraintIndex, startTime, true, ref startPos);
|
||||
}
|
||||
Vector2 currentDelta = endPos - startPos;
|
||||
|
||||
// Looped additions
|
||||
if (startTime > endTime) {
|
||||
Vector2 loopPos = Vector2.zero;
|
||||
Vector2 zeroPos = Vector2.zero;
|
||||
if (translateTimeline != null) {
|
||||
loopPos = translateTimeline.Evaluate(animation.Duration);
|
||||
zeroPos = translateTimeline.Evaluate(0);
|
||||
} else if (xTimeline != null || yTimeline != null) {
|
||||
loopPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, animation.Duration);
|
||||
zeroPos = TimelineExtensions.Evaluate(xTimeline, yTimeline, 0);
|
||||
}
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex];
|
||||
ApplyConstraintToPos(animation, constraint, constraintIndex, animation.Duration, false, ref loopPos);
|
||||
ApplyConstraintToPos(animation, constraint, constraintIndex, 0, false, ref zeroPos);
|
||||
}
|
||||
currentDelta += loopPos - zeroPos;
|
||||
}
|
||||
UpdateLastConstraintPos(constraintsItems);
|
||||
return currentDelta;
|
||||
}
|
||||
|
||||
public float GetAnimationRootMotionRotation (Animation animation) {
|
||||
return GetAnimationRootMotionRotation(0, animation.Duration, animation);
|
||||
}
|
||||
|
||||
public float GetAnimationRootMotionRotation (float startTime, float endTime,
|
||||
Animation animation) {
|
||||
|
||||
if (startTime == endTime)
|
||||
return 0;
|
||||
|
||||
RotateTimeline rotateTimeline = animation.FindTimelineForBone<RotateTimeline>(rootMotionBoneIndex);
|
||||
|
||||
// Non-looped base
|
||||
float endRotation = 0;
|
||||
float startRotation = 0;
|
||||
if (rotateTimeline != null) {
|
||||
endRotation = rotateTimeline.Evaluate(endTime);
|
||||
startRotation = rotateTimeline.Evaluate(startTime);
|
||||
}
|
||||
|
||||
ExposedList<IConstraint> constraints = animationComponent.Skeleton.Constraints;
|
||||
IConstraint[] constraintsItems = constraints.Items;
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint;
|
||||
if (constraint == null) continue;
|
||||
ApplyConstraintToRotation(animation, constraint, constraintIndex, endTime, false, ref endRotation);
|
||||
ApplyConstraintToRotation(animation, constraint, constraintIndex, startTime, true, ref startRotation);
|
||||
}
|
||||
float currentDelta = endRotation - startRotation;
|
||||
|
||||
// Looped additions
|
||||
if (startTime > endTime) {
|
||||
float loopRotation = 0;
|
||||
float zeroPos = 0;
|
||||
if (rotateTimeline != null) {
|
||||
loopRotation = rotateTimeline.Evaluate(animation.Duration);
|
||||
zeroPos = rotateTimeline.Evaluate(0);
|
||||
}
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex];
|
||||
ApplyConstraintToRotation(animation, constraint, constraintIndex, animation.Duration, false, ref loopRotation);
|
||||
ApplyConstraintToRotation(animation, constraint, constraintIndex, 0, false, ref zeroPos);
|
||||
}
|
||||
currentDelta += loopRotation - zeroPos;
|
||||
}
|
||||
UpdateLastConstraintRotation(constraintsItems);
|
||||
return currentDelta;
|
||||
}
|
||||
|
||||
void ApplyConstraintToPos (Animation animation, TransformConstraint constraint,
|
||||
int constraintIndex, float time, bool useLastConstraintPos, ref Vector2 pos) {
|
||||
TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
|
||||
if (timeline == null)
|
||||
return;
|
||||
Vector2 mixXY = timeline.EvaluateTranslateXYMix(time);
|
||||
Vector2 invMixXY = timeline.EvaluateTranslateXYMix(time);
|
||||
Vector2 constraintPos;
|
||||
if (useLastConstraintPos)
|
||||
constraintPos = transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)];
|
||||
else {
|
||||
Bone sourceBone = constraint.Source;
|
||||
constraintPos = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y);
|
||||
}
|
||||
pos = new Vector2(
|
||||
pos.x * invMixXY.x + constraintPos.x * mixXY.x,
|
||||
pos.y * invMixXY.y + constraintPos.y * mixXY.y);
|
||||
}
|
||||
|
||||
void ApplyConstraintToRotation (Animation animation, TransformConstraint constraint,
|
||||
int constraintIndex, float time, bool useLastConstraintRotation, ref float rotation) {
|
||||
TransformConstraintTimeline timeline = animation.FindTransformConstraintTimeline(constraintIndex);
|
||||
if (timeline == null)
|
||||
return;
|
||||
float mixRotate = timeline.EvaluateRotateMix(time);
|
||||
float invMixRotate = timeline.EvaluateRotateMix(time);
|
||||
float constraintRotation;
|
||||
if (useLastConstraintRotation)
|
||||
constraintRotation = transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)];
|
||||
else {
|
||||
Bone sourceBone = constraint.Source;
|
||||
constraintRotation = sourceBone.Pose.Rotation;
|
||||
}
|
||||
rotation = rotation * invMixRotate + constraintRotation * mixRotate;
|
||||
}
|
||||
|
||||
void UpdateLastConstraintPos (IConstraint[] constraintsItems) {
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex];
|
||||
Bone sourceBone = constraint.Source;
|
||||
transformConstraintLastPos[GetConstraintLastPosIndex(constraintIndex)] = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateLastConstraintRotation (IConstraint[] constraintsItems) {
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = (TransformConstraint)constraintsItems[constraintIndex];
|
||||
Bone sourceBone = constraint.Source;
|
||||
transformConstraintLastRotation[GetConstraintLastPosIndex(constraintIndex)] = sourceBone.Pose.Rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
|
||||
RootMotionInfo rootMotion = new RootMotionInfo();
|
||||
float duration = animation.Duration;
|
||||
float mid = duration * 0.5f;
|
||||
rootMotion.timeIsPastMid = currentTime > mid;
|
||||
TranslateTimeline timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
|
||||
if (timeline != null) {
|
||||
rootMotion.start = timeline.Evaluate(0);
|
||||
rootMotion.current = timeline.Evaluate(currentTime);
|
||||
rootMotion.mid = timeline.Evaluate(mid);
|
||||
rootMotion.end = timeline.Evaluate(duration);
|
||||
return rootMotion;
|
||||
}
|
||||
TranslateXTimeline xTimeline = animation.FindTimelineForBone<TranslateXTimeline>(rootMotionBoneIndex);
|
||||
TranslateYTimeline yTimeline = animation.FindTimelineForBone<TranslateYTimeline>(rootMotionBoneIndex);
|
||||
if (xTimeline != null || yTimeline != null) {
|
||||
rootMotion.start = TimelineExtensions.Evaluate(xTimeline, yTimeline, 0);
|
||||
rootMotion.current = TimelineExtensions.Evaluate(xTimeline, yTimeline, currentTime);
|
||||
rootMotion.mid = TimelineExtensions.Evaluate(xTimeline, yTimeline, mid);
|
||||
rootMotion.end = TimelineExtensions.Evaluate(xTimeline, yTimeline, duration);
|
||||
return rootMotion;
|
||||
}
|
||||
return rootMotion;
|
||||
}
|
||||
|
||||
int GetConstraintLastPosIndex (int constraintIndex) {
|
||||
ExposedList<IConstraint> constraints = animationComponent.Skeleton.Constraints;
|
||||
return transformConstraintIndices.FindIndex(addedIndex => addedIndex == constraintIndex);
|
||||
}
|
||||
|
||||
void FindTransformConstraintsAffectingBone () {
|
||||
ExposedList<IConstraint> constraints = animationComponent.Skeleton.Constraints;
|
||||
IConstraint[] constraintsItems = constraints.Items;
|
||||
for (int constraintIndex = 0, n = constraints.Count; constraintIndex < n; ++constraintIndex) {
|
||||
TransformConstraint constraint = constraintsItems[constraintIndex] as TransformConstraint;
|
||||
if (constraint == null) continue;
|
||||
if (constraint.Bones.Contains(rootMotionBone.AppliedPose)) {
|
||||
transformConstraintIndices.Add(constraintIndex);
|
||||
Bone sourceBone = constraint.Source;
|
||||
Vector2 constraintPos = new Vector2(sourceBone.Pose.X, sourceBone.Pose.Y);
|
||||
transformConstraintLastPos.Add(constraintPos);
|
||||
transformConstraintLastRotation.Add(sourceBone.Pose.Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetTimelineMovementDelta (float startTime, float endTime,
|
||||
TranslateXTimeline xTimeline, TranslateYTimeline yTimeline, Animation animation) {
|
||||
|
||||
Vector2 currentDelta;
|
||||
if (startTime > endTime) // Looped
|
||||
currentDelta =
|
||||
(TimelineExtensions.Evaluate(xTimeline, yTimeline, animation.Duration)
|
||||
- TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime))
|
||||
+ (TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime)
|
||||
- TimelineExtensions.Evaluate(xTimeline, yTimeline, 0));
|
||||
else if (startTime != endTime) // Non-looped
|
||||
currentDelta = TimelineExtensions.Evaluate(xTimeline, yTimeline, endTime)
|
||||
- TimelineExtensions.Evaluate(xTimeline, yTimeline, startTime);
|
||||
else
|
||||
currentDelta = Vector2.zero;
|
||||
return currentDelta;
|
||||
}
|
||||
|
||||
void GatherTopLevelBones () {
|
||||
topLevelBones.Clear();
|
||||
Skeleton skeleton = animationComponent.Skeleton;
|
||||
foreach (Bone bone in skeleton.Bones) {
|
||||
if (bone.Parent == null)
|
||||
topLevelBones.Add(bone);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleUpdateLocal (ISkeletonRenderer skeletonRenderer) {
|
||||
if (!this.isActiveAndEnabled)
|
||||
return; // Root motion is only applied when component is enabled.
|
||||
|
||||
Vector2 boneLocalDelta = CalculateAnimationsMovementDelta();
|
||||
Vector2 parentBoneScale;
|
||||
Vector2 totalScale;
|
||||
Vector2 skeletonTranslationDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale, out totalScale);
|
||||
float skeletonRotationDelta = 0;
|
||||
if (transformRotation) {
|
||||
float boneLocalDeltaRotation = CalculateAnimationsRotationDelta();
|
||||
boneLocalDeltaRotation *= rootMotionScaleRotation;
|
||||
skeletonRotationDelta = GetSkeletonSpaceRotationDelta(boneLocalDeltaRotation, totalScale);
|
||||
}
|
||||
|
||||
bool usesFixedUpdate = SkeletonAnimationUsesFixedUpdate;
|
||||
ApplyRootMotion(skeletonTranslationDelta, skeletonRotationDelta, parentBoneScale, usesFixedUpdate);
|
||||
|
||||
if (usesFixedUpdate)
|
||||
PhysicsUpdate(usesFixedUpdate);
|
||||
}
|
||||
|
||||
void ApplyRootMotion (Vector2 skeletonTranslationDelta, float skeletonRotationDelta, Vector2 parentBoneScale,
|
||||
bool skeletonAnimationUsesFixedUpdate) {
|
||||
|
||||
// Accumulated displacement is applied on the next Physics update in FixedUpdate.
|
||||
// Until the next Physics update, tempSkeletonDisplacement and tempSkeletonRotation
|
||||
// are offsetting bone locations to prevent stutter which would otherwise occur if
|
||||
// we don't move every Update.
|
||||
bool usesRigidbody = this.UsesRigidbody;
|
||||
bool applyToTransform = !usesRigidbody && (ProcessRootMotionOverride == null || !disableOnOverride);
|
||||
accumulatedUntilFixedUpdate = !applyToTransform && !skeletonAnimationUsesFixedUpdate;
|
||||
|
||||
if (ProcessRootMotionOverride != null)
|
||||
ProcessRootMotionOverride(this, skeletonTranslationDelta, skeletonRotationDelta);
|
||||
|
||||
// Apply root motion to Transform or update values applied to RigidBody later (must happen in FixedUpdate).
|
||||
if (usesRigidbody) {
|
||||
rigidbodyDisplacement += transform.TransformVector(skeletonTranslationDelta);
|
||||
if (skeletonRotationDelta != 0.0f) {
|
||||
if (rigidBody != null) {
|
||||
Quaternion addedWorldRotation = Quaternion.Euler(0, 0, skeletonRotationDelta);
|
||||
rigidbodyLocalRotation = rigidbodyLocalRotation * addedWorldRotation;
|
||||
} else if (rigidBody2D != null) {
|
||||
Vector3 lossyScale = transform.lossyScale;
|
||||
float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
|
||||
rigidbody2DRotation += rotationSign * skeletonRotationDelta;
|
||||
}
|
||||
}
|
||||
} else if (applyToTransform) {
|
||||
transform.position += transform.TransformVector(skeletonTranslationDelta);
|
||||
if (skeletonRotationDelta != 0.0f) {
|
||||
Vector3 lossyScale = transform.lossyScale;
|
||||
float rotationSign = lossyScale.x * lossyScale.y > 0 ? 1 : -1;
|
||||
transform.Rotate(0, 0, rotationSign * skeletonRotationDelta);
|
||||
}
|
||||
}
|
||||
|
||||
tempSkeletonDisplacement += skeletonTranslationDelta;
|
||||
tempSkeletonRotation += skeletonRotationDelta;
|
||||
if (accumulatedUntilFixedUpdate) {
|
||||
SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, tempSkeletonRotation, parentBoneScale);
|
||||
} else {
|
||||
ClearEffectiveBoneOffsets(parentBoneScale);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyTransformConstraints () {
|
||||
rootMotionBone.AppliedPose.X = rootMotionBone.Pose.X;
|
||||
rootMotionBone.AppliedPose.Y = rootMotionBone.Pose.Y;
|
||||
rootMotionBone.AppliedPose.Rotation = rootMotionBone.Pose.Rotation;
|
||||
IConstraint[] transformConstraintsItems = animationComponent.Skeleton.Constraints.Items;
|
||||
foreach (int constraintIndex in this.transformConstraintIndices) {
|
||||
TransformConstraint constraint = (TransformConstraint)transformConstraintsItems[constraintIndex];
|
||||
// apply the constraint and sets Bone.ax, Bone.ay and Bone.arotation values.
|
||||
/// Update is based on Bone.x, Bone.y and Bone.rotation, so skeleton.UpdateWorldTransform()
|
||||
/// can be called afterwards without having a different starting point.
|
||||
constraint.Update(animationComponent.Skeleton, Physics.None);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetScaleAffectingRootMotion () {
|
||||
Vector2 parentBoneScale;
|
||||
return GetScaleAffectingRootMotion(out parentBoneScale);
|
||||
}
|
||||
|
||||
Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) {
|
||||
Skeleton skeleton = animationComponent.Skeleton;
|
||||
Vector2 totalScale = Vector2.one;
|
||||
totalScale.x *= skeleton.ScaleX;
|
||||
totalScale.y *= skeleton.ScaleY;
|
||||
|
||||
parentBoneScale = Vector2.one;
|
||||
Bone scaleBone = rootMotionBone;
|
||||
while ((scaleBone = scaleBone.Parent) != null) {
|
||||
#if USE_APPLIED_PARENT_SCALE
|
||||
parentBoneScale.x *= scaleBone.AppliedPose.ScaleX;
|
||||
parentBoneScale.y *= scaleBone.AppliedPose.ScaleY;
|
||||
#else
|
||||
parentBoneScale.x *= scaleBone.Pose.ScaleX;
|
||||
parentBoneScale.y *= scaleBone.Pose.ScaleY;
|
||||
#endif
|
||||
}
|
||||
totalScale = Vector2.Scale(totalScale, parentBoneScale);
|
||||
totalScale *= AdditionalScale;
|
||||
return totalScale;
|
||||
}
|
||||
|
||||
Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale, out Vector2 totalScale) {
|
||||
Vector2 skeletonDelta = boneLocalDelta;
|
||||
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;
|
||||
}
|
||||
|
||||
float GetSkeletonSpaceRotationDelta (float boneLocalDelta, Vector2 totalScaleAffectingRootMotion) {
|
||||
float rotationSign = totalScaleAffectingRootMotion.x * totalScaleAffectingRootMotion.y > 0 ? 1 : -1;
|
||||
return rotationSign * boneLocalDelta;
|
||||
}
|
||||
|
||||
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, float rotationSkeletonSpace, Vector2 parentBoneScale) {
|
||||
|
||||
ApplyTransformConstraints();
|
||||
|
||||
// Move top level bones in opposite direction of the root motion bone
|
||||
Skeleton skeleton = animationComponent.Skeleton;
|
||||
foreach (Bone topLevelBone in topLevelBones) {
|
||||
if (topLevelBone == rootMotionBone) {
|
||||
if (transformPositionX) topLevelBone.Pose.X = displacementSkeletonSpace.x / skeleton.ScaleX;
|
||||
if (transformPositionY) topLevelBone.Pose.Y = displacementSkeletonSpace.y / skeleton.ScaleY;
|
||||
if (transformRotation) {
|
||||
float rotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
|
||||
topLevelBone.Pose.Rotation = rotationSign * rotationSkeletonSpace;
|
||||
}
|
||||
} else {
|
||||
bool useAppliedTransform = transformConstraintIndices.Count > 0;
|
||||
float rootMotionBoneX = useAppliedTransform ? rootMotionBone.AppliedPose.X : rootMotionBone.Pose.X;
|
||||
float rootMotionBoneY = useAppliedTransform ? rootMotionBone.AppliedPose.Y : rootMotionBone.Pose.Y;
|
||||
|
||||
float offsetX = (initialOffset.x - rootMotionBoneX) * parentBoneScale.x;
|
||||
float offsetY = (initialOffset.y - rootMotionBoneY) * parentBoneScale.y;
|
||||
|
||||
if (transformPositionX) topLevelBone.Pose.X = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
|
||||
if (transformPositionY) topLevelBone.Pose.Y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
|
||||
|
||||
if (transformRotation) {
|
||||
float rootMotionBoneRotation = useAppliedTransform ? rootMotionBone.AppliedPose.Rotation : rootMotionBone.Pose.Rotation;
|
||||
|
||||
float parentBoneRotationSign = (parentBoneScale.x * parentBoneScale.y > 0 ? 1 : -1);
|
||||
float offsetRotation = (initialOffsetRotation - rootMotionBoneRotation) * parentBoneRotationSign;
|
||||
|
||||
float skeletonRotationSign = skeleton.ScaleX * skeleton.ScaleY > 0 ? 1 : -1;
|
||||
topLevelBone.Pose.Rotation = (rotationSkeletonSpace * skeletonRotationSign) + offsetRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
|
||||
SetEffectiveBoneOffsetsTo(Vector2.zero, 0, parentBoneScale);
|
||||
}
|
||||
|
||||
void ClearRigidbodyTempMovement () {
|
||||
rigidbodyDisplacement = Vector2.zero;
|
||||
tempSkeletonDisplacement = Vector2.zero;
|
||||
rigidbodyLocalRotation = Quaternion.identity;
|
||||
rigidbody2DRotation = 0;
|
||||
tempSkeletonRotation = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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,359 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2017_1_OR_NEWER
|
||||
#define BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif
|
||||
|
||||
#if !SPINE_DISABLE_THREADING
|
||||
#define USE_THREADED_ANIMATION_UPDATE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
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 : SkeletonAnimationBase, IAnimationStateComponent, IUpgradable {
|
||||
|
||||
#region Serialized state and Beginner API
|
||||
[FormerlySerializedAs("_animationName")] [SerializeField] [SpineAnimation] protected string animationName = "";
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>If enabled, AnimationState time is advanced by Unscaled Game Time
|
||||
/// (<c>Time.unscaledDeltaTime</c> instead of the default Game Time(<c>Time.deltaTime</c>).
|
||||
/// to animate independent of game <c>Time.timeScale</c>.
|
||||
/// Instance timeScale will still be applied.</summary>
|
||||
public bool unscaledTime;
|
||||
#endregion
|
||||
|
||||
public override void MainThreadBeforeUpdateInternal () {
|
||||
base.MainThreadBeforeUpdateInternal();
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (isUpdatedExternally) {
|
||||
if (state != null) state.DelayListenerNotifications();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void MainThreadAfterUpdateInternal () {
|
||||
base.MainThreadAfterUpdateInternal();
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
if (isUpdatedExternally) {
|
||||
if (state != null) state.IssueDelayedListenerNotifications();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected 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 state;
|
||||
}
|
||||
set { state = value; }
|
||||
}
|
||||
|
||||
public override bool IsValid {
|
||||
get { return skeletonRenderer != null && skeletonRenderer.IsValid && state != null; }
|
||||
}
|
||||
|
||||
public bool UnscaledTime { get { return unscaledTime; } set { unscaledTime = value; } }
|
||||
|
||||
#region Serialized state and Beginner API
|
||||
/// <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 (!this.IsValid) {
|
||||
return animationName;
|
||||
} else {
|
||||
TrackEntry entry = state.GetTrack(0);
|
||||
return entry == null ? null : entry.Animation.Name;
|
||||
}
|
||||
}
|
||||
set {
|
||||
Initialize(false);
|
||||
if (!IsValid) {
|
||||
animationName = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationName == value) {
|
||||
TrackEntry entry = state.GetTrack(0);
|
||||
if (entry != null && entry.Loop == loop)
|
||||
return;
|
||||
}
|
||||
animationName = value;
|
||||
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
state.ClearTrack(0);
|
||||
} else {
|
||||
SkeletonData skeletonData = skeletonRenderer.SkeletonDataAsset.GetSkeletonData(false);
|
||||
if (skeletonData == null)
|
||||
return;
|
||||
Spine.Animation animationObject = skeletonData.FindAnimation(value);
|
||||
if (animationObject != null)
|
||||
state.SetAnimation(0, animationObject, loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh, resets the skeleton's pose, and clears all previously active animations.</summary>
|
||||
public override void ClearAnimationState () {
|
||||
if (state != null) state.ClearTracks();
|
||||
}
|
||||
|
||||
public override void InitializeAnimationComponent () {
|
||||
base.InitializeAnimationComponent();
|
||||
if (!skeletonRenderer.IsValid)
|
||||
return;
|
||||
|
||||
AnimationStateData data = skeletonRenderer.SkeletonDataAsset.GetAnimationStateData();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
AnimationState oldAnimationState = state;
|
||||
#endif
|
||||
state = new Spine.AnimationState(data);
|
||||
state.Dispose += OnAnimationDisposed;
|
||||
if (state == null)
|
||||
return;
|
||||
#if UNITY_EDITOR
|
||||
if (oldAnimationState != null)
|
||||
state.AssignEventSubscribersFrom(oldAnimationState);
|
||||
#endif
|
||||
UpdateInitialAnimation();
|
||||
}
|
||||
|
||||
protected virtual void OnAnimationDisposed (TrackEntry entry) {
|
||||
// when updateMode disables applying animations, still ensure animations are mixed out
|
||||
UpdateMode updateMode = skeletonRenderer.UpdateMode;
|
||||
if (updateMode != UpdateMode.FullUpdate &&
|
||||
updateMode != UpdateMode.EverythingExceptMesh) {
|
||||
entry.Animation.Apply(skeleton, 0, 0, false, null, 0f, true, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateInitialAnimation () {
|
||||
state.ClearTrack(0);
|
||||
if (!string.IsNullOrEmpty(animationName)) {
|
||||
SkeletonData skeletonData = skeletonRenderer.SkeletonDataAsset.GetSkeletonData(false);
|
||||
if (skeletonData == null)
|
||||
return;
|
||||
Spine.Animation animation = skeletonData.FindAnimation(animationName);
|
||||
if (animation != null) {
|
||||
state.SetAnimation(0, animation, loop);
|
||||
#if UNITY_EDITOR
|
||||
if (!ApplicationIsPlaying)
|
||||
Update(0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_THREADED_ANIMATION_UPDATE
|
||||
public override float UsedExternalDeltaTime {
|
||||
get {
|
||||
return unscaledTime ? ExternalUnscaledDeltaTime : ExternalDeltaTime;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
protected override float DeltaTime {
|
||||
get {
|
||||
return unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateAnimationStatus (float deltaTime) {
|
||||
deltaTime *= timeScale;
|
||||
if (state != null) {
|
||||
state.Update(deltaTime);
|
||||
skeleton.Update(deltaTime);
|
||||
#if UNITY_EDITOR
|
||||
if (ApplicationIsPlaying)
|
||||
UpdatePropertyToCurrentAnimationEditor();
|
||||
#endif
|
||||
if (skeletonRenderer.UpdateMode == UpdateMode.OnlyAnimationStatus) {
|
||||
state.ApplyEventTimelinesOnly(skeleton, issueEvents: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyStateToSkeleton (bool calledFromMainThread) {
|
||||
if (skeletonRenderer.UpdateMode != UpdateMode.OnlyEventTimelines)
|
||||
state.Apply(skeletonRenderer.Skeleton);
|
||||
else
|
||||
state.ApplyEventTimelinesOnly(skeletonRenderer.Skeleton, issueEvents: true);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected void UpdatePropertyToCurrentAnimationEditor () {
|
||||
if (state.Tracks.Count == 0 || state.Tracks.Items[0] == null)
|
||||
return;
|
||||
Animation currentAnimation = state.Tracks.Items[0].Animation;
|
||||
animationName = currentAnimation == null ? "<None>" : currentAnimation.Name;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>Adds and prepares SkeletonAnimation and SkeletonRenderer components to a GameObject at runtime.</summary>
|
||||
/// <returns>A struct referencing the newly instantiated SkeletonAnimation and SkeletonRenderer components.</returns>
|
||||
public static SkeletonComponents<SkeletonRenderer, SkeletonAnimation> AddToGameObject (
|
||||
GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false) {
|
||||
|
||||
return Spine.Unity.SkeletonRenderer.AddSpineComponents<SkeletonRenderer, SkeletonAnimation>(
|
||||
gameObject, skeletonDataAsset, quiet);
|
||||
}
|
||||
|
||||
/// <summary>Instantiates a new UnityEngine.GameObject and adds SkeletonAnimation and SkeletonRenderer components to it.</summary>
|
||||
/// <returns>A struct referencing the newly instantiated SkeletonAnimation and SkeletonRenderer components.</returns>
|
||||
public static SkeletonComponents<SkeletonRenderer, SkeletonAnimation> NewSkeletonAnimationGameObject (SkeletonDataAsset skeletonDataAsset,
|
||||
bool quiet = false) {
|
||||
return Spine.Unity.SkeletonRenderer.NewSpineGameObject<SkeletonRenderer, SkeletonAnimation>(
|
||||
skeletonDataAsset, quiet);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
// compatibility layer between 4.1 and 4.2, automatically transfer serialized attributes.
|
||||
public override void UpgradeTo43 () {
|
||||
if (!Application.isPlaying && !wasDeprecatedTransferred) {
|
||||
UpgradeTo43Components();
|
||||
TransferDeprecatedFields();
|
||||
InitializeAnimationComponent();
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpgradeTo43Components () {
|
||||
if (gameObject.GetComponent<SkeletonRenderer>() == null &&
|
||||
gameObject.GetComponent<SkeletonGraphic>() == null) {
|
||||
gameObject.AddComponent<SkeletonRenderer>();
|
||||
EditorBridge.RequestMarkDirty(gameObject);
|
||||
Debug.Log(string.Format("{0}: Auto-migrated old SkeletonAnimation component to split SkeletonAnimation + SkeletonRenderer components.",
|
||||
gameObject.name), gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Transfer of former base class SkeletonRenderer parameters.</summary>
|
||||
protected void TransferDeprecatedFields () {
|
||||
wasDeprecatedTransferred = true;
|
||||
|
||||
SkeletonRenderer skeletonRenderer = gameObject.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
return;
|
||||
|
||||
skeletonRenderer.skeletonDataAsset = this.skeletonDataAssetDeprecated;
|
||||
skeletonRenderer.initialSkinName = this.initialSkinNameDeprecated;
|
||||
skeletonRenderer.EditorSkipSkinSync = this.editorSkipSkinSyncDeprecated;
|
||||
skeletonRenderer.initialFlipX = this.initialFlipXDeprecated;
|
||||
skeletonRenderer.initialFlipY = this.initialFlipYDeprecated;
|
||||
skeletonRenderer.UpdateMode = this.updateModeDeprecated;
|
||||
skeletonRenderer.updateWhenInvisible = this.updateWhenInvisibleDeprecated;
|
||||
skeletonRenderer.separatorSlotNames = this.separatorSlotNamesDeprecated;
|
||||
|
||||
skeletonRenderer.MeshSettings.zSpacing = this.zSpacingDeprecated;
|
||||
skeletonRenderer.MeshSettings.useClipping = this.useClippingDeprecated;
|
||||
skeletonRenderer.MeshSettings.immutableTriangles = this.immutableTrianglesDeprecated;
|
||||
skeletonRenderer.MeshSettings.pmaVertexColors = this.pmaVertexColorsDeprecated;
|
||||
skeletonRenderer.MeshSettings.tintBlack = this.tintBlackDeprecated;
|
||||
skeletonRenderer.MeshSettings.addNormals = this.addNormalsDeprecated;
|
||||
skeletonRenderer.MeshSettings.calculateTangents = this.calculateTangentsDeprecated;
|
||||
|
||||
skeletonRenderer.clearStateOnDisable = this.clearStateOnDisableDeprecated;
|
||||
skeletonRenderer.singleSubmesh = this.singleSubmeshDeprecated;
|
||||
skeletonRenderer.MaskInteraction = this.maskInteractionDeprecated;
|
||||
}
|
||||
|
||||
[SerializeField] protected bool wasDeprecatedTransferred = false;
|
||||
// SkeletonRenderer former base class parameters
|
||||
[FormerlySerializedAs("skeletonDataAsset")] [SerializeField] private SkeletonDataAsset skeletonDataAssetDeprecated;
|
||||
|
||||
[FormerlySerializedAs("initialSkinName")] [SpineSkin(defaultAsEmptyString: true)] [SerializeField] private string initialSkinNameDeprecated;
|
||||
[FormerlySerializedAs("editorSkipSkinSync")] [SerializeField] private bool editorSkipSkinSyncDeprecated = false;
|
||||
[FormerlySerializedAs("initialFlipX")] [SerializeField] private bool initialFlipXDeprecated = false;
|
||||
[FormerlySerializedAs("initialFlipY")] [SerializeField] private bool initialFlipYDeprecated = false;
|
||||
[FormerlySerializedAs("updateMode")] [SerializeField] private UpdateMode updateModeDeprecated = UpdateMode.FullUpdate;
|
||||
[FormerlySerializedAs("updateWhenInvisible")] [SerializeField] private UpdateMode updateWhenInvisibleDeprecated = UpdateMode.FullUpdate;
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators"),
|
||||
UnityEngine.Serialization.FormerlySerializedAs("separatorSlotNames")]
|
||||
[SerializeField] private string[] separatorSlotNamesDeprecated = new string[0];
|
||||
|
||||
[FormerlySerializedAs("zSpacing")] [SerializeField] private float zSpacingDeprecated = 0f;
|
||||
[FormerlySerializedAs("useClipping")] [SerializeField] private bool useClippingDeprecated = true;
|
||||
[FormerlySerializedAs("immutableTriangles")] [SerializeField] private bool immutableTrianglesDeprecated = false;
|
||||
[FormerlySerializedAs("pmaVertexColors")] [SerializeField] private bool pmaVertexColorsDeprecated = true;
|
||||
[FormerlySerializedAs("clearStateOnDisable")] [SerializeField] private bool clearStateOnDisableDeprecated = false;
|
||||
[FormerlySerializedAs("tintBlack")] [SerializeField] private bool tintBlackDeprecated = false;
|
||||
[FormerlySerializedAs("singleSubmesh")] [SerializeField] private bool singleSubmeshDeprecated = false;
|
||||
[FormerlySerializedAs("calculateNormals"),
|
||||
FormerlySerializedAs("addNormals")]
|
||||
[SerializeField] private bool addNormalsDeprecated = false;
|
||||
[FormerlySerializedAs("calculateTangents")] [SerializeField] private bool calculateTangentsDeprecated = false;
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
[FormerlySerializedAs("maskInteraction")] [SerializeField] private SpriteMaskInteraction maskInteractionDeprecated = SpriteMaskInteraction.None;
|
||||
#endif // BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif // UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d247ba06193faa74d9335f5481b2b56c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,755 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2019_3_OR_NEWER
|
||||
#define CONFIGURABLE_ENTER_PLAY_MODE
|
||||
#endif
|
||||
|
||||
#if !SPINE_DISABLE_THREADING
|
||||
#define USE_THREADED_SKELETON_UPDATE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
#define SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
// Partial class: covers common identical attributes, properties and methods shared across all
|
||||
// ISkeletonRenderer subclasses. This is a workaround for single inheritance limitations and covers code which
|
||||
// would otherwise be located in a base-class of SkeletonGraphic.
|
||||
public partial class SkeletonGraphic : MaskableGraphic, ISkeletonRenderer {
|
||||
|
||||
// Identical code shared by ISkeletonRenderer subclasses as a workaround for single inheritance limitations.
|
||||
#region Identical common ISkeletonRenderer code
|
||||
#region ISkeletonRenderer Attributes
|
||||
// Core Attributes
|
||||
public ISkeletonAnimation skeletonAnimation;
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
[System.NonSerialized] public Skeleton skeleton;
|
||||
[System.NonSerialized] public bool valid;
|
||||
|
||||
protected bool wasMeshUpdatedAfterInit = false;
|
||||
protected bool updateTriangles = true;
|
||||
|
||||
// Initialization Settings
|
||||
/// <summary>Skin name to use when the Skeleton is initialized.</summary>
|
||||
[SpineSkin(dataField: "skeletonDataAsset", defaultAsEmptyString: true)] public string initialSkinName;
|
||||
/// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
|
||||
public bool initialFlipX, initialFlipY;
|
||||
|
||||
// Render Settings
|
||||
[SerializeField] protected MeshGenerator.Settings meshSettings = MeshGenerator.Settings.Default;
|
||||
[System.NonSerialized] protected readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
|
||||
|
||||
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
|
||||
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;
|
||||
/// <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;
|
||||
|
||||
// Submesh Separation
|
||||
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
|
||||
[SpineSlot] public 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;
|
||||
|
||||
// Overrides Attributes
|
||||
// These are API for anything that wants to take over rendering for a SkeletonRenderer.
|
||||
public bool disableRenderingOnOverride = true;
|
||||
event InstructionDelegate generateMeshOverride;
|
||||
[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
|
||||
[System.NonSerialized] protected bool materialsNeedUpdate = false;
|
||||
|
||||
// Physics Attributes
|
||||
/// <seealso cref="PhysicsPositionInheritanceFactor"/>
|
||||
[SerializeField] protected Vector2 physicsPositionInheritanceFactor = Vector2.one;
|
||||
/// <seealso cref="PhysicsRotationInheritanceFactor"/>
|
||||
[SerializeField] protected float physicsRotationInheritanceFactor = 1.0f;
|
||||
/// <seealso cref="PhysicsPositionInheritanceLimit"/>
|
||||
[SerializeField] protected Vector2 physicsPositionInheritanceLimit = Vector2.positiveInfinity;
|
||||
/// <seealso cref="PhysicsRotationInheritanceLimit"/>
|
||||
[SerializeField] protected float physicsRotationInheritanceLimit = float.MaxValue;
|
||||
/// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
|
||||
[SerializeField] protected Transform physicsMovementRelativeTo = null;
|
||||
|
||||
/// <summary>Used for applying Transform translation to skeleton PhysicsConstraints.</summary>
|
||||
protected Vector3 lastPosition;
|
||||
/// <summary>Used for applying Transform rotation to skeleton PhysicsConstraints.</summary>
|
||||
protected float lastRotation;
|
||||
/// <summary>Position delta for threaded processing as Transform access from worker thread is not allowed.</summary>
|
||||
protected Vector3 positionDelta;
|
||||
/// <summary>Rotation delta for threaded processing as Transform access from worker thread is not allowed.</summary>
|
||||
protected float rotationDelta;
|
||||
|
||||
// Threaded Update System Attributes
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
protected static int mainThreadID = -1;
|
||||
|
||||
[SerializeField] protected SettingsTriState threadedMeshGeneration = SettingsTriState.UseGlobalSetting;
|
||||
protected bool isUpdatedExternally = false;
|
||||
protected bool requiresMeshBufferAssignmentMainThread = false;
|
||||
#if UNITY_EDITOR
|
||||
static bool applicationIsPlaying = false;
|
||||
#endif
|
||||
#endif // USE_THREADED_SKELETON_UPDATE
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <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>
|
||||
protected bool editorSkipSkinSync = false;
|
||||
protected bool requiresEditorUpdate = false;
|
||||
#endif
|
||||
#endregion ISkeletonRenderer Attributes
|
||||
|
||||
#region ISkeletonRenderer Properties
|
||||
public ISkeletonRenderer Renderer { get { return this; } }
|
||||
public UnityEngine.MonoBehaviour Component { get { return this; } }
|
||||
public ISkeletonAnimation Animation { get { return skeletonAnimation; } set { skeletonAnimation = value; } }
|
||||
public SkeletonDataAsset SkeletonDataAsset {
|
||||
get { return skeletonDataAsset; }
|
||||
set { skeletonDataAsset = value; }
|
||||
}
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton;
|
||||
}
|
||||
set {
|
||||
skeleton = value;
|
||||
}
|
||||
}
|
||||
public SkeletonData SkeletonData {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton == null ? null : skeleton.Data;
|
||||
}
|
||||
}
|
||||
public bool IsValid { get { return valid; } }
|
||||
public string InitialSkinName { get { return initialSkinName; } set { initialSkinName = value; } }
|
||||
/// <summary>Flip X to use when the Skeleton is initialized.</summary>
|
||||
public bool InitialFlipX { get { return initialFlipX; } set { initialFlipX = value; } }
|
||||
/// <summary>Flip Y to use when the Skeleton is initialized.</summary>
|
||||
public bool InitialFlipY { get { return initialFlipY; } set { initialFlipY = value; } }
|
||||
|
||||
/// <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; } }
|
||||
/// <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 { get { return updateWhenInvisible; } set { updateWhenInvisible = value; } }
|
||||
|
||||
public MeshGenerator.Settings MeshSettings { get { return meshSettings; } set { meshSettings = value; } }
|
||||
|
||||
// Submesh Separation
|
||||
/// <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>
|
||||
public List<Slot> SeparatorSlots { get { return separatorSlots; } }
|
||||
public bool EnableSeparatorSlots { get { return enableSeparatorSlots; } set { enableSeparatorSlots = value; } }
|
||||
|
||||
// Overrides Properties
|
||||
public bool HasGenerateMeshOverride { get { return generateMeshOverride != null; } }
|
||||
|
||||
/// <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);
|
||||
DisableRenderers();
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
}
|
||||
}
|
||||
remove {
|
||||
generateMeshOverride -= value;
|
||||
if (disableRenderingOnOverride && generateMeshOverride == null) {
|
||||
Initialize(false);
|
||||
EnableRenderers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
|
||||
public Dictionary<Slot, Material> CustomSlotMaterials {
|
||||
get { materialsNeedUpdate = true; return customSlotMaterials; }
|
||||
}
|
||||
|
||||
// Physics Properties
|
||||
/// <summary>When set to non-zero, Transform position movement in X and Y direction
|
||||
/// is applied to skeleton PhysicsConstraints, multiplied by this scale factor.
|
||||
/// Typical values are <c>Vector2.one</c> to apply XY movement 1:1,
|
||||
/// <c>Vector2(2f, 2f)</c> to apply movement with double intensity,
|
||||
/// <c>Vector2(1f, 0f)</c> to apply only horizontal movement, or
|
||||
/// <c>Vector2.zero</c> to not apply any Transform position movement at all.</summary>
|
||||
public Vector2 PhysicsPositionInheritanceFactor {
|
||||
get {
|
||||
return physicsPositionInheritanceFactor;
|
||||
}
|
||||
set {
|
||||
if (physicsPositionInheritanceFactor == Vector2.zero && value != Vector2.zero) ResetLastPosition();
|
||||
physicsPositionInheritanceFactor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>When set to non-zero, Transform rotation movement is applied to skeleton PhysicsConstraints,
|
||||
/// multiplied by this scale factor. Typical values are <c>1</c> to apply movement 1:1,
|
||||
/// <c>2</c> to apply movement with double intensity, or
|
||||
/// <c>0</c> to not apply any Transform rotation movement at all.</summary>
|
||||
public float PhysicsRotationInheritanceFactor {
|
||||
get {
|
||||
return physicsRotationInheritanceFactor;
|
||||
}
|
||||
set {
|
||||
if (physicsRotationInheritanceFactor == 0f && value != 0f) ResetLastRotation();
|
||||
physicsRotationInheritanceFactor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limits Transform position movement in X and Y direction that is applied to skeleton PhysicsConstraints,
|
||||
/// after it has been multiplied by <see cref="PhysicsPositionInheritanceFactor"/>.
|
||||
/// </summary>
|
||||
public Vector2 PhysicsPositionInheritanceLimit {
|
||||
get { return physicsPositionInheritanceLimit; }
|
||||
set { physicsPositionInheritanceLimit = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limits Transform rotation movement that is applied to skeleton PhysicsConstraints,
|
||||
/// after it has been multiplied by <see cref="PhysicsRotationInheritanceFactor"/>.
|
||||
/// </summary>
|
||||
public float PhysicsRotationInheritanceLimit {
|
||||
get { return physicsRotationInheritanceLimit; }
|
||||
set { physicsRotationInheritanceLimit = value; }
|
||||
}
|
||||
|
||||
/// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
|
||||
public Transform PhysicsMovementRelativeTo {
|
||||
get {
|
||||
return physicsMovementRelativeTo;
|
||||
}
|
||||
set {
|
||||
physicsMovementRelativeTo = value;
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) ResetLastPosition();
|
||||
if (physicsRotationInheritanceFactor != 0f) ResetLastRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// Threaded Update System Properties
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
public bool IsUpdatedExternally {
|
||||
get { return isUpdatedExternally; }
|
||||
set { isUpdatedExternally = value; }
|
||||
}
|
||||
|
||||
protected bool UsesThreadedMeshGeneration {
|
||||
get { return threadedMeshGeneration == SettingsTriState.Enable || RuntimeSettings.UseThreadedMeshGeneration; }
|
||||
}
|
||||
|
||||
public bool RequiresMeshBufferAssignmentMainThread {
|
||||
get { return requiresMeshBufferAssignmentMainThread; }
|
||||
}
|
||||
|
||||
public SettingsTriState ThreadedMeshGeneration {
|
||||
get { return threadedMeshGeneration; }
|
||||
set {
|
||||
if (threadedMeshGeneration == value) return;
|
||||
threadedMeshGeneration = value;
|
||||
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system) {
|
||||
if (threadedMeshGeneration == SettingsTriState.Enable || RuntimeSettings.UseThreadedMeshGeneration)
|
||||
system.RegisterForUpdate(this);
|
||||
else
|
||||
system.UnregisterFromUpdate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// ApplicationIsPlaying for threaded access. Unfortunately Application.isPlaying throws
|
||||
// when called from worker thread.
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return applicationIsPlaying; }
|
||||
set { applicationIsPlaying = value; }
|
||||
}
|
||||
#endif
|
||||
#else // USE_THREADED_SKELETON_UPDATE
|
||||
public bool IsUpdatedExternally {
|
||||
get { return false; }
|
||||
set { }
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return Application.isPlaying; }
|
||||
set { }
|
||||
}
|
||||
#endif
|
||||
public bool RequiresMeshBufferAssignmentMainThread { get { return true; } }
|
||||
#endif // USE_THREADED_SKELETON_UPDATE
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <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>
|
||||
public bool EditorSkipSkinSync {
|
||||
get { return editorSkipSkinSync; }
|
||||
set { editorSkipSkinSync = value; }
|
||||
}
|
||||
#endif
|
||||
#endregion ISkeletonRenderer Properties
|
||||
|
||||
#region ISkeletonRenderer Methods
|
||||
protected virtual void ClearCommon () {
|
||||
// Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
|
||||
// otherwise constant reloading will be triggered at prefabs.
|
||||
currentInstructions.Clear();
|
||||
rendererBuffers.Clear();
|
||||
ClearMeshGenerator();
|
||||
skeleton = null;
|
||||
valid = false;
|
||||
|
||||
if (skeletonAnimation != null)
|
||||
skeletonAnimation.ClearAnimationState();
|
||||
}
|
||||
|
||||
protected virtual void InitializeCommon (bool overwrite, bool quiet = false) {
|
||||
if (valid && !overwrite)
|
||||
return;
|
||||
|
||||
if (skeletonDataAsset == null || skeletonDataAsset.skeletonJSON == null) {
|
||||
Clear();
|
||||
return;
|
||||
} else if (skeleton != null && skeletonDataAsset.GetSkeletonData(true) != skeleton.Data) {
|
||||
Clear();
|
||||
} else if (this.Freeze) {
|
||||
return;
|
||||
} else {
|
||||
ClearCommon();
|
||||
}
|
||||
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(quiet);
|
||||
if (skeletonData == null) return;
|
||||
|
||||
rendererBuffers.Initialize();
|
||||
|
||||
skeleton = new Skeleton(skeletonData) {
|
||||
ScaleX = initialFlipX ? -1 : 1,
|
||||
ScaleY = initialFlipY ? -1 : 1
|
||||
};
|
||||
valid = true;
|
||||
|
||||
ResetLastPositionAndRotation();
|
||||
AssignInitialSkin();
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0; i < separatorSlotNames.Length; i++)
|
||||
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
|
||||
|
||||
AfterAnimationApplied();
|
||||
wasMeshUpdatedAfterInit = false;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
protected virtual void AssignInitialSkin () {
|
||||
if (string.IsNullOrEmpty(initialSkinName) || string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
|
||||
skeleton.SetSkin((Skin)null);
|
||||
else
|
||||
skeleton.SetSkin(initialSkinName);
|
||||
}
|
||||
|
||||
public void ResetLastPosition () {
|
||||
lastPosition = GetPhysicsTransformPosition();
|
||||
}
|
||||
|
||||
public void ResetLastRotation () {
|
||||
lastRotation = GetPhysicsTransformRotation();
|
||||
}
|
||||
|
||||
public void ResetLastPositionAndRotation () {
|
||||
lastPosition = GetPhysicsTransformPosition();
|
||||
lastRotation = GetPhysicsTransformRotation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers Transform movement for later application in <see cref="ApplyTransformMovementToPhysics"/>.
|
||||
/// Must be called in main thread.
|
||||
/// </summary>
|
||||
public virtual void GatherTransformMovementForPhysics () {
|
||||
#if UNITY_EDITOR
|
||||
bool isPlaying = ApplicationIsPlaying;
|
||||
#else
|
||||
bool isPlaying = true;
|
||||
#endif
|
||||
if (isPlaying) {
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) {
|
||||
Vector3 position = GetPhysicsTransformPosition();
|
||||
positionDelta = (position - lastPosition) / this.MeshScale;
|
||||
|
||||
positionDelta = transform.InverseTransformVector(positionDelta);
|
||||
if (physicsMovementRelativeTo != null) {
|
||||
positionDelta = physicsMovementRelativeTo.TransformVector(positionDelta);
|
||||
}
|
||||
positionDelta.x *= physicsPositionInheritanceFactor.x;
|
||||
positionDelta.y *= physicsPositionInheritanceFactor.y;
|
||||
positionDelta.x = Mathf.Clamp(positionDelta.x, -physicsPositionInheritanceLimit.x, physicsPositionInheritanceLimit.x);
|
||||
positionDelta.y = Mathf.Clamp(positionDelta.y, -physicsPositionInheritanceLimit.y, physicsPositionInheritanceLimit.y);
|
||||
lastPosition = position;
|
||||
}
|
||||
if (physicsRotationInheritanceFactor != 0f) {
|
||||
float rotation = GetPhysicsTransformRotation();
|
||||
rotationDelta = rotation - lastRotation;
|
||||
if (rotationDelta > 180f) rotationDelta -= 360f;
|
||||
else if (rotationDelta < -180f) rotationDelta += 360f;
|
||||
rotationDelta = Mathf.Clamp(rotationDelta, -physicsRotationInheritanceLimit, physicsRotationInheritanceLimit);
|
||||
lastRotation = rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies position and rotation Transform movement previously gathered via
|
||||
/// <see cref="GatherTransformMovementForPhysics"/>. May be called in worker thread.
|
||||
/// </summary>
|
||||
public virtual void ApplyTransformMovementToPhysics () {
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) {
|
||||
skeleton.PhysicsTranslate(positionDelta.x, positionDelta.y);
|
||||
}
|
||||
if (physicsRotationInheritanceFactor != 0f) {
|
||||
skeleton.PhysicsRotate(0, 0, physicsRotationInheritanceFactor * rotationDelta);
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector3 GetPhysicsTransformPosition () {
|
||||
if (physicsMovementRelativeTo == null) {
|
||||
return transform.position;
|
||||
} else {
|
||||
if (physicsMovementRelativeTo == transform.parent)
|
||||
return transform.localPosition;
|
||||
else
|
||||
return physicsMovementRelativeTo.InverseTransformPoint(transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
protected float GetPhysicsTransformRotation () {
|
||||
if (physicsMovementRelativeTo == null) {
|
||||
return this.transform.rotation.eulerAngles.z;
|
||||
} else {
|
||||
if (physicsMovementRelativeTo == this.transform.parent)
|
||||
return this.transform.localRotation.eulerAngles.z;
|
||||
else {
|
||||
Quaternion relative = Quaternion.Inverse(physicsMovementRelativeTo.rotation) * this.transform.rotation;
|
||||
return relative.eulerAngles.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AfterAnimationApplied (bool calledFromMainThread = true) {
|
||||
if (_UpdateLocal != null)
|
||||
_UpdateLocal(this);
|
||||
|
||||
if (_UpdateWorld == null) {
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
} else {
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
_UpdateWorld(this);
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
}
|
||||
|
||||
if (calledFromMainThread && _UpdateComplete != null) {
|
||||
_UpdateComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
public CoroutineIterator AfterAnimationAppliedSplit (CoroutineIterator coroutineIterator) {
|
||||
if (coroutineIterator.IsDone)
|
||||
return CoroutineIterator.Done;
|
||||
|
||||
/*
|
||||
0:
|
||||
if (_UpdateLocal != null) {
|
||||
yield return true; // continue in main thread
|
||||
1:
|
||||
_UpdateLocal(this);
|
||||
yield return false; // continue in worker thread
|
||||
}
|
||||
|
||||
2:
|
||||
if (_UpdateWorld == null) {
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
// goto 5
|
||||
} else {
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
yield return true; // continue in main thread
|
||||
3:
|
||||
_UpdateWorld(this);
|
||||
yield return false; // continue in worker thread
|
||||
4:
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
// goto 5
|
||||
}
|
||||
|
||||
5:
|
||||
if (_UpdateComplete != null) {
|
||||
yield return true; // continue in main thread
|
||||
6:
|
||||
_UpdateComplete(this);
|
||||
// last call, no need to switch back to worker thread.
|
||||
}
|
||||
*/
|
||||
const int StateBits = 3;
|
||||
const uint StateMask = (1 << StateBits) - 1;
|
||||
switch (coroutineIterator.State(StateMask)) {
|
||||
case 0:
|
||||
if (_UpdateLocal != null) {
|
||||
AssertIsWorkerThread();
|
||||
return coroutineIterator.YieldReturnAtState(1, StateMask);
|
||||
} else {
|
||||
goto case 2;
|
||||
}
|
||||
case 1:
|
||||
AssertIsMainThread();
|
||||
_UpdateLocal(this);
|
||||
return coroutineIterator.YieldReturnAtState(2, StateMask);
|
||||
case 2:
|
||||
if (_UpdateWorld == null) {
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
goto case 5;
|
||||
} else {
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
return coroutineIterator.YieldReturnAtState(3, StateMask);
|
||||
}
|
||||
case 3:
|
||||
AssertIsMainThread();
|
||||
_UpdateWorld(this);
|
||||
return coroutineIterator.YieldReturnAtState(4, StateMask);
|
||||
case 4:
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
goto case 5;
|
||||
case 5:
|
||||
if (_UpdateComplete != null) {
|
||||
AssertIsWorkerThread();
|
||||
return coroutineIterator.YieldReturnAtState(6, StateMask);
|
||||
} else {
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
case 6:
|
||||
AssertIsMainThread();
|
||||
_UpdateComplete(this);
|
||||
return CoroutineIterator.Done;
|
||||
default:
|
||||
Debug.LogError(string.Format(
|
||||
"Internal coroutine logic error: SkeletonRenderer.AfterAnimationAppliedSplit state was {0}.",
|
||||
coroutineIterator.State(StateMask)), this);
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
}
|
||||
|
||||
protected void IssueOnPostProcessVertices (MeshGeneratorBuffers buffers) {
|
||||
if (OnPostProcessVertices != null)
|
||||
OnPostProcessVertices.Invoke(buffers);
|
||||
}
|
||||
|
||||
protected virtual void UpdateWorldTransform (Physics physics) {
|
||||
skeleton.UpdateWorldTransform(physics);
|
||||
}
|
||||
|
||||
public virtual void UpdateMesh (bool calledFromMainThread = true) {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
bool canPrepareInstructions = calledFromMainThread || !NeedsMainThreadRendererPreparation;
|
||||
#else
|
||||
bool canPrepareInstructions = true;
|
||||
#endif
|
||||
if (canPrepareInstructions)
|
||||
PrepareInstructionsAndRenderers();
|
||||
|
||||
updateTriangles = UpdateBuffersToInstructions(calledFromMainThread);
|
||||
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (calledFromMainThread) {
|
||||
requiresMeshBufferAssignmentMainThread = false;
|
||||
UpdateMeshAndMaterialsToBuffers();
|
||||
} else {
|
||||
requiresMeshBufferAssignmentMainThread = true;
|
||||
}
|
||||
#else
|
||||
UpdateMeshAndMaterialsToBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <returns>True if triangles (indices array) need to be updated.</returns>
|
||||
public virtual bool UpdateBuffersToInstructions (bool calledFromMainThread = true) {
|
||||
if (!valid || currentInstructions.rawVertexCount < 0) return false;
|
||||
wasMeshUpdatedAfterInit = true;
|
||||
|
||||
if (this.generateMeshOverride != null) {
|
||||
if (calledFromMainThread)
|
||||
this.generateMeshOverride(currentInstructions);
|
||||
if (disableRenderingOnOverride) return false;
|
||||
}
|
||||
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions = currentInstructions.submeshInstructions;
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
|
||||
|
||||
// Update vertex buffers based on vertices from the attachments and assign buffers to a target UnityEngine.Mesh.
|
||||
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed, calledFromMainThread);
|
||||
FillBuffersFromSubmeshInstructions(workingSubmeshInstructions, currentSmartMesh, updateTriangles);
|
||||
return updateTriangles;
|
||||
}
|
||||
|
||||
public virtual void UpdateMeshAndMaterialsToBuffers () {
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions = currentInstructions.submeshInstructions;
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh = rendererBuffers.GetCurrentMesh();
|
||||
UpdateMeshAndMaterialsToBuffers(workingSubmeshInstructions, currentSmartMesh, updateTriangles);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMeshAndMaterialsToBuffers (
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions, MeshRendererBuffers.SmartMesh currentSmartMesh,
|
||||
bool updateTriangles) {
|
||||
|
||||
FillMeshFromBuffers(currentSmartMesh, updateTriangles);
|
||||
|
||||
bool materialsChanged;
|
||||
rendererBuffers.GatherMaterialsFromInstructions(workingSubmeshInstructions, out materialsChanged);
|
||||
if (materialsChanged || materialsNeedUpdate) {
|
||||
UpdateUsedMaterialsForRenderers(workingSubmeshInstructions);
|
||||
}
|
||||
|
||||
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
if (Application.isPlaying)
|
||||
HandleOnDemandLoading();
|
||||
#endif
|
||||
|
||||
// The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh.
|
||||
AssignMeshAtRenderer(workingSubmeshInstructions, currentSmartMesh);
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public virtual void UpdateMaterials () {
|
||||
UpdateUsedMaterialsForRenderers(currentInstructions.submeshInstructions);
|
||||
}
|
||||
|
||||
// Threading Asserts
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
protected void InitializeMainThreadID () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (mainThreadID == -1)
|
||||
mainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsMainThread () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId != mainThreadID)
|
||||
Debug.LogError("AssertIsMainThread failed: worker thread calling main thread code. Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsWorkerThread () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId == mainThreadID)
|
||||
Debug.LogError("AssertIsWorkerThread failed: main thread calling worker thread code! Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
#endregion ISkeletonRenderer Methods
|
||||
|
||||
#region ISkeletonRenderer Events
|
||||
protected event SkeletonRendererDelegate _UpdateLocal;
|
||||
protected event SkeletonRendererDelegate _UpdateWorld;
|
||||
protected event SkeletonRendererDelegate _UpdateComplete;
|
||||
|
||||
/// <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 SkeletonRendererDelegate 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 SkeletonRendererDelegate 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 SkeletonRendererDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
|
||||
public event SkeletonRendererDelegate OnRebuild;
|
||||
|
||||
/// <summary>OnInstructionsPrepared is raised at the end of <c>LateUpdate</c> after render instructions
|
||||
/// are done, target renderers are prepared, and the mesh is ready to be generated.</summary>
|
||||
public event InstructionDelegate OnInstructionsPrepared;
|
||||
#endregion ISkeletonRenderer Events
|
||||
#endregion Identical common ISkeletonRenderer code
|
||||
// End of identical code shared by ISkeletonRenderer subclasses as a workaround for single inheritance limitations.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 903c50a86a432684aa719e571dfd38ae
|
||||
timeCreated: 1754679077
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,869 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
#define BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif
|
||||
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
#define USES_ENTITY_ID
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Animations;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(Animator))]
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-main-components#SkeletonMecanim-Component")]
|
||||
public class SkeletonMecanim : SkeletonAnimationBase, IUpgradable {
|
||||
|
||||
public SkeletonMecanim.MecanimTranslator translator;
|
||||
public MecanimTranslator Translator { get { return translator; } }
|
||||
|
||||
public UnityEngine.Animator AnimatorComponent {
|
||||
get { return this.GetComponent<Animator>(); }
|
||||
}
|
||||
|
||||
public override bool IsValid {
|
||||
get {
|
||||
return skeletonRenderer != null && skeletonRenderer.IsValid && translator != null &&
|
||||
translator.Animator && translator.Animator.isInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeAnimationComponent () {
|
||||
base.InitializeAnimationComponent();
|
||||
if (!skeletonRenderer.IsValid)
|
||||
return;
|
||||
|
||||
if (translator == null) translator = new MecanimTranslator();
|
||||
translator.Initialize(this.AnimatorComponent, skeletonRenderer.SkeletonDataAsset);
|
||||
}
|
||||
|
||||
protected override void UpdateAnimationStatus (float deltaTime) {
|
||||
// Note: main animation status is updated by Mecanim Animator component
|
||||
|
||||
skeleton.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override void MainThreadBeforeUpdateInternal () {
|
||||
base.MainThreadBeforeUpdateInternal();
|
||||
translator.GatherAnimatorState();
|
||||
}
|
||||
|
||||
protected override void ApplyStateToSkeleton (bool calledFromMainThread) {
|
||||
#if UNITY_EDITOR
|
||||
if (calledFromMainThread)
|
||||
EditorRebindAnimator();
|
||||
|
||||
if (ApplicationIsPlaying || !calledFromMainThread) {
|
||||
translator.Apply(skeletonRenderer.Skeleton);
|
||||
} else {
|
||||
Animator translatorAnimator = translator.Animator;
|
||||
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(skeletonRenderer.Skeleton);
|
||||
}
|
||||
}
|
||||
#else
|
||||
translator.Apply(skeletonRenderer.Skeleton);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void EditorRebindAnimator () {
|
||||
Animator translatorAnimator = translator.Animator;
|
||||
if (translatorAnimator != null && !translatorAnimator.isInitialized)
|
||||
translatorAnimator.Rebind();
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
// compatibility layer between 4.1 and 4.2, automatically transfer serialized attributes.
|
||||
public override void UpgradeTo43 () {
|
||||
if (!Application.isPlaying && !wasDeprecatedTransferred) {
|
||||
UpgradeTo43Components();
|
||||
TransferDeprecatedFields();
|
||||
InitializeAnimationComponent();
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpgradeTo43Components () {
|
||||
if (gameObject.GetComponent<SkeletonRenderer>() == null &&
|
||||
gameObject.GetComponent<SkeletonGraphic>() == null) {
|
||||
gameObject.AddComponent<SkeletonRenderer>();
|
||||
Debug.Log(string.Format("{0}: Auto-migrated old SkeletonMecanim component to split SkeletonMecanim + SkeletonRenderer components.",
|
||||
gameObject.name), gameObject);
|
||||
EditorBridge.RequestMarkDirty(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Transfer of former base class SkeletonRenderer parameters.</summary>
|
||||
protected void TransferDeprecatedFields () {
|
||||
wasDeprecatedTransferred = true;
|
||||
|
||||
SkeletonRenderer skeletonRenderer = gameObject.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
return;
|
||||
|
||||
skeletonRenderer.skeletonDataAsset = this.skeletonDataAssetDeprecated;
|
||||
skeletonRenderer.initialSkinName = this.initialSkinNameDeprecated;
|
||||
skeletonRenderer.EditorSkipSkinSync = this.editorSkipSkinSyncDeprecated;
|
||||
skeletonRenderer.initialFlipX = this.initialFlipXDeprecated;
|
||||
skeletonRenderer.initialFlipY = this.initialFlipYDeprecated;
|
||||
skeletonRenderer.UpdateMode = this.updateModeDeprecated;
|
||||
skeletonRenderer.updateWhenInvisible = this.updateWhenInvisibleDeprecated;
|
||||
skeletonRenderer.separatorSlotNames = this.separatorSlotNamesDeprecated;
|
||||
|
||||
skeletonRenderer.MeshSettings.zSpacing = this.zSpacingDeprecated;
|
||||
skeletonRenderer.MeshSettings.useClipping = this.useClippingDeprecated;
|
||||
skeletonRenderer.MeshSettings.immutableTriangles = this.immutableTrianglesDeprecated;
|
||||
skeletonRenderer.MeshSettings.pmaVertexColors = this.pmaVertexColorsDeprecated;
|
||||
skeletonRenderer.MeshSettings.tintBlack = this.tintBlackDeprecated;
|
||||
skeletonRenderer.MeshSettings.addNormals = this.addNormalsDeprecated;
|
||||
skeletonRenderer.MeshSettings.calculateTangents = this.calculateTangentsDeprecated;
|
||||
|
||||
skeletonRenderer.clearStateOnDisable = this.clearStateOnDisableDeprecated;
|
||||
skeletonRenderer.singleSubmesh = this.singleSubmeshDeprecated;
|
||||
skeletonRenderer.MaskInteraction = this.maskInteractionDeprecated;
|
||||
|
||||
translator.TransferDeprecatedFields();
|
||||
}
|
||||
|
||||
[SerializeField] protected bool wasDeprecatedTransferred = false;
|
||||
// SkeletonRenderer former base class parameters
|
||||
[FormerlySerializedAs("skeletonDataAsset")] [SerializeField] private SkeletonDataAsset skeletonDataAssetDeprecated;
|
||||
|
||||
[FormerlySerializedAs("initialSkinName")] [SpineSkin(defaultAsEmptyString: true)] [SerializeField] private string initialSkinNameDeprecated;
|
||||
[FormerlySerializedAs("editorSkipSkinSync")] [SerializeField] private bool editorSkipSkinSyncDeprecated = false;
|
||||
[FormerlySerializedAs("initialFlipX")] [SerializeField] private bool initialFlipXDeprecated = false;
|
||||
[FormerlySerializedAs("initialFlipY")] [SerializeField] private bool initialFlipYDeprecated = false;
|
||||
[FormerlySerializedAs("updateMode")] [SerializeField] private UpdateMode updateModeDeprecated = UpdateMode.FullUpdate;
|
||||
[FormerlySerializedAs("updateWhenInvisible")] [SerializeField] private UpdateMode updateWhenInvisibleDeprecated = UpdateMode.FullUpdate;
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators"),
|
||||
UnityEngine.Serialization.FormerlySerializedAs("separatorSlotNames")]
|
||||
[SerializeField] private string[] separatorSlotNamesDeprecated = new string[0];
|
||||
|
||||
[FormerlySerializedAs("zSpacing")] [SerializeField] private float zSpacingDeprecated = 0f;
|
||||
[FormerlySerializedAs("useClipping")] [SerializeField] private bool useClippingDeprecated = true;
|
||||
[FormerlySerializedAs("immutableTriangles")] [SerializeField] private bool immutableTrianglesDeprecated = false;
|
||||
[FormerlySerializedAs("pmaVertexColors")] [SerializeField] private bool pmaVertexColorsDeprecated = true;
|
||||
[FormerlySerializedAs("clearStateOnDisable")] [SerializeField] private bool clearStateOnDisableDeprecated = false;
|
||||
[FormerlySerializedAs("tintBlack")] [SerializeField] private bool tintBlackDeprecated = false;
|
||||
[FormerlySerializedAs("singleSubmesh")] [SerializeField] private bool singleSubmeshDeprecated = false;
|
||||
[FormerlySerializedAs("calculateNormals"),
|
||||
FormerlySerializedAs("addNormals")]
|
||||
[SerializeField] private bool addNormalsDeprecated = false;
|
||||
[FormerlySerializedAs("calculateTangents")] [SerializeField] private bool calculateTangentsDeprecated = false;
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
[FormerlySerializedAs("maskInteraction")] [SerializeField] private SpriteMaskInteraction maskInteractionDeprecated = SpriteMaskInteraction.None;
|
||||
#endif // BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif // UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endregion
|
||||
|
||||
[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 bool[] layerIsAdditive = new bool[0];
|
||||
|
||||
// Migration from removed MixBlend
|
||||
[FormerlySerializedAs("layerBlendModes")] [SerializeField] private int[] layerBlendModesDeprecated = new int[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, Match }
|
||||
|
||||
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 struct ClipInfo {
|
||||
public Spine.Animation animation;
|
||||
public float weight;
|
||||
public float length;
|
||||
public bool isLooping;
|
||||
}
|
||||
|
||||
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<ClipInfo> clipInfos = new List<ClipInfo>();
|
||||
public readonly List<ClipInfo> nextClipInfos = new List<ClipInfo>();
|
||||
public readonly List<ClipInfo> interruptingClipInfos = new List<ClipInfo>();
|
||||
public float[] clipResolvedWeights = new float[0];
|
||||
public float[] nextClipResolvedWeights = new float[0];
|
||||
public float[] interruptingClipResolvedWeights = new float[0];
|
||||
|
||||
public AnimatorStateInfo stateInfo;
|
||||
public AnimatorStateInfo nextStateInfo;
|
||||
public AnimatorStateInfo interruptingStateInfo;
|
||||
|
||||
public float interruptingClipTimeAddition = 0;
|
||||
public float layerWeight = 0;
|
||||
}
|
||||
protected ClipInfos[] layerClipInfos = new ClipInfos[0];
|
||||
protected int layerCount = 0;
|
||||
|
||||
Animator animator;
|
||||
public Animator Animator { get { return this.animator; } }
|
||||
|
||||
public int MecanimLayerCount {
|
||||
get {
|
||||
if (!animator)
|
||||
return 0;
|
||||
if (!animator.isInitialized || !animator.isActiveAndEnabled || animator.runtimeAnimatorController == null)
|
||||
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();
|
||||
SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
|
||||
foreach (Animation a in data.Animations)
|
||||
animationTable.Add(a.Name.GetHashCode(), a);
|
||||
|
||||
clipNameHashCodeTable.Clear();
|
||||
ClearClipInfosForLayers();
|
||||
}
|
||||
|
||||
private bool ApplyAnimation (Skeleton skeleton, ClipInfo info, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float layerWeight, bool layerIsAdditive, bool fromSetup,
|
||||
bool useCustomClipWeight = false, float customClipWeight = 1.0f) {
|
||||
float weight = info.weight * layerWeight;
|
||||
if (weight < WeightEpsilon)
|
||||
return false;
|
||||
|
||||
if (info.animation == null)
|
||||
return false;
|
||||
|
||||
float time = AnimationTime(stateInfo.normalizedTime, info.length,
|
||||
info.isLooping, stateInfo.speed < 0);
|
||||
weight = useCustomClipWeight ? layerWeight * customClipWeight : weight;
|
||||
info.animation.Apply(skeleton, 0, time, info.isLooping, null,
|
||||
weight, fromSetup, layerIsAdditive, false, false);
|
||||
if (_OnClipApplied != null)
|
||||
OnClipAppliedCallback(info.animation, stateInfo, layerIndex, time, info.isLooping, weight);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ApplyInterruptionAnimation (Skeleton skeleton,
|
||||
bool interpolateWeightTo1, ClipInfo info, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float layerWeight, bool layerIsAdditive, float interruptingClipTimeAddition,
|
||||
bool useCustomClipWeight = false, float customClipWeight = 1.0f) {
|
||||
|
||||
float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
|
||||
float weight = clipWeight * layerWeight;
|
||||
if (weight < WeightEpsilon)
|
||||
return false;
|
||||
|
||||
if (info.animation == null)
|
||||
return false;
|
||||
|
||||
float time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition,
|
||||
info.length, stateInfo.speed < 0);
|
||||
weight = useCustomClipWeight ? layerWeight * customClipWeight : weight;
|
||||
bool fromSetup = false;
|
||||
info.animation.Apply(skeleton, 0, time, info.isLooping, null,
|
||||
weight, fromSetup, layerIsAdditive, false, false);
|
||||
if (_OnClipApplied != null) {
|
||||
OnClipAppliedCallback(info.animation, stateInfo, layerIndex, time, info.isLooping, weight);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnClipAppliedCallback (Spine.Animation animation, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float time, bool isLooping, float weight) {
|
||||
|
||||
float speedFactor = stateInfo.speedMultiplier * stateInfo.speed;
|
||||
float lastTime = time - (Time.deltaTime * speedFactor);
|
||||
if (isLooping && animation.Duration != 0) {
|
||||
time %= animation.Duration;
|
||||
lastTime %= animation.Duration;
|
||||
}
|
||||
_OnClipApplied(animation, layerIndex, weight, time, lastTime, speedFactor < 0);
|
||||
}
|
||||
|
||||
public void GatherAnimatorState () {
|
||||
layerCount = animator.layerCount;
|
||||
MigrateLayerBlendModes();
|
||||
#if UNITY_EDITOR
|
||||
GetLayerBlendModes();
|
||||
#endif
|
||||
if (layerIsAdditive.Length < layerCount)
|
||||
System.Array.Resize<bool>(ref layerIsAdditive, layerCount);
|
||||
if (layerMixModes.Length < layerCount) {
|
||||
int oldSize = layerMixModes.Length;
|
||||
System.Array.Resize<MixMode>(ref layerMixModes, layerCount);
|
||||
for (int layer = oldSize; layer < layerCount; ++layer) {
|
||||
layerMixModes[layer] = layerIsAdditive[layer] ? MixMode.AlwaysMix : MixMode.MixNext;
|
||||
}
|
||||
}
|
||||
|
||||
InitClipInfosForLayers();
|
||||
for (int layer = 0, n = layerCount; layer < n; layer++) {
|
||||
GetStateUpdatesFromAnimator(layer);
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply (Skeleton skeleton) {
|
||||
// Clear Previous
|
||||
if (autoReset) {
|
||||
List<Animation> previousAnimations = this.previousAnimations;
|
||||
for (int i = 0, n = previousAnimations.Count; i < n; i++)
|
||||
previousAnimations[i].Apply(skeleton, 0, 0, false, null, 0, true, false, true, false); // SetKeyedItemsToSetupPose
|
||||
|
||||
previousAnimations.Clear();
|
||||
for (int layer = 0, n = layerCount; layer < n; layer++) {
|
||||
ClipInfos layerInfos = layerClipInfos[layer];
|
||||
float layerWeight = layerInfos.layerWeight;
|
||||
if (layerWeight <= 0) continue;
|
||||
|
||||
AnimatorStateInfo nextStateInfo = layerInfos.nextStateInfo;
|
||||
|
||||
bool hasNext = nextStateInfo.fullPathHash != 0;
|
||||
|
||||
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
|
||||
IList<ClipInfo> 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++) {
|
||||
ClipInfo info = clipInfo[c];
|
||||
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
if (info.animation != null)
|
||||
previousAnimations.Add(info.animation);
|
||||
}
|
||||
|
||||
if (hasNext) {
|
||||
for (int c = 0; c < nextClipInfoCount; c++) {
|
||||
ClipInfo info = nextClipInfo[c];
|
||||
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
if (info.animation != null)
|
||||
previousAnimations.Add(info.animation);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInterruptionActive) {
|
||||
for (int c = 0; c < interruptingClipInfoCount; c++) {
|
||||
ClipInfo info = interruptingClipInfo[c];
|
||||
float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
|
||||
float weight = clipWeight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
if (info.animation != null)
|
||||
previousAnimations.Add(info.animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply
|
||||
int appliedCount = 0;
|
||||
for (int layer = 0, n = layerCount; layer < n; layer++) {
|
||||
ClipInfos layerInfos = layerClipInfos[layer];
|
||||
float layerWeight = layerInfos.layerWeight;
|
||||
|
||||
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<ClipInfo> clipInfo, nextClipInfo, interruptingClipInfo;
|
||||
bool interpolateWeightTo1;
|
||||
GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
|
||||
out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1);
|
||||
|
||||
bool add = layerIsAdditive[layer];
|
||||
MixMode mode = GetMixMode(layer, add);
|
||||
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, add, appliedCount++ == 0);
|
||||
}
|
||||
if (hasNext) {
|
||||
for (int c = 0; c < nextClipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, add, appliedCount++ == 0);
|
||||
}
|
||||
}
|
||||
if (isInterruptionActive) {
|
||||
for (int c = 0; c < interruptingClipInfoCount; c++) {
|
||||
appliedCount++;
|
||||
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, add, interruptingClipTimeAddition);
|
||||
}
|
||||
}
|
||||
} else if (mode == MixMode.Match) {
|
||||
// Calculate matching Spine lerp(lerp(A, B, w2), C, w3) weights
|
||||
// from Unity's absolute weights A*W1 + B*W2 + C*W3.
|
||||
MatchWeights(layerClipInfos[layer], hasNext, isInterruptionActive, clipInfoCount,
|
||||
nextClipInfoCount, interruptingClipInfoCount, clipInfo, nextClipInfo, interruptingClipInfo);
|
||||
|
||||
float[] customWeights = layerClipInfos[layer].clipResolvedWeights;
|
||||
for (int c = 0; c < clipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, add,
|
||||
appliedCount++ == 0, true, customWeights[c]);
|
||||
}
|
||||
if (hasNext) {
|
||||
customWeights = layerClipInfos[layer].nextClipResolvedWeights;
|
||||
for (int c = 0; c < nextClipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, add,
|
||||
appliedCount++ == 0, true, customWeights[c]);
|
||||
}
|
||||
}
|
||||
if (isInterruptionActive) {
|
||||
customWeights = layerClipInfos[layer].interruptingClipResolvedWeights;
|
||||
for (int c = 0; c < interruptingClipInfoCount; c++) {
|
||||
appliedCount++;
|
||||
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, add, interruptingClipTimeAddition,
|
||||
true, customWeights[c]);
|
||||
}
|
||||
}
|
||||
} 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, add,
|
||||
appliedCount++ == 0, true, 1.0f))
|
||||
continue;
|
||||
++c; break;
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < clipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, add, appliedCount++ == 0);
|
||||
}
|
||||
|
||||
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, add,
|
||||
appliedCount++ == 0, true, 1.0f))
|
||||
continue;
|
||||
++c; break;
|
||||
}
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < nextClipInfoCount; c++) {
|
||||
if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, add, appliedCount++ == 0))
|
||||
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++) {
|
||||
appliedCount++;
|
||||
if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, add, interruptingClipTimeAddition, true, 1.0f)) {
|
||||
|
||||
++c; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < interruptingClipInfoCount; c++) {
|
||||
appliedCount++;
|
||||
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, add, interruptingClipTimeAddition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve matching weights from Unity's absolute weights A*w1 + B*w2 + C*w3 to
|
||||
/// Spine's lerp(lerp(A, B, x), C, y) weights, in reverse order of clips.
|
||||
/// </summary>
|
||||
protected void MatchWeights (ClipInfos clipInfos, bool hasNext, bool isInterruptionActive,
|
||||
int clipInfoCount, int nextClipInfoCount, int interruptingClipInfoCount,
|
||||
IList<ClipInfo> clipInfo, IList<ClipInfo> nextClipInfo, IList<ClipInfo> interruptingClipInfo) {
|
||||
|
||||
if (clipInfos.clipResolvedWeights.Length < clipInfoCount) {
|
||||
System.Array.Resize<float>(ref clipInfos.clipResolvedWeights, clipInfoCount);
|
||||
}
|
||||
if (hasNext && clipInfos.nextClipResolvedWeights.Length < nextClipInfoCount) {
|
||||
System.Array.Resize<float>(ref clipInfos.nextClipResolvedWeights, nextClipInfoCount);
|
||||
}
|
||||
if (isInterruptionActive && clipInfos.interruptingClipResolvedWeights.Length < interruptingClipInfoCount) {
|
||||
System.Array.Resize<float>(ref clipInfos.interruptingClipResolvedWeights, interruptingClipInfoCount);
|
||||
}
|
||||
|
||||
float inverseWeight = 1.0f;
|
||||
if (isInterruptionActive) {
|
||||
for (int c = interruptingClipInfoCount - 1; c >= 0; c--) {
|
||||
float unityWeight = interruptingClipInfo[c].weight;
|
||||
clipInfos.interruptingClipResolvedWeights[c] = interruptingClipInfo[c].weight * inverseWeight;
|
||||
inverseWeight /= (1.0f - unityWeight);
|
||||
}
|
||||
}
|
||||
if (hasNext) {
|
||||
for (int c = nextClipInfoCount - 1; c >= 0; c--) {
|
||||
float unityWeight = nextClipInfo[c].weight;
|
||||
clipInfos.nextClipResolvedWeights[c] = nextClipInfo[c].weight * inverseWeight;
|
||||
inverseWeight /= (1.0f - unityWeight);
|
||||
}
|
||||
}
|
||||
for (int c = clipInfoCount - 1; c >= 0; c--) {
|
||||
float unityWeight = clipInfo[c].weight;
|
||||
clipInfos.clipResolvedWeights[c] = (c == 0) ? 1f : clipInfo[c].weight * inverseWeight;
|
||||
inverseWeight /= (1.0f - unityWeight);
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValuePair<Spine.Animation, float> GetActiveAnimationAndTime (int layer) {
|
||||
if (layer >= layerClipInfos.Length)
|
||||
return new KeyValuePair<Spine.Animation, float>(null, 0);
|
||||
|
||||
ClipInfos layerInfos = layerClipInfos[layer];
|
||||
bool isInterruptionActive = layerInfos.isInterruptionActive;
|
||||
ClipInfo clipInfo;
|
||||
Spine.Animation animation = null;
|
||||
AnimatorStateInfo stateInfo;
|
||||
if (isInterruptionActive && layerInfos.interruptingClipInfoCount > 0) {
|
||||
clipInfo = layerInfos.interruptingClipInfos[0];
|
||||
stateInfo = layerInfos.interruptingStateInfo;
|
||||
} else if (layerInfos.clipInfoCount > 0) {
|
||||
clipInfo = layerInfos.clipInfos[0];
|
||||
stateInfo = layerInfos.stateInfo;
|
||||
} else {
|
||||
return new KeyValuePair<Spine.Animation, float>(null, 0);
|
||||
}
|
||||
animation = clipInfo.animation;
|
||||
float time = AnimationTime(stateInfo.normalizedTime, clipInfo.length,
|
||||
clipInfo.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 < layerCount) {
|
||||
System.Array.Resize<ClipInfos>(ref layerClipInfos, layerCount);
|
||||
for (int layer = 0, n = 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, bool layerIsAdditive) {
|
||||
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 (layerIsAdditive && mode == MixMode.MixNext) {
|
||||
mode = MixMode.AlwaysMix;
|
||||
layerMixModes[layer] = mode;
|
||||
}
|
||||
return mode;
|
||||
} else {
|
||||
return layerIsAdditive ? MixMode.AlwaysMix : MixMode.MixNext;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void TransferDeprecatedFields () {
|
||||
MigrateLayerBlendModes();
|
||||
}
|
||||
#endif
|
||||
void MigrateLayerBlendModes () {
|
||||
if (layerIsAdditive.Length == 0 && layerBlendModesDeprecated.Length > 0) {
|
||||
layerIsAdditive = new bool[layerBlendModesDeprecated.Length];
|
||||
for (int i = 0; i < layerBlendModesDeprecated.Length; i++)
|
||||
layerIsAdditive[i] = layerBlendModesDeprecated[i] == 3; // MixBlend.Add was 3.
|
||||
layerBlendModesDeprecated = new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void GetLayerBlendModes () {
|
||||
if (layerIsAdditive.Length < layerCount) {
|
||||
System.Array.Resize<bool>(ref layerIsAdditive, layerCount);
|
||||
}
|
||||
AnimatorController controller = animator.runtimeAnimatorController as AnimatorController;
|
||||
for (int layer = 0, n = layerCount; layer < n; ++layer) {
|
||||
if (controller != null)
|
||||
layerIsAdditive[layer] = controller.layers[layer].blendingMode == AnimatorLayerBlendingMode.Additive;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void GetStateUpdatesFromAnimator (int layer) {
|
||||
|
||||
ClipInfos layerInfos = layerClipInfos[layer];
|
||||
|
||||
// Note: Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
|
||||
layerInfos.layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer);
|
||||
|
||||
int clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer);
|
||||
int nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer);
|
||||
|
||||
List<ClipInfo> clipInfos = layerInfos.clipInfos;
|
||||
List<ClipInfo> nextClipInfos = layerInfos.nextClipInfos;
|
||||
List<ClipInfo> 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).
|
||||
AnimatorStateInfo interruptingStateInfo = animator.GetNextAnimatorStateInfo(layer);
|
||||
layerInfos.isLastFrameOfInterruption = interruptingStateInfo.fullPathHash == 0;
|
||||
if (!layerInfos.isLastFrameOfInterruption) {
|
||||
List<AnimatorClipInfo> tempInterruptingClipInfos = new List<AnimatorClipInfo>();
|
||||
animator.GetNextAnimatorClipInfo(layer, tempInterruptingClipInfos);
|
||||
|
||||
interruptingClipInfos.Clear();
|
||||
for (int i = 0; i < tempInterruptingClipInfos.Count; i++) {
|
||||
AnimatorClipInfo animatorInfo = tempInterruptingClipInfos[i];
|
||||
ClipInfo info = new ClipInfo();
|
||||
info.animation = GetAnimation(animatorInfo.clip);
|
||||
info.weight = animatorInfo.weight;
|
||||
info.length = animatorInfo.clip.length;
|
||||
info.isLooping = animatorInfo.clip.isLooping;
|
||||
interruptingClipInfos.Add(info);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Get current clip infos and extract data
|
||||
List<AnimatorClipInfo> tempClipInfos = new List<AnimatorClipInfo>();
|
||||
animator.GetCurrentAnimatorClipInfo(layer, tempClipInfos);
|
||||
clipInfos.Clear();
|
||||
for (int i = 0; i < tempClipInfos.Count; i++) {
|
||||
AnimatorClipInfo animatorInfo = tempClipInfos[i];
|
||||
ClipInfo info = new ClipInfo();
|
||||
info.animation = GetAnimation(animatorInfo.clip);
|
||||
info.weight = animatorInfo.weight;
|
||||
info.length = animatorInfo.clip.length;
|
||||
info.isLooping = animatorInfo.clip.isLooping;
|
||||
clipInfos.Add(info);
|
||||
}
|
||||
|
||||
// Get next clip infos and extract data
|
||||
List<AnimatorClipInfo> tempNextClipInfos = new List<AnimatorClipInfo>();
|
||||
animator.GetNextAnimatorClipInfo(layer, tempNextClipInfos);
|
||||
nextClipInfos.Clear();
|
||||
for (int i = 0; i < tempNextClipInfos.Count; i++) {
|
||||
AnimatorClipInfo animatorInfo = tempNextClipInfos[i];
|
||||
ClipInfo info = new ClipInfo();
|
||||
info.animation = GetAnimation(animatorInfo.clip);
|
||||
info.weight = animatorInfo.weight;
|
||||
info.length = animatorInfo.clip.length;
|
||||
info.isLooping = animatorInfo.clip.isLooping;
|
||||
nextClipInfos.Add(info);
|
||||
}
|
||||
|
||||
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<ClipInfo> clipInfo,
|
||||
out IList<ClipInfo> nextClipInfo,
|
||||
out IList<ClipInfo> interruptingClipInfo,
|
||||
out bool shallInterpolateWeightTo1) {
|
||||
|
||||
ClipInfos 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) {
|
||||
|
||||
ClipInfos 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();
|
||||
#if USES_ENTITY_ID
|
||||
public bool Equals (AnimationClip x, AnimationClip y) { return x.GetEntityId() == y.GetEntityId(); }
|
||||
public int GetHashCode (AnimationClip o) { return o.GetHashCode(); }
|
||||
#else
|
||||
public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
|
||||
public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
|
||||
#endif
|
||||
}
|
||||
|
||||
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:
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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("https://esotericsoftware.com/spine-unity-utility-components#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();
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
if (buffers != null) buffers.Dispose();
|
||||
}
|
||||
|
||||
public void ClearMesh () {
|
||||
LazyIntialize();
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
|
||||
public void RenderParts (SkeletonRenderer skeletonRenderer, ExposedList<SubmeshInstruction> instructions,
|
||||
int startSubmesh, int endSubmesh) {
|
||||
|
||||
LazyIntialize();
|
||||
|
||||
// STEP 1: Create instruction
|
||||
MeshRendererBuffers.SmartMesh smartMesh = buffers.GetNextMesh();
|
||||
currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh);
|
||||
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
|
||||
|
||||
// STEP 2: Generate mesh buffers.
|
||||
SubmeshInstruction[] 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);
|
||||
}
|
||||
|
||||
bool materialsChanged;
|
||||
buffers.GatherMaterialsFromInstructions(currentInstructions.submeshInstructions, out materialsChanged);
|
||||
|
||||
// STEP 3: modify mesh.
|
||||
Mesh mesh = smartMesh.mesh;
|
||||
|
||||
if (meshGenerator.VertexCount <= 0) { // Clear an empty mesh
|
||||
updateTriangles = false;
|
||||
mesh.Clear();
|
||||
} else {
|
||||
meshGenerator.FillVertexData(mesh);
|
||||
if (updateTriangles) {
|
||||
meshGenerator.FillTriangles(mesh);
|
||||
}
|
||||
if (updateTriangles || materialsChanged) {
|
||||
Material[] materials = buffers.UpdateSharedMaterialsArray();
|
||||
if (skeletonRenderer)
|
||||
skeletonRenderer.ConfigureMaterials(materials, currentInstructions.submeshInstructions);
|
||||
meshRenderer.sharedMaterials = materials;
|
||||
}
|
||||
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) {
|
||||
GameObject go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
|
||||
go.transform.SetParent(parent, false);
|
||||
SkeletonPartsRenderer returnComponent = go.AddComponent<SkeletonPartsRenderer>();
|
||||
returnComponent.MeshRenderer.sortingOrder = sortingOrder;
|
||||
|
||||
return returnComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0b968d1e7333b499e347acb644f1c1
|
||||
timeCreated: 1458045480
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 HAS_PROPERTY_BLOCK_QUERY
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-utility-components#SkeletonRenderSeparator")]
|
||||
public class SkeletonRenderSeparator : MonoBehaviour, IUpgradable {
|
||||
public const int DefaultSortingOrderIncrement = 5;
|
||||
|
||||
#region Inspector
|
||||
[SerializeField]
|
||||
protected SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonRenderer SkeletonRenderer {
|
||||
get { return skeletonRenderer; }
|
||||
set {
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
|
||||
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>();
|
||||
[System.NonSerialized] public bool isVisible = true;
|
||||
|
||||
#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 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;
|
||||
}
|
||||
|
||||
SkeletonRenderSeparator srs = skeletonRenderer.gameObject.AddComponent<SkeletonRenderSeparator>();
|
||||
srs.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
skeletonRenderer.Initialize(false);
|
||||
int count = extraPartsRenderers;
|
||||
if (addMinimumPartsRenderers)
|
||||
count = extraPartsRenderers + skeletonRenderer.separatorSlots.Count + 1;
|
||||
|
||||
Transform skeletonRendererTransform = skeletonRenderer.transform;
|
||||
List<SkeletonPartsRenderer> componentRenderers = srs.partsRenderers;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
SkeletonPartsRenderer spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRendererTransform, i.ToString());
|
||||
MeshRenderer 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.UpdateMesh();
|
||||
}
|
||||
#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) {
|
||||
SkeletonPartsRenderer previous = partsRenderers[partsRenderers.Count - 1];
|
||||
MeshRenderer previousMeshRenderer = previous.MeshRenderer;
|
||||
sortingLayerID = previousMeshRenderer.sortingLayerID;
|
||||
sortingOrder = previousMeshRenderer.sortingOrder + sortingOrderIncrement;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = partsRenderers.Count.ToString();
|
||||
|
||||
SkeletonPartsRenderer spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRenderer.transform, name);
|
||||
partsRenderers.Add(spr);
|
||||
|
||||
MeshRenderer mr = spr.MeshRenderer;
|
||||
mr.sortingLayerID = sortingLayerID;
|
||||
mr.sortingOrder = sortingOrder;
|
||||
|
||||
return spr;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public void OnEnable () {
|
||||
if (skeletonRenderer == null) return;
|
||||
if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock();
|
||||
mainMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
|
||||
|
||||
skeletonRenderer.enableSeparatorSlots = true;
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
skeletonRenderer.GenerateMeshOverride += HandleRender;
|
||||
|
||||
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++) {
|
||||
SkeletonPartsRenderer currentRenderer = partsRenderers[i];
|
||||
if (currentRenderer == null) continue; // skip null items.
|
||||
|
||||
MeshRenderer mr = currentRenderer.MeshRenderer;
|
||||
mr.lightProbeUsage = lightProbeUsage;
|
||||
mr.receiveShadows = receiveShadows;
|
||||
mr.reflectionProbeUsage = reflectionProbeUsage;
|
||||
mr.shadowCastingMode = shadowCastingMode;
|
||||
mr.motionVectorGenerationMode = motionVectorGenerationMode;
|
||||
mr.probeAnchor = probeAnchor;
|
||||
}
|
||||
}
|
||||
|
||||
if (skeletonRenderer.updateWhenInvisible != UpdateMode.FullUpdate)
|
||||
skeletonRenderer.UpdateMesh();
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
public void OnDisable () {
|
||||
if (skeletonRenderer == null) return;
|
||||
|
||||
skeletonRenderer.enableSeparatorSlots = false;
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
|
||||
skeletonRenderer.UpdateMesh();
|
||||
ClearPartsRendererMeshes();
|
||||
}
|
||||
|
||||
public void UpdateVisibility () {
|
||||
if (skeletonRenderer == null) return;
|
||||
foreach (SkeletonPartsRenderer partsRenderer in partsRenderers) {
|
||||
if (partsRenderer == null) continue;
|
||||
|
||||
if (partsRenderer.MeshRenderer.isVisible) {
|
||||
if (!isVisible) {
|
||||
skeletonRenderer.OnBecameVisible();
|
||||
isVisible = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isVisible) {
|
||||
isVisible = false;
|
||||
skeletonRenderer.OnBecameInvisible();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialPropertyBlock copiedBlock;
|
||||
|
||||
void HandleRender (SkeletonRendererInstruction instruction) {
|
||||
int rendererCount = partsRenderers.Count;
|
||||
if (rendererCount <= 0) return;
|
||||
|
||||
#if HAS_PROPERTY_BLOCK_QUERY
|
||||
bool assignPropertyBlock = this.copyPropertyBlock && mainMeshRenderer.HasPropertyBlock();
|
||||
#else
|
||||
bool assignPropertyBlock = this.copyPropertyBlock;
|
||||
#endif
|
||||
if (assignPropertyBlock)
|
||||
mainMeshRenderer.GetPropertyBlock(copiedBlock);
|
||||
|
||||
MeshGenerator.Settings originalSettings = skeletonRenderer.MeshSettings;
|
||||
MeshGenerator.Settings settings = new MeshGenerator.Settings {
|
||||
addNormals = originalSettings.addNormals,
|
||||
calculateTangents = originalSettings.calculateTangents,
|
||||
immutableTriangles = false, // parts cannot do immutable triangles.
|
||||
pmaVertexColors = originalSettings.pmaVertexColors,
|
||||
tintBlack = originalSettings.tintBlack,
|
||||
useClipping = true,
|
||||
zSpacing = originalSettings.zSpacing
|
||||
};
|
||||
|
||||
ExposedList<SubmeshInstruction> submeshInstructions = instruction.submeshInstructions;
|
||||
SubmeshInstruction[] submeshInstructionsItems = submeshInstructions.Items;
|
||||
int lastSubmeshInstruction = submeshInstructions.Count - 1;
|
||||
|
||||
int rendererIndex = 0;
|
||||
SkeletonPartsRenderer currentRenderer = partsRenderers[rendererIndex];
|
||||
for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) {
|
||||
if (currentRenderer == null)
|
||||
continue;
|
||||
if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) {
|
||||
// Apply properties
|
||||
MeshGenerator meshGenerator = currentRenderer.MeshGenerator;
|
||||
meshGenerator.settings = settings;
|
||||
|
||||
if (assignPropertyBlock)
|
||||
currentRenderer.SetPropertyBlock(copiedBlock);
|
||||
// Render
|
||||
currentRenderer.RenderParts(skeletonRenderer, 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();
|
||||
}
|
||||
}
|
||||
|
||||
protected void ClearPartsRendererMeshes () {
|
||||
foreach (SkeletonPartsRenderer partsRenderer in partsRenderers) {
|
||||
if (partsRenderer != null)
|
||||
partsRenderer.ClearMesh();
|
||||
}
|
||||
}
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at SkeletonRenderSeparator, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+12
@@ -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,754 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_2019_3_OR_NEWER
|
||||
#define CONFIGURABLE_ENTER_PLAY_MODE
|
||||
#endif
|
||||
|
||||
#if !SPINE_DISABLE_THREADING
|
||||
#define USE_THREADED_SKELETON_UPDATE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
#define SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
// Partial class: covers common identical attributes, properties and methods shared across all
|
||||
// ISkeletonRenderer subclasses. This is a workaround for single inheritance limitations and covers code which
|
||||
// would otherwise be located in a base-class of SkeletonRenderer.
|
||||
public partial class SkeletonRenderer : MonoBehaviour, ISkeletonRenderer, IHasSkeletonRenderer {
|
||||
|
||||
// Identical code shared by ISkeletonRenderer subclasses as a workaround for single inheritance limitations.
|
||||
#region Identical common ISkeletonRenderer code
|
||||
#region ISkeletonRenderer Attributes
|
||||
// Core Attributes
|
||||
public ISkeletonAnimation skeletonAnimation;
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
[System.NonSerialized] public Skeleton skeleton;
|
||||
[System.NonSerialized] public bool valid;
|
||||
|
||||
protected bool wasMeshUpdatedAfterInit = false;
|
||||
protected bool updateTriangles = true;
|
||||
|
||||
// Initialization Settings
|
||||
/// <summary>Skin name to use when the Skeleton is initialized.</summary>
|
||||
[SpineSkin(dataField: "skeletonDataAsset", defaultAsEmptyString: true)] public string initialSkinName;
|
||||
/// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
|
||||
public bool initialFlipX, initialFlipY;
|
||||
|
||||
// Render Settings
|
||||
[SerializeField] protected MeshGenerator.Settings meshSettings = MeshGenerator.Settings.Default;
|
||||
[System.NonSerialized] protected readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
|
||||
|
||||
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
|
||||
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;
|
||||
/// <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;
|
||||
|
||||
// Submesh Separation
|
||||
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
|
||||
[SpineSlot] public 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;
|
||||
|
||||
// Overrides Attributes
|
||||
// These are API for anything that wants to take over rendering for a SkeletonRenderer.
|
||||
public bool disableRenderingOnOverride = true;
|
||||
event InstructionDelegate generateMeshOverride;
|
||||
[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
|
||||
[System.NonSerialized] protected bool materialsNeedUpdate = false;
|
||||
|
||||
// Physics Attributes
|
||||
/// <seealso cref="PhysicsPositionInheritanceFactor"/>
|
||||
[SerializeField] protected Vector2 physicsPositionInheritanceFactor = Vector2.one;
|
||||
/// <seealso cref="PhysicsRotationInheritanceFactor"/>
|
||||
[SerializeField] protected float physicsRotationInheritanceFactor = 1.0f;
|
||||
/// <seealso cref="PhysicsPositionInheritanceLimit"/>
|
||||
[SerializeField] protected Vector2 physicsPositionInheritanceLimit = Vector2.positiveInfinity;
|
||||
/// <seealso cref="PhysicsRotationInheritanceLimit"/>
|
||||
[SerializeField] protected float physicsRotationInheritanceLimit = float.MaxValue;
|
||||
/// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
|
||||
[SerializeField] protected Transform physicsMovementRelativeTo = null;
|
||||
|
||||
/// <summary>Used for applying Transform translation to skeleton PhysicsConstraints.</summary>
|
||||
protected Vector3 lastPosition;
|
||||
/// <summary>Used for applying Transform rotation to skeleton PhysicsConstraints.</summary>
|
||||
protected float lastRotation;
|
||||
/// <summary>Position delta for threaded processing as Transform access from worker thread is not allowed.</summary>
|
||||
protected Vector3 positionDelta;
|
||||
/// <summary>Rotation delta for threaded processing as Transform access from worker thread is not allowed.</summary>
|
||||
protected float rotationDelta;
|
||||
|
||||
// Threaded Update System Attributes
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
protected static int mainThreadID = -1;
|
||||
|
||||
[SerializeField] protected SettingsTriState threadedMeshGeneration = SettingsTriState.UseGlobalSetting;
|
||||
protected bool isUpdatedExternally = false;
|
||||
protected bool requiresMeshBufferAssignmentMainThread = false;
|
||||
#if UNITY_EDITOR
|
||||
static bool applicationIsPlaying = false;
|
||||
#endif
|
||||
#endif // USE_THREADED_SKELETON_UPDATE
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <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>
|
||||
protected bool editorSkipSkinSync = false;
|
||||
protected bool requiresEditorUpdate = false;
|
||||
#endif
|
||||
#endregion ISkeletonRenderer Attributes
|
||||
|
||||
#region ISkeletonRenderer Properties
|
||||
public ISkeletonRenderer Renderer { get { return this; } }
|
||||
public UnityEngine.MonoBehaviour Component { get { return this; } }
|
||||
public ISkeletonAnimation Animation { get { return skeletonAnimation; } set { skeletonAnimation = value; } }
|
||||
public SkeletonDataAsset SkeletonDataAsset {
|
||||
get { return skeletonDataAsset; }
|
||||
set { skeletonDataAsset = value; }
|
||||
}
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton;
|
||||
}
|
||||
set {
|
||||
skeleton = value;
|
||||
}
|
||||
}
|
||||
public SkeletonData SkeletonData {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton == null ? null : skeleton.Data;
|
||||
}
|
||||
}
|
||||
public bool IsValid { get { return valid; } }
|
||||
|
||||
public string InitialSkinName { get { return initialSkinName; } set { initialSkinName = value; } }
|
||||
/// <summary>Flip X to use when the Skeleton is initialized.</summary>
|
||||
public bool InitialFlipX { get { return initialFlipX; } set { initialFlipX = value; } }
|
||||
/// <summary>Flip Y to use when the Skeleton is initialized.</summary>
|
||||
public bool InitialFlipY { get { return initialFlipY; } set { initialFlipY = value; } }
|
||||
|
||||
/// <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; } }
|
||||
/// <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 { get { return updateWhenInvisible; } set { updateWhenInvisible = value; } }
|
||||
|
||||
public MeshGenerator.Settings MeshSettings { get { return meshSettings; } set { meshSettings = value; } }
|
||||
|
||||
// Submesh Separation
|
||||
/// <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>
|
||||
public List<Slot> SeparatorSlots { get { return separatorSlots; } }
|
||||
public bool EnableSeparatorSlots { get { return enableSeparatorSlots; } set { enableSeparatorSlots = value; } }
|
||||
|
||||
// Overrides Properties
|
||||
public bool HasGenerateMeshOverride { get { return generateMeshOverride != null; } }
|
||||
|
||||
/// <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);
|
||||
DisableRenderers();
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
}
|
||||
}
|
||||
remove {
|
||||
generateMeshOverride -= value;
|
||||
if (disableRenderingOnOverride && generateMeshOverride == null) {
|
||||
Initialize(false);
|
||||
EnableRenderers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
|
||||
public Dictionary<Slot, Material> CustomSlotMaterials {
|
||||
get { materialsNeedUpdate = true; return customSlotMaterials; }
|
||||
}
|
||||
|
||||
// Physics Properties
|
||||
/// <summary>When set to non-zero, Transform position movement in X and Y direction
|
||||
/// is applied to skeleton PhysicsConstraints, multiplied by this scale factor.
|
||||
/// Typical values are <c>Vector2.one</c> to apply XY movement 1:1,
|
||||
/// <c>Vector2(2f, 2f)</c> to apply movement with double intensity,
|
||||
/// <c>Vector2(1f, 0f)</c> to apply only horizontal movement, or
|
||||
/// <c>Vector2.zero</c> to not apply any Transform position movement at all.</summary>
|
||||
public Vector2 PhysicsPositionInheritanceFactor {
|
||||
get {
|
||||
return physicsPositionInheritanceFactor;
|
||||
}
|
||||
set {
|
||||
if (physicsPositionInheritanceFactor == Vector2.zero && value != Vector2.zero) ResetLastPosition();
|
||||
physicsPositionInheritanceFactor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>When set to non-zero, Transform rotation movement is applied to skeleton PhysicsConstraints,
|
||||
/// multiplied by this scale factor. Typical values are <c>1</c> to apply movement 1:1,
|
||||
/// <c>2</c> to apply movement with double intensity, or
|
||||
/// <c>0</c> to not apply any Transform rotation movement at all.</summary>
|
||||
public float PhysicsRotationInheritanceFactor {
|
||||
get {
|
||||
return physicsRotationInheritanceFactor;
|
||||
}
|
||||
set {
|
||||
if (physicsRotationInheritanceFactor == 0f && value != 0f) ResetLastRotation();
|
||||
physicsRotationInheritanceFactor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limits Transform position movement in X and Y direction that is applied to skeleton PhysicsConstraints,
|
||||
/// after it has been multiplied by <see cref="PhysicsPositionInheritanceFactor"/>.
|
||||
/// </summary>
|
||||
public Vector2 PhysicsPositionInheritanceLimit {
|
||||
get { return physicsPositionInheritanceLimit; }
|
||||
set { physicsPositionInheritanceLimit = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limits Transform rotation movement that is applied to skeleton PhysicsConstraints,
|
||||
/// after it has been multiplied by <see cref="PhysicsRotationInheritanceFactor"/>.
|
||||
/// </summary>
|
||||
public float PhysicsRotationInheritanceLimit {
|
||||
get { return physicsRotationInheritanceLimit; }
|
||||
set { physicsRotationInheritanceLimit = value; }
|
||||
}
|
||||
|
||||
/// <summary>Reference transform relative to which physics movement will be calculated, or null to use world location.</summary>
|
||||
public Transform PhysicsMovementRelativeTo {
|
||||
get {
|
||||
return physicsMovementRelativeTo;
|
||||
}
|
||||
set {
|
||||
physicsMovementRelativeTo = value;
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) ResetLastPosition();
|
||||
if (physicsRotationInheritanceFactor != 0f) ResetLastRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// Threaded Update System Properties
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
public bool IsUpdatedExternally {
|
||||
get { return isUpdatedExternally; }
|
||||
set { isUpdatedExternally = value; }
|
||||
}
|
||||
|
||||
protected bool UsesThreadedMeshGeneration {
|
||||
get { return threadedMeshGeneration == SettingsTriState.Enable || RuntimeSettings.UseThreadedMeshGeneration; }
|
||||
}
|
||||
|
||||
public bool RequiresMeshBufferAssignmentMainThread {
|
||||
get { return requiresMeshBufferAssignmentMainThread; }
|
||||
}
|
||||
|
||||
public SettingsTriState ThreadedMeshGeneration {
|
||||
get { return threadedMeshGeneration; }
|
||||
set {
|
||||
if (threadedMeshGeneration == value) return;
|
||||
threadedMeshGeneration = value;
|
||||
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system) {
|
||||
if (threadedMeshGeneration == SettingsTriState.Enable || RuntimeSettings.UseThreadedMeshGeneration)
|
||||
system.RegisterForUpdate(this);
|
||||
else
|
||||
system.UnregisterFromUpdate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// ApplicationIsPlaying for threaded access. Unfortunately Application.isPlaying throws
|
||||
// when called from worker thread.
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return applicationIsPlaying; }
|
||||
set { applicationIsPlaying = value; }
|
||||
}
|
||||
#endif
|
||||
#else // USE_THREADED_SKELETON_UPDATE
|
||||
public bool IsUpdatedExternally {
|
||||
get { return false; }
|
||||
set { }
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
public static bool ApplicationIsPlaying {
|
||||
get { return Application.isPlaying; }
|
||||
set { }
|
||||
}
|
||||
#endif
|
||||
public bool RequiresMeshBufferAssignmentMainThread { get { return true; } }
|
||||
#endif // USE_THREADED_SKELETON_UPDATE
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <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>
|
||||
public bool EditorSkipSkinSync {
|
||||
get { return editorSkipSkinSync; }
|
||||
set { editorSkipSkinSync = value; }
|
||||
}
|
||||
#endif
|
||||
#endregion ISkeletonRenderer Properties
|
||||
|
||||
#region ISkeletonRenderer Methods
|
||||
protected virtual void ClearCommon () {
|
||||
// Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
|
||||
// otherwise constant reloading will be triggered at prefabs.
|
||||
currentInstructions.Clear();
|
||||
rendererBuffers.Clear();
|
||||
ClearMeshGenerator();
|
||||
skeleton = null;
|
||||
valid = false;
|
||||
|
||||
if (skeletonAnimation != null)
|
||||
skeletonAnimation.ClearAnimationState();
|
||||
}
|
||||
|
||||
protected virtual void InitializeCommon (bool overwrite, bool quiet = false) {
|
||||
if (valid && !overwrite)
|
||||
return;
|
||||
|
||||
if (skeletonDataAsset == null || skeletonDataAsset.skeletonJSON == null) {
|
||||
Clear();
|
||||
return;
|
||||
} else if (skeleton != null && skeletonDataAsset.GetSkeletonData(true) != skeleton.Data) {
|
||||
Clear();
|
||||
} else if (this.Freeze) {
|
||||
return;
|
||||
} else {
|
||||
ClearCommon();
|
||||
}
|
||||
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(quiet);
|
||||
if (skeletonData == null) return;
|
||||
|
||||
rendererBuffers.Initialize();
|
||||
|
||||
skeleton = new Skeleton(skeletonData) {
|
||||
ScaleX = initialFlipX ? -1 : 1,
|
||||
ScaleY = initialFlipY ? -1 : 1
|
||||
};
|
||||
valid = true;
|
||||
|
||||
ResetLastPositionAndRotation();
|
||||
AssignInitialSkin();
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0; i < separatorSlotNames.Length; i++)
|
||||
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
|
||||
|
||||
AfterAnimationApplied();
|
||||
wasMeshUpdatedAfterInit = false;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
protected virtual void AssignInitialSkin () {
|
||||
if (string.IsNullOrEmpty(initialSkinName) || string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
|
||||
skeleton.SetSkin((Skin)null);
|
||||
else
|
||||
skeleton.SetSkin(initialSkinName);
|
||||
}
|
||||
|
||||
public void ResetLastPosition () {
|
||||
lastPosition = GetPhysicsTransformPosition();
|
||||
}
|
||||
|
||||
public void ResetLastRotation () {
|
||||
lastRotation = GetPhysicsTransformRotation();
|
||||
}
|
||||
|
||||
public void ResetLastPositionAndRotation () {
|
||||
lastPosition = GetPhysicsTransformPosition();
|
||||
lastRotation = GetPhysicsTransformRotation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers Transform movement for later application in <see cref="ApplyTransformMovementToPhysics"/>.
|
||||
/// Must be called in main thread.
|
||||
/// </summary>
|
||||
public virtual void GatherTransformMovementForPhysics () {
|
||||
#if UNITY_EDITOR
|
||||
bool isPlaying = ApplicationIsPlaying;
|
||||
#else
|
||||
bool isPlaying = true;
|
||||
#endif
|
||||
if (isPlaying) {
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) {
|
||||
Vector3 position = GetPhysicsTransformPosition();
|
||||
positionDelta = (position - lastPosition) / this.MeshScale;
|
||||
|
||||
positionDelta = transform.InverseTransformVector(positionDelta);
|
||||
if (physicsMovementRelativeTo != null) {
|
||||
positionDelta = physicsMovementRelativeTo.TransformVector(positionDelta);
|
||||
}
|
||||
positionDelta.x *= physicsPositionInheritanceFactor.x;
|
||||
positionDelta.y *= physicsPositionInheritanceFactor.y;
|
||||
positionDelta.x = Mathf.Clamp(positionDelta.x, -physicsPositionInheritanceLimit.x, physicsPositionInheritanceLimit.x);
|
||||
positionDelta.y = Mathf.Clamp(positionDelta.y, -physicsPositionInheritanceLimit.y, physicsPositionInheritanceLimit.y);
|
||||
lastPosition = position;
|
||||
}
|
||||
if (physicsRotationInheritanceFactor != 0f) {
|
||||
float rotation = GetPhysicsTransformRotation();
|
||||
rotationDelta = rotation - lastRotation;
|
||||
if (rotationDelta > 180f) rotationDelta -= 360f;
|
||||
else if (rotationDelta < -180f) rotationDelta += 360f;
|
||||
rotationDelta = Mathf.Clamp(rotationDelta, -physicsRotationInheritanceLimit, physicsRotationInheritanceLimit);
|
||||
lastRotation = rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies position and rotation Transform movement previously gathered via
|
||||
/// <see cref="GatherTransformMovementForPhysics"/>. May be called in worker thread.
|
||||
/// </summary>
|
||||
public virtual void ApplyTransformMovementToPhysics () {
|
||||
if (physicsPositionInheritanceFactor != Vector2.zero) {
|
||||
skeleton.PhysicsTranslate(positionDelta.x, positionDelta.y);
|
||||
}
|
||||
if (physicsRotationInheritanceFactor != 0f) {
|
||||
skeleton.PhysicsRotate(0, 0, physicsRotationInheritanceFactor * rotationDelta);
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector3 GetPhysicsTransformPosition () {
|
||||
if (physicsMovementRelativeTo == null) {
|
||||
return transform.position;
|
||||
} else {
|
||||
if (physicsMovementRelativeTo == transform.parent)
|
||||
return transform.localPosition;
|
||||
else
|
||||
return physicsMovementRelativeTo.InverseTransformPoint(transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
protected float GetPhysicsTransformRotation () {
|
||||
if (physicsMovementRelativeTo == null) {
|
||||
return this.transform.rotation.eulerAngles.z;
|
||||
} else {
|
||||
if (physicsMovementRelativeTo == this.transform.parent)
|
||||
return this.transform.localRotation.eulerAngles.z;
|
||||
else {
|
||||
Quaternion relative = Quaternion.Inverse(physicsMovementRelativeTo.rotation) * this.transform.rotation;
|
||||
return relative.eulerAngles.z;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AfterAnimationApplied (bool calledFromMainThread = true) {
|
||||
if (_UpdateLocal != null)
|
||||
_UpdateLocal(this);
|
||||
|
||||
if (_UpdateWorld == null) {
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
} else {
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
_UpdateWorld(this);
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
}
|
||||
|
||||
if (calledFromMainThread && _UpdateComplete != null) {
|
||||
_UpdateComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
public CoroutineIterator AfterAnimationAppliedSplit (CoroutineIterator coroutineIterator) {
|
||||
if (coroutineIterator.IsDone)
|
||||
return CoroutineIterator.Done;
|
||||
|
||||
/*
|
||||
0:
|
||||
if (_UpdateLocal != null) {
|
||||
yield return true; // continue in main thread
|
||||
1:
|
||||
_UpdateLocal(this);
|
||||
yield return false; // continue in worker thread
|
||||
}
|
||||
|
||||
2:
|
||||
if (_UpdateWorld == null) {
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
// goto 5
|
||||
} else {
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
yield return true; // continue in main thread
|
||||
3:
|
||||
_UpdateWorld(this);
|
||||
yield return false; // continue in worker thread
|
||||
4:
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
// goto 5
|
||||
}
|
||||
|
||||
5:
|
||||
if (_UpdateComplete != null) {
|
||||
yield return true; // continue in main thread
|
||||
6:
|
||||
_UpdateComplete(this);
|
||||
// last call, no need to switch back to worker thread.
|
||||
}
|
||||
*/
|
||||
const int StateBits = 3;
|
||||
const uint StateMask = (1 << StateBits) - 1;
|
||||
switch (coroutineIterator.State(StateMask)) {
|
||||
case 0:
|
||||
if (_UpdateLocal != null) {
|
||||
AssertIsWorkerThread();
|
||||
return coroutineIterator.YieldReturnAtState(1, StateMask);
|
||||
} else {
|
||||
goto case 2;
|
||||
}
|
||||
case 1:
|
||||
AssertIsMainThread();
|
||||
_UpdateLocal(this);
|
||||
return coroutineIterator.YieldReturnAtState(2, StateMask);
|
||||
case 2:
|
||||
if (_UpdateWorld == null) {
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
goto case 5;
|
||||
} else {
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Pose);
|
||||
return coroutineIterator.YieldReturnAtState(3, StateMask);
|
||||
}
|
||||
case 3:
|
||||
AssertIsMainThread();
|
||||
_UpdateWorld(this);
|
||||
return coroutineIterator.YieldReturnAtState(4, StateMask);
|
||||
case 4:
|
||||
AssertIsWorkerThread();
|
||||
UpdateWorldTransform(Physics.Update);
|
||||
goto case 5;
|
||||
case 5:
|
||||
if (_UpdateComplete != null) {
|
||||
AssertIsWorkerThread();
|
||||
return coroutineIterator.YieldReturnAtState(6, StateMask);
|
||||
} else {
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
case 6:
|
||||
AssertIsMainThread();
|
||||
_UpdateComplete(this);
|
||||
return CoroutineIterator.Done;
|
||||
default:
|
||||
Debug.LogError(string.Format(
|
||||
"Internal coroutine logic error: SkeletonRenderer.AfterAnimationAppliedSplit state was {0}.",
|
||||
coroutineIterator.State(StateMask)), this);
|
||||
return CoroutineIterator.Done;
|
||||
}
|
||||
}
|
||||
|
||||
protected void IssueOnPostProcessVertices (MeshGeneratorBuffers buffers) {
|
||||
if (OnPostProcessVertices != null)
|
||||
OnPostProcessVertices.Invoke(buffers);
|
||||
}
|
||||
|
||||
protected virtual void UpdateWorldTransform (Physics physics) {
|
||||
skeleton.UpdateWorldTransform(physics);
|
||||
}
|
||||
|
||||
public virtual void UpdateMesh (bool calledFromMainThread = true) {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
bool canPrepareInstructions = calledFromMainThread || !NeedsMainThreadRendererPreparation;
|
||||
#else
|
||||
bool canPrepareInstructions = true;
|
||||
#endif
|
||||
if (canPrepareInstructions)
|
||||
PrepareInstructionsAndRenderers();
|
||||
|
||||
updateTriangles = UpdateBuffersToInstructions(calledFromMainThread);
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (calledFromMainThread) {
|
||||
requiresMeshBufferAssignmentMainThread = false;
|
||||
UpdateMeshAndMaterialsToBuffers();
|
||||
} else {
|
||||
requiresMeshBufferAssignmentMainThread = true;
|
||||
}
|
||||
#else
|
||||
UpdateMeshAndMaterialsToBuffers();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <returns>True if triangles (indices array) need to be updated.</returns>
|
||||
public virtual bool UpdateBuffersToInstructions (bool calledFromMainThread = true) {
|
||||
if (!valid || currentInstructions.rawVertexCount < 0) return false;
|
||||
wasMeshUpdatedAfterInit = true;
|
||||
|
||||
if (this.generateMeshOverride != null) {
|
||||
if (calledFromMainThread)
|
||||
this.generateMeshOverride(currentInstructions);
|
||||
if (disableRenderingOnOverride) return false;
|
||||
}
|
||||
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions = currentInstructions.submeshInstructions;
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
|
||||
|
||||
// Update vertex buffers based on vertices from the attachments and assign buffers to a target UnityEngine.Mesh.
|
||||
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed, calledFromMainThread);
|
||||
FillBuffersFromSubmeshInstructions(workingSubmeshInstructions, currentSmartMesh, updateTriangles);
|
||||
return updateTriangles;
|
||||
}
|
||||
|
||||
public virtual void UpdateMeshAndMaterialsToBuffers () {
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions = currentInstructions.submeshInstructions;
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh = rendererBuffers.GetCurrentMesh();
|
||||
UpdateMeshAndMaterialsToBuffers(workingSubmeshInstructions, currentSmartMesh, updateTriangles);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMeshAndMaterialsToBuffers (
|
||||
ExposedList<SubmeshInstruction> workingSubmeshInstructions, MeshRendererBuffers.SmartMesh currentSmartMesh,
|
||||
bool updateTriangles) {
|
||||
|
||||
FillMeshFromBuffers(currentSmartMesh, updateTriangles);
|
||||
|
||||
bool materialsChanged;
|
||||
rendererBuffers.GatherMaterialsFromInstructions(workingSubmeshInstructions, out materialsChanged);
|
||||
if (materialsChanged || materialsNeedUpdate) {
|
||||
UpdateUsedMaterialsForRenderers(workingSubmeshInstructions);
|
||||
}
|
||||
|
||||
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
if (Application.isPlaying)
|
||||
HandleOnDemandLoading();
|
||||
#endif
|
||||
|
||||
// The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh.
|
||||
AssignMeshAtRenderer(workingSubmeshInstructions, currentSmartMesh);
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public virtual void UpdateMaterials () {
|
||||
UpdateUsedMaterialsForRenderers(currentInstructions.submeshInstructions);
|
||||
}
|
||||
|
||||
// Threading Asserts
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
protected void InitializeMainThreadID () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (mainThreadID == -1)
|
||||
mainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsMainThread () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId != mainThreadID)
|
||||
Debug.LogError("AssertIsMainThread failed: worker thread calling main thread code. Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private void AssertIsWorkerThread () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (System.Threading.Thread.CurrentThread.ManagedThreadId == mainThreadID)
|
||||
Debug.LogError("AssertIsWorkerThread failed: main thread calling worker thread code! Thread ID:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
|
||||
#endif
|
||||
}
|
||||
#endregion ISkeletonRenderer Methods
|
||||
|
||||
#region ISkeletonRenderer Events
|
||||
protected event SkeletonRendererDelegate _UpdateLocal;
|
||||
protected event SkeletonRendererDelegate _UpdateWorld;
|
||||
protected event SkeletonRendererDelegate _UpdateComplete;
|
||||
|
||||
/// <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 SkeletonRendererDelegate 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 SkeletonRendererDelegate 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 SkeletonRendererDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
|
||||
|
||||
/// <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;
|
||||
|
||||
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
|
||||
public event SkeletonRendererDelegate OnRebuild;
|
||||
|
||||
/// <summary>OnInstructionsPrepared is raised at the end of <c>LateUpdate</c> after render instructions
|
||||
/// are done, target renderers are prepared, and the mesh is ready to be generated.</summary>
|
||||
public event InstructionDelegate OnInstructionsPrepared;
|
||||
#endregion ISkeletonRenderer Events
|
||||
#endregion Identical common ISkeletonRenderer code
|
||||
// End of identical code shared by ISkeletonRenderer subclasses as a workaround for single inheritance limitations.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9178134ad83033c48a2e3dec43d8d200
|
||||
timeCreated: 1754676176
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,908 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
#define REVERT_HAS_OVERLOADS
|
||||
#endif
|
||||
|
||||
#if !SPINE_DISABLE_THREADING
|
||||
#define USE_THREADED_SKELETON_UPDATE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
#define SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
[DefaultExecutionOrder(1)]
|
||||
/// <summary>
|
||||
/// Component for managing and rendering a Spine skeleton using a standard MeshRenderer.
|
||||
/// </summary>
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(MeshRenderer)), DisallowMultipleComponent]
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-main-components#SkeletonRenderer-Component")]
|
||||
public partial class SkeletonRenderer : MonoBehaviour, ISkeletonRenderer, IHasSkeletonRenderer, IUpgradable {
|
||||
// Note: Partial class. As a workaround for single inheritance limitations, this class
|
||||
// is split into a common code part (what would be the base class) and a specific code part.
|
||||
// This file covers specific attributes, properties and methods.
|
||||
// Common ISkeletonRenderer members can be found in SkeletonRenderer.Common.cs.
|
||||
|
||||
#region SkeletonRenderer specific code
|
||||
#region Events
|
||||
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
#endregion Events
|
||||
|
||||
#region Attributes
|
||||
// Component References
|
||||
MeshRenderer meshRenderer;
|
||||
MeshFilter meshFilter;
|
||||
|
||||
// Mesh Generation
|
||||
/// <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 USE_THREADED_SKELETON_UPDATE
|
||||
protected MeshGenerator meshGenerator = null;
|
||||
#else
|
||||
protected readonly MeshGenerator meshGenerator = new MeshGenerator();
|
||||
#endif
|
||||
[System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
|
||||
|
||||
[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
|
||||
|
||||
#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
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
/// <seealso cref="MaskInteraction"/>
|
||||
[SerializeField] protected SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
|
||||
|
||||
/// <summary>Cached reference to the already setup material override set at the respective
|
||||
/// SkeletonDataAsset.atlasAssets array entry.</summary>
|
||||
[System.NonSerialized] public MaterialOverrideSet[] insideMaskMaterials = null;
|
||||
[System.NonSerialized] public MaterialOverrideSet[] outsideMaskMaterials = null;
|
||||
|
||||
/// <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;
|
||||
|
||||
public const string MATERIAL_OVERRIDE_SET_INSIDE_MASK_NAME = "InsideMask";
|
||||
public const string MATERIAL_OVERRIDE_SET_OUTSIDE_MASK_NAME = "OutsideMask";
|
||||
#if UNITY_EDITOR
|
||||
private static bool haveStencilParametersBeenFixed = false;
|
||||
#endif
|
||||
#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");
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>Sets the MeshFilter's hide flags to DontSaveInEditor which fixes the prefab
|
||||
/// always being marked as changed, but at the cost of references to the MeshFilter by other
|
||||
/// components being lost.</summary>
|
||||
public SettingsTriState fixPrefabOverrideViaMeshFilter = SettingsTriState.UseGlobalSetting;
|
||||
public static bool fixPrefabOverrideViaMeshFilterGlobal = false;
|
||||
#endif
|
||||
#endregion Attributes
|
||||
|
||||
#region Properties
|
||||
#region General Properties
|
||||
#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 {
|
||||
set {
|
||||
if (maskInteraction == value) return;
|
||||
maskInteraction = value;
|
||||
materialsNeedUpdate = true;
|
||||
}
|
||||
get { return maskInteraction; }
|
||||
}
|
||||
#endif
|
||||
/// <summary>Use this Dictionary to override a Material with a different Material.</summary>
|
||||
public Dictionary<Material, Material> CustomMaterialOverride {
|
||||
get { materialsNeedUpdate = true; return customMaterialOverride; }
|
||||
}
|
||||
|
||||
/// <summary>Returns the <see cref="SkeletonClipping"/> used by this renderer for use with e.g.
|
||||
/// <see cref="Skeleton.GetBounds(out float, out float, out float, out float, ref float[], SkeletonClipping)"/>
|
||||
/// </summary>
|
||||
public SkeletonClipping SkeletonClipping { get { return meshGenerator.SkeletonClipping; } }
|
||||
public bool Freeze { get { return false; } }
|
||||
public float MeshScale { get { return 1; } }
|
||||
public Vector2 MeshOffset { get { return Vector2.zero; } }
|
||||
protected bool UsesSingleSubmesh { get { return singleSubmesh; } }
|
||||
protected bool NeedsToGenerateMesh { get { return (meshRenderer && meshRenderer.enabled) || HasGenerateMeshOverride; } }
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
public virtual bool NeedsMainThreadRendererPreparation {
|
||||
get { return generateMeshOverride != null || OnInstructionsPrepared != null; }
|
||||
}
|
||||
#endif
|
||||
#endregion General Properties
|
||||
|
||||
#region Render Settings Compatibility Properties
|
||||
public float zSpacing { get { return meshSettings.zSpacing; } set { meshSettings.zSpacing = value; } }
|
||||
|
||||
/// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
|
||||
public bool useClipping { get { return meshSettings.useClipping; } set { meshSettings.useClipping = value; } }
|
||||
|
||||
/// <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 { get { return meshSettings.immutableTriangles; } set { meshSettings.immutableTriangles = value; } }
|
||||
|
||||
/// <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 { get { return meshSettings.pmaVertexColors; } set { meshSettings.pmaVertexColors = value; } }
|
||||
|
||||
/// <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 { get { return meshSettings.tintBlack; } set { meshSettings.tintBlack = value; } }
|
||||
|
||||
/// <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>
|
||||
public bool addNormals { get { return meshSettings.addNormals; } set { meshSettings.addNormals = value; } }
|
||||
|
||||
/// <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 { get { return meshSettings.calculateTangents; } set { meshSettings.calculateTangents = value; } }
|
||||
#endregion Render Settings Compatibility Properties
|
||||
#endregion Properties
|
||||
|
||||
#region Methods
|
||||
#region Lifecycle
|
||||
public virtual void Awake () {
|
||||
InitializeMainThreadID();
|
||||
#if UNITY_EDITOR
|
||||
SkeletonRenderer.ApplicationIsPlaying = Application.isPlaying;
|
||||
#endif
|
||||
MeshGenerator.InitializeGlobalSettings();
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (meshGenerator == null)
|
||||
meshGenerator = new MeshGenerator();
|
||||
#endif
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
if (!Application.isPlaying && !wasDeprecatedTransferred) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
#endif
|
||||
#if UNITY_EDITOR && BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
EditorFixStencilCompParameters();
|
||||
#endif
|
||||
|
||||
Initialize(false);
|
||||
if (generateMeshOverride == null || !disableRenderingOnOverride)
|
||||
updateMode = updateWhenInvisible;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE
|
||||
public virtual void Start () {
|
||||
Initialize(false);
|
||||
}
|
||||
#endif
|
||||
/// <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;
|
||||
|
||||
skeletonAnimation = this.GetComponent<ISkeletonAnimation>();
|
||||
if (skeletonAnimation != null)
|
||||
skeletonAnimation.EnsureRendererEventsSubscribed();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (BuildUtilities.IsInSkeletonAssetBuildPreProcessing)
|
||||
return;
|
||||
#endif
|
||||
meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter == null)
|
||||
meshFilter = gameObject.AddComponent<MeshFilter>();
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
|
||||
InitializeCommon(overwrite, quiet);
|
||||
#if UNITY_EDITOR
|
||||
requiresEditorUpdate = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Clear () {
|
||||
ClearMeshAtRenderer();
|
||||
ClearCommon();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh and resets the skeleton's pose.
|
||||
/// Also clears the animation state when a SkeletonAnimation component is
|
||||
/// associated with this SkeletonRenderer</summary>
|
||||
public virtual void ClearState () {
|
||||
ClearSkeletonState();
|
||||
skeletonAnimation.ClearAnimationState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh and resets the skeleton's pose.</summary>
|
||||
public virtual void ClearSkeletonState () {
|
||||
ClearMeshAtRenderer();
|
||||
currentInstructions.Clear();
|
||||
if (skeleton != null) skeleton.SetupPose();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR || USE_THREADED_SKELETON_UPDATE
|
||||
void OnEnable () {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
LateUpdate();
|
||||
}
|
||||
#endif
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (Application.isPlaying && UsesThreadedMeshGeneration && !isUpdatedExternally) {
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system)
|
||||
system.RegisterForUpdate(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif // UNITY_EDITOR || USE_THREADED_SKELETON_UPDATE
|
||||
|
||||
void OnDisable () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (Application.isPlaying && UsesThreadedMeshGeneration) {
|
||||
SkeletonUpdateSystem system = SkeletonUpdateSystem.Instance;
|
||||
if (system)
|
||||
system.UnregisterFromUpdate(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (clearStateOnDisable && valid)
|
||||
ClearState();
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
rendererBuffers.Dispose();
|
||||
valid = false;
|
||||
}
|
||||
|
||||
public void OnBecameVisible () {
|
||||
UpdateMode previousUpdateMode = updateMode;
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
// OnBecameVisible is called after Update and LateUpdate(),
|
||||
// so update if previousUpdateMode didn't already update this frame.
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate) {
|
||||
if (skeletonAnimation != null)
|
||||
skeletonAnimation.OnBecameVisibleFromMode(previousUpdateMode);
|
||||
LateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBecameInvisible () {
|
||||
updateMode = updateWhenInvisible;
|
||||
}
|
||||
|
||||
public void LateUpdate () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (isUpdatedExternally) return;
|
||||
#endif
|
||||
LateUpdateImplementation();
|
||||
}
|
||||
#endregion Lifecycle
|
||||
|
||||
#region Mesh Generation
|
||||
/// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
|
||||
public void SetMeshSettings (MeshGenerator.Settings settings) {
|
||||
meshSettings = settings;
|
||||
}
|
||||
|
||||
protected void ClearMeshGenerator () {
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
if (meshGenerator == null)
|
||||
meshGenerator = new MeshGenerator();
|
||||
#endif
|
||||
meshGenerator.Begin();
|
||||
}
|
||||
|
||||
protected Mesh GetCurrentMesh () {
|
||||
return rendererBuffers.GetCurrentMesh().mesh;
|
||||
}
|
||||
|
||||
protected bool RequiresMultipleSubmeshesByDrawOrder () {
|
||||
if (!valid)
|
||||
return false;
|
||||
return MeshGenerator.RequiresMultipleSubmeshesByDrawOrder(skeleton);
|
||||
}
|
||||
|
||||
public void PrepareInstructionsAndRenderers () {
|
||||
if (UsesSingleSubmesh) {
|
||||
MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
|
||||
} else {
|
||||
GenerateSkeletonRendererInstructions();
|
||||
}
|
||||
if (OnInstructionsPrepared != null)
|
||||
OnInstructionsPrepared(this.currentInstructions);
|
||||
}
|
||||
|
||||
protected virtual void GenerateSkeletonRendererInstructions () {
|
||||
MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials,
|
||||
enableSeparatorSlots ? separatorSlots : null,
|
||||
enableSeparatorSlots ? separatorSlots.Count > 0 : false,
|
||||
meshSettings.immutableTriangles);
|
||||
}
|
||||
|
||||
protected virtual bool FillBuffersFromSubmeshInstructions (ExposedList<SubmeshInstruction> workingSubmeshInstructions,
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh, bool updateTriangles) {
|
||||
return FillSingleBufferFromInstructions(workingSubmeshInstructions, currentSmartMesh, updateTriangles);
|
||||
}
|
||||
|
||||
/// <returns>True if any mesh has been filled with data, false otherwise.</returns>
|
||||
protected virtual bool FillSingleBufferFromInstructions (ExposedList<SubmeshInstruction> workingSubmeshInstructions,
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh, bool updateTriangles) {
|
||||
|
||||
meshGenerator.settings = meshSettings;
|
||||
meshGenerator.Begin();
|
||||
|
||||
if (!currentInstructions.hasActiveClipping)
|
||||
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
|
||||
else if (UsesSingleSubmesh)
|
||||
meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
|
||||
else
|
||||
meshGenerator.BuildMesh(currentInstructions, updateTriangles);
|
||||
|
||||
currentSmartMesh.instructionUsed.Set(currentInstructions);
|
||||
IssueOnPostProcessVertices(meshGenerator.Buffers);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool FillMeshFromBuffers (MeshRendererBuffers.SmartMesh currentSmartMesh, bool updateTriangles) {
|
||||
Mesh currentMesh = currentSmartMesh.mesh;
|
||||
meshGenerator.FillVertexData(currentMesh);
|
||||
if (updateTriangles) { // Check if the triangles should also be updated.
|
||||
meshGenerator.FillTriangles(currentMesh);
|
||||
}
|
||||
meshGenerator.FillLateVertexData(currentMesh);
|
||||
return true;
|
||||
}
|
||||
#endregion Mesh Generation
|
||||
|
||||
#region Renderer Assignment
|
||||
protected void EnableRenderers () {
|
||||
if (meshRenderer)
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
|
||||
protected void DisableRenderers () {
|
||||
if (meshRenderer)
|
||||
meshRenderer.enabled = false;
|
||||
}
|
||||
|
||||
protected virtual void AssignMeshAtRenderer (ExposedList<SubmeshInstruction> workingSubmeshInstructions,
|
||||
MeshRendererBuffers.SmartMesh currentSmartMesh) {
|
||||
|
||||
Mesh currentMesh = currentSmartMesh.mesh;
|
||||
AssignMeshAtRenderer(currentMesh);
|
||||
}
|
||||
|
||||
protected virtual void AssignMeshAtRenderer (UnityEngine.Mesh mesh) {
|
||||
if (meshFilter != null)
|
||||
meshFilter.sharedMesh = mesh;
|
||||
|
||||
if (meshRenderer != null) {
|
||||
meshRenderer.sharedMaterials = rendererBuffers.sharedMaterials;
|
||||
}
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
|
||||
SetMaterialSettingsToFixDrawOrder();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected void ClearMeshAtRenderer () {
|
||||
if (meshFilter == null) meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter != null) meshFilter.sharedMesh = null;
|
||||
}
|
||||
#endregion Renderer Assignment
|
||||
|
||||
#region Separator Slots
|
||||
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();
|
||||
|
||||
ExposedList<Slot> slots = skeleton.Slots;
|
||||
foreach (Slot slot in slots) {
|
||||
if (slotNamePredicate.Invoke(slot.Data.Name))
|
||||
separatorSlots.Add(slot);
|
||||
}
|
||||
|
||||
if (updateStringArray) {
|
||||
List<string> detectedSeparatorNames = new List<string>();
|
||||
foreach (Slot slot in skeleton.Slots) {
|
||||
string slotName = slot.Data.Name;
|
||||
if (slotNamePredicate.Invoke(slotName))
|
||||
detectedSeparatorNames.Add(slotName);
|
||||
}
|
||||
if (!clearExistingSeparators) {
|
||||
string[] originalNames = separatorSlotNames;
|
||||
foreach (string originalName in originalNames)
|
||||
detectedSeparatorNames.Add(originalName);
|
||||
}
|
||||
separatorSlotNames = detectedSeparatorNames.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void ReapplySeparatorSlotNames () {
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
|
||||
Slot 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
|
||||
}
|
||||
}
|
||||
#endregion Separator Slots
|
||||
|
||||
#region Material Configuration
|
||||
protected virtual void UpdateUsedMaterialsForRenderers (ExposedList<SubmeshInstruction> instructions) {
|
||||
rendererBuffers.UpdateSharedMaterialsArray();
|
||||
ConfigureMaterials(instructions);
|
||||
materialsNeedUpdate = false;
|
||||
}
|
||||
|
||||
public virtual void ConfigureMaterials (Material[] sharedMaterials, ExposedList<SubmeshInstruction> instructions) {
|
||||
if (customMaterialOverride.Count > 0) {
|
||||
for (int i = 0, count = sharedMaterials.Length; i < count; ++i) {
|
||||
Material material = sharedMaterials[i];
|
||||
if (material == null) continue;
|
||||
|
||||
Material overrideMaterial;
|
||||
if (customMaterialOverride.TryGetValue(material, out overrideMaterial))
|
||||
sharedMaterials[i] = overrideMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
|
||||
if (insideMaskMaterials == null || insideMaskMaterials.Length == 0) {
|
||||
InitSpriteMaskMaterialsMaskMode(ref insideMaskMaterials, this,
|
||||
MATERIAL_OVERRIDE_SET_INSIDE_MASK_NAME, STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE);
|
||||
if (insideMaskMaterials == null) return;
|
||||
}
|
||||
foreach (MaterialOverrideSet overrideSet in insideMaskMaterials) {
|
||||
overrideSet.ApplyOverrideTo(sharedMaterials);
|
||||
}
|
||||
} else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
|
||||
if (outsideMaskMaterials == null || outsideMaskMaterials.Length == 0) {
|
||||
InitSpriteMaskMaterialsMaskMode(ref outsideMaskMaterials, this,
|
||||
MATERIAL_OVERRIDE_SET_OUTSIDE_MASK_NAME, STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE);
|
||||
if (outsideMaskMaterials == null) return;
|
||||
}
|
||||
foreach (MaterialOverrideSet overrideSet in outsideMaskMaterials) {
|
||||
overrideSet.ApplyOverrideTo(sharedMaterials);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected virtual void ConfigureMaterials (ExposedList<SubmeshInstruction> instructions) {
|
||||
ConfigureMaterials(rendererBuffers.sharedMaterials, instructions);
|
||||
}
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
private void InitSpriteMaskMaterialsMaskMode (ref MaterialOverrideSet[] maskMaterials,
|
||||
SkeletonRenderer skeletonRenderer, string overrideSetName,
|
||||
UnityEngine.Rendering.CompareFunction maskFunction) {
|
||||
|
||||
AtlasAssetBase[] atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
|
||||
int atlasAssetCount = atlasAssets.Length;
|
||||
if (maskMaterials == null || maskMaterials.Length != atlasAssetCount)
|
||||
maskMaterials = new MaterialOverrideSet[atlasAssetCount];
|
||||
|
||||
for (int i = 0, n = atlasAssetCount; i < n; ++i) {
|
||||
AtlasAssetBase atlasAsset = atlasAssets[i];
|
||||
maskMaterials[i] = atlasAsset.GetMaterialOverrideSet(overrideSetName);
|
||||
if (maskMaterials[i] == null) {
|
||||
#if UNITY_EDITOR
|
||||
// Editor script shall create assets.
|
||||
if (!Application.isPlaying) {
|
||||
maskMaterials = null;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
maskMaterials[i] = InitSpriteMaskOverrideSet(
|
||||
atlasAsset, overrideSetName, maskFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MaterialOverrideSet InitSpriteMaskOverrideSet (
|
||||
AtlasAssetBase atlasAsset, string overrideSetName, UnityEngine.Rendering.CompareFunction maskFunction) {
|
||||
|
||||
MaterialOverrideSet overrideSet = atlasAsset.AddMaterialOverrideSet(overrideSetName);
|
||||
foreach (Material originalMaterial in atlasAsset.Materials) {
|
||||
if (originalMaterial == null)
|
||||
continue;
|
||||
Material maskMaterial = new Material(originalMaterial);
|
||||
maskMaterial.name += overrideSetName;
|
||||
maskMaterial.SetFloat(SkeletonRenderer.STENCIL_COMP_PARAM_ID, (int)maskFunction);
|
||||
overrideSet.AddOverride(originalMaterial, maskMaterial);
|
||||
}
|
||||
return overrideSet;
|
||||
}
|
||||
#endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
/// <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
|
||||
|
||||
#if SPINE_OPTIONAL_ON_DEMAND_LOADING
|
||||
void HandleOnDemandLoading () {
|
||||
foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) {
|
||||
if (atlasAsset.TextureLoadingMode != AtlasAssetBase.LoadingMode.Normal) {
|
||||
atlasAsset.BeginCustomTextureLoading();
|
||||
Material[] materials = rendererBuffers.sharedMaterials;
|
||||
for (int i = 0, count = materials.Length; i < count; ++i) {
|
||||
Material overrideMaterial = null;
|
||||
atlasAsset.RequireTexturesLoaded(materials[i], ref overrideMaterial);
|
||||
if (overrideMaterial != null)
|
||||
materials[i] = overrideMaterial;
|
||||
}
|
||||
atlasAsset.EndCustomTextureLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion Material Configuration
|
||||
|
||||
#region Runtime Instantiation
|
||||
public static SkeletonComponents<Renderer, Animation> NewSpineGameObject<Renderer, Animation> (
|
||||
SkeletonDataAsset skeletonDataAsset, bool quiet = false)
|
||||
where Renderer : MonoBehaviour, ISkeletonRenderer
|
||||
where Animation : SkeletonAnimationBase {
|
||||
|
||||
return SkeletonRenderer.AddSpineComponents<Renderer, Animation>(new GameObject("New Spine GameObject"), skeletonDataAsset, quiet);
|
||||
}
|
||||
|
||||
/// <summary>Add and prepare a Spine animation component and the default
|
||||
/// SkeletonRenderer components to a GameObject at runtime.</summary>
|
||||
/// <typeparam name="Animation">Animation should be SkeletonAnimation, SkeletonMecanim or any custom derived class.</typeparam>
|
||||
public static SkeletonComponents<Renderer, Animation> AddSpineComponents<Renderer, Animation> (
|
||||
GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false)
|
||||
where Renderer : MonoBehaviour, ISkeletonRenderer
|
||||
where Animation : SkeletonAnimationBase {
|
||||
|
||||
Renderer rendererComponent = gameObject.AddComponent<Renderer>();
|
||||
if (skeletonDataAsset != null) {
|
||||
rendererComponent.SkeletonDataAsset = skeletonDataAsset;
|
||||
rendererComponent.Initialize(false, quiet);
|
||||
}
|
||||
Animation animationComponent = gameObject.AddComponent<Animation>();
|
||||
if (skeletonDataAsset != null) {
|
||||
animationComponent.Initialize(false, quiet);
|
||||
}
|
||||
rendererComponent.Animation = animationComponent;
|
||||
return new SkeletonComponents<Renderer, Animation>(rendererComponent, animationComponent);
|
||||
}
|
||||
#endregion Runtime Instantiation
|
||||
|
||||
#region Internal Methods
|
||||
#if USE_THREADED_SKELETON_UPDATE
|
||||
public virtual void MainThreadPrepareLateUpdateInternal () {
|
||||
if (!valid) return;
|
||||
if (updateMode != UpdateMode.FullUpdate && wasMeshUpdatedAfterInit) return;
|
||||
|
||||
if (NeedsMainThreadRendererPreparation)
|
||||
PrepareInstructionsAndRenderers();
|
||||
|
||||
if (generateMeshOverride != null)
|
||||
generateMeshOverride(currentInstructions);
|
||||
}
|
||||
#else
|
||||
public virtual void MainThreadPrepareLateUpdateInternal () {
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
|
||||
public virtual void LateUpdateImplementation (bool calledFromMainThread = true) {
|
||||
if (calledFromMainThread) {
|
||||
#if UNITY_EDITOR && NEW_PREFAB_SYSTEM
|
||||
// Don't store mesh or material at the prefab, otherwise it will permanently reload
|
||||
UnityEditor.PrefabAssetType prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
|
||||
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
|
||||
(prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
|
||||
return;
|
||||
}
|
||||
EditorUpdateMeshFilterHideFlags();
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying && requiresEditorUpdate) {
|
||||
Initialize(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!valid) return;
|
||||
|
||||
// instantiation can happen from Update() after this component, leading to a missing Update() call.
|
||||
if (calledFromMainThread && skeletonAnimation != null)
|
||||
skeletonAnimation.UpdateOncePerFrame(0);
|
||||
|
||||
// Generate mesh once, required to update mesh bounds for visibility
|
||||
if (updateMode != UpdateMode.FullUpdate && wasMeshUpdatedAfterInit) return;
|
||||
if (calledFromMainThread && !NeedsToGenerateMesh) return;
|
||||
UpdateMesh(calledFromMainThread);
|
||||
}
|
||||
#endregion Internal Methods
|
||||
#region Editor Methods
|
||||
#if UNITY_EDITOR
|
||||
protected void OnValidate () {
|
||||
requiresEditorUpdate = true;
|
||||
}
|
||||
|
||||
// revert each prefab override only once each editor-frame.
|
||||
private static int lastPrefabRevertFrame = -1;
|
||||
private static HashSet<MeshFilter> revertedPrefabMeshes = new HashSet<MeshFilter>();
|
||||
private static bool preventReentrance = false;
|
||||
|
||||
public void EditorUpdateMeshFilterHideFlags () {
|
||||
if (!meshFilter) {
|
||||
meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter == null)
|
||||
meshFilter = gameObject.AddComponent<MeshFilter>();
|
||||
}
|
||||
|
||||
bool dontSaveInEditor = false;
|
||||
if (fixPrefabOverrideViaMeshFilter == SettingsTriState.Enable ||
|
||||
(fixPrefabOverrideViaMeshFilter == SettingsTriState.UseGlobalSetting &&
|
||||
fixPrefabOverrideViaMeshFilterGlobal))
|
||||
dontSaveInEditor = true;
|
||||
|
||||
if (dontSaveInEditor) {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
int currentFrame = Time.frameCount;
|
||||
if (lastPrefabRevertFrame != currentFrame) {
|
||||
lastPrefabRevertFrame = currentFrame;
|
||||
revertedPrefabMeshes.Clear();
|
||||
}
|
||||
|
||||
if (!preventReentrance && UnityEditor.PrefabUtility.IsPartOfAnyPrefab(meshFilter)) {
|
||||
if (!revertedPrefabMeshes.Contains(meshFilter)) {
|
||||
GameObject instanceRoot = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(meshFilter);
|
||||
if (instanceRoot != null) {
|
||||
UnityEditor.PropertyModification[] mods = UnityEditor.PrefabUtility.GetPropertyModifications(instanceRoot);
|
||||
bool hasMeshOverride = false;
|
||||
if (mods != null) {
|
||||
foreach (var mod in mods) {
|
||||
if (mod.target == meshFilter && mod.propertyPath == "m_Mesh") {
|
||||
hasMeshOverride = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasMeshOverride) {
|
||||
preventReentrance = true;
|
||||
try {
|
||||
List<ObjectOverride> objectOverrides = UnityEditor.PrefabUtility.GetObjectOverrides(instanceRoot);
|
||||
foreach (ObjectOverride objectOverride in objectOverrides) {
|
||||
if (objectOverride.instanceObject == meshFilter) {
|
||||
#if REVERT_HAS_OVERLOADS
|
||||
objectOverride.Revert(UnityEditor.InteractionMode.AutomatedAction);
|
||||
#else
|
||||
objectOverride.Revert();
|
||||
#endif
|
||||
revertedPrefabMeshes.Add(meshFilter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
preventReentrance = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
meshFilter.hideFlags = HideFlags.DontSaveInEditor;
|
||||
} else {
|
||||
meshFilter.hideFlags = HideFlags.None;
|
||||
}
|
||||
}
|
||||
|
||||
private void EditorFixStencilCompParameters () {
|
||||
if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
|
||||
haveStencilParametersBeenFixed = true;
|
||||
FixAllProjectMaterialsStencilCompParameters();
|
||||
}
|
||||
}
|
||||
|
||||
private void FixAllProjectMaterialsStencilCompParameters () {
|
||||
string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
|
||||
foreach (string guid in materialGUIDS) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
Material 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 (Material 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
|
||||
|
||||
#endregion Editor Methods
|
||||
#region Transfer of Deprecated Fields
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public void UpgradeTo43 () {
|
||||
TransferDeprecatedFields();
|
||||
}
|
||||
|
||||
protected virtual void TransferDeprecatedFields () {
|
||||
wasDeprecatedTransferred = true;
|
||||
// only transfer once, let SkeletonAnimation transfer properties if present
|
||||
if (this.Animation != null) return;
|
||||
|
||||
meshSettings.zSpacing = this.zSpacingDeprecated;
|
||||
meshSettings.useClipping = this.useClippingDeprecated;
|
||||
meshSettings.immutableTriangles = this.immutableTrianglesDeprecated;
|
||||
meshSettings.pmaVertexColors = this.pmaVertexColorsDeprecated;
|
||||
meshSettings.tintBlack = this.tintBlackDeprecated;
|
||||
meshSettings.addNormals = this.addNormalsDeprecated;
|
||||
meshSettings.calculateTangents = this.calculateTangentsDeprecated;
|
||||
}
|
||||
|
||||
[SerializeField] protected bool wasDeprecatedTransferred = false;
|
||||
[FormerlySerializedAs("zSpacing")] [SerializeField] private float zSpacingDeprecated = 0f;
|
||||
[FormerlySerializedAs("useClipping")] [SerializeField] private bool useClippingDeprecated = true;
|
||||
[FormerlySerializedAs("immutableTriangles")] [SerializeField] private bool immutableTrianglesDeprecated = false;
|
||||
[FormerlySerializedAs("pmaVertexColors")] [SerializeField] private bool pmaVertexColorsDeprecated = true;
|
||||
[FormerlySerializedAs("tintBlack")] [SerializeField] private bool tintBlackDeprecated = false;
|
||||
[FormerlySerializedAs("calculateNormals"),
|
||||
FormerlySerializedAs("addNormals")]
|
||||
[SerializeField] private bool addNormalsDeprecated = false;
|
||||
[FormerlySerializedAs("calculateTangents")] [SerializeField] private bool calculateTangentsDeprecated = false;
|
||||
#endif // UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endregion Transfer of Deprecated Fields
|
||||
#endregion Methods
|
||||
#endregion SkeletonRenderer specific code
|
||||
}
|
||||
|
||||
public struct SkeletonComponents<Renderer, Animation> {
|
||||
public Renderer skeletonRenderer;
|
||||
public Animation skeletonAnimation;
|
||||
|
||||
public SkeletonComponents (Renderer skeletonRenderer, Animation skeletonAnimation) {
|
||||
this.skeletonRenderer = skeletonRenderer;
|
||||
this.skeletonAnimation = skeletonAnimation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e075b9a3e08e2f74fbd651c858ab16ed
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7236dbdc6a4e5a4989483dac97aee0b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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("https://esotericsoftware.com/spine-unity-utility-components#SkeletonGraphicCustomMaterials")]
|
||||
public class SkeletonGraphicCustomMaterials : MonoBehaviour {
|
||||
#region Inspector
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
[SerializeField] protected List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
|
||||
[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) {
|
||||
AtlasAssetBase[] atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
|
||||
|
||||
List<AtlasMaterialOverride> initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = new AtlasMaterialOverride {
|
||||
overrideEnabled = false,
|
||||
originalTexture = atlasMaterial.mainTexture
|
||||
};
|
||||
|
||||
initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
|
||||
}
|
||||
}
|
||||
customMaterialOverrides = initialAtlasMaterialOverrides;
|
||||
}
|
||||
|
||||
// Populate texture list
|
||||
if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
|
||||
AtlasAssetBase[] atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
|
||||
|
||||
List<AtlasTextureOverride> initialAtlasTextureOverrides = new List<AtlasTextureOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
AtlasTextureOverride atlasTextureOverride = new AtlasTextureOverride {
|
||||
overrideEnabled = false,
|
||||
originalTexture = atlasMaterial.mainTexture
|
||||
};
|
||||
|
||||
initialAtlasTextureOverrides.Add(atlasTextureOverride);
|
||||
}
|
||||
}
|
||||
customTextureOverrides = initialAtlasTextureOverrides;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
void SetCustomSlotMaterials () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customSlotMaterials.Count; i++) {
|
||||
SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
|
||||
if (!slotMaterialOverride.overrideEnabled || string.IsNullOrEmpty(slotMaterialOverride.slotName))
|
||||
continue;
|
||||
|
||||
Slot slotObject = skeletonGraphic.skeleton.FindSlot(slotMaterialOverride.slotName);
|
||||
if (slotObject != null)
|
||||
skeletonGraphic.CustomSlotMaterials[slotObject] = slotMaterialOverride.material;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomSlotMaterials () {
|
||||
if (skeletonGraphic == 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 = skeletonGraphic.skeleton.FindSlot(slotMaterialOverride.slotName);
|
||||
if (slotObject == null)
|
||||
continue;
|
||||
Material currentMaterial;
|
||||
if (!skeletonGraphic.CustomSlotMaterials.TryGetValue(slotObject, out currentMaterial))
|
||||
continue;
|
||||
|
||||
// Do not revert the material if it was changed by something else
|
||||
if (currentMaterial != slotMaterialOverride.material)
|
||||
continue;
|
||||
|
||||
skeletonGraphic.CustomSlotMaterials.Remove(slotObject);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
SetCustomSlotMaterials();
|
||||
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();
|
||||
RemoveCustomSlotMaterials();
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct SlotMaterialOverride : IEquatable<SlotMaterialOverride> {
|
||||
public bool overrideEnabled;
|
||||
|
||||
[SpineSlot]
|
||||
public string slotName;
|
||||
public Material material;
|
||||
|
||||
public bool Equals (SlotMaterialOverride other) {
|
||||
return overrideEnabled == other.overrideEnabled && slotName == other.slotName && material == other.material;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c8717e10b272bf42b05d363ac2679a6
|
||||
timeCreated: 1588789074
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
// Contributed by: Lost Polygon
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-utility-components#SkeletonRendererCustomMaterials")]
|
||||
public class SkeletonRendererCustomMaterials : MonoBehaviour, IUpgradable {
|
||||
|
||||
#region Inspector
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
[SerializeField] protected List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
|
||||
[SerializeField] protected List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#if AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Reset () {
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
|
||||
// Populate atlas list
|
||||
if (skeletonRenderer != null && skeletonRenderer.skeletonDataAsset != null) {
|
||||
AtlasAssetBase[] atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
|
||||
|
||||
List<AtlasMaterialOverride> initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
AtlasMaterialOverride 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);
|
||||
if (slotObject != null)
|
||||
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);
|
||||
if (slotObject == null)
|
||||
continue;
|
||||
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;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customMaterialOverrides.Count; i++) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
|
||||
if (atlasMaterialOverride.overrideDisabled)
|
||||
continue;
|
||||
|
||||
skeletonRenderer.CustomMaterialOverride[atlasMaterialOverride.originalMaterial] = atlasMaterialOverride.replacementMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterialOverrides () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at SkeletonRendererCustomMaterials, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26947ae098a8447408d80c0c86e35b48
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6e0caaafe294de48af468a6a9321473
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
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, IUpgradable {
|
||||
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
public GameObject activeOnNormalX;
|
||||
public GameObject activeOnFlippedX;
|
||||
HingeJoint2D[] jointsNormalX;
|
||||
HingeJoint2D[] jointsFlippedX;
|
||||
ISkeletonComponent skeletonComponent;
|
||||
|
||||
bool wasFlippedXBefore = false;
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
protected void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
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) {
|
||||
HingeJoint2D joint = joints[i];
|
||||
Transform 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;
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public virtual void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null && skeletonGraphic == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at ActivateBasedOnFlipDirection, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] protected bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ae96e4f2feb654681a2f16e4effeec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc20d5e917562341a5007777a9d0db2
|
||||
timeCreated: 1571763023
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02aae87c39b869548a9051fbdb1975e6
|
||||
timeCreated: 1572012493
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 456a736ebb92ebf4b959fa9c4b704427
|
||||
timeCreated: 1571763206
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+534
@@ -0,0 +1,534 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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_6000_0_OR_NEWER
|
||||
#define USE_RIGIDBODY_BODY_TYPE
|
||||
#endif
|
||||
|
||||
#if !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(ISkeletonRenderer))]
|
||||
[HelpURL("https://esotericsoftware.com/spine-unity-utility-components#SkeletonUtility")]
|
||||
public sealed class SkeletonUtility : MonoBehaviour, IUpgradable {
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
Slot slot = skeleton.FindSlot(slotName);
|
||||
Attachment attachment = slot != null ? skin.GetAttachment(slot.Data.Index, attachmentName) : null;
|
||||
if (attachment == null) {
|
||||
Debug.LogFormat("Attachment in slot '{0}' named '{1}' not found in skin '{2}'.", slotName, attachmentName, skin.Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
BoundingBoxAttachment box = attachment as BoundingBoxAttachment;
|
||||
if (box != null) {
|
||||
return AddBoundingBoxGameObject(box.Name, box, skeleton, slot, parent, isTrigger);
|
||||
} else {
|
||||
Debug.LogFormat("Attachment '{0}' was not a Bounding Box.", attachmentName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PolygonCollider2D AddBoundingBoxGameObject (string name, BoundingBoxAttachment box, Skeleton skeleton, Slot slot, Transform parent, bool isTrigger = true) {
|
||||
GameObject go = new GameObject("[BoundingBox]" + (string.IsNullOrEmpty(name) ? box.Name : name));
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Spawn BoundingBox");
|
||||
#endif
|
||||
Transform got = go.transform;
|
||||
got.parent = parent;
|
||||
got.localPosition = Vector3.zero;
|
||||
got.localRotation = Quaternion.identity;
|
||||
got.localScale = Vector3.one;
|
||||
return AddBoundingBoxAsComponent(box, skeleton, slot, go, isTrigger);
|
||||
}
|
||||
|
||||
public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Skeleton skeleton, Slot slot, GameObject gameObject, bool isTrigger = true) {
|
||||
if (box == null) return null;
|
||||
PolygonCollider2D collider = gameObject.AddComponent<PolygonCollider2D>();
|
||||
collider.isTrigger = isTrigger;
|
||||
SetColliderPointsLocal(collider, skeleton, slot, box);
|
||||
return collider;
|
||||
}
|
||||
|
||||
public static void SetColliderPointsLocal (PolygonCollider2D collider, Skeleton skeleton, 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.");
|
||||
Vector2[] verts = box.GetLocalVertices(skeleton, 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) {
|
||||
Rigidbody2D rb = gameObject.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) {
|
||||
rb = gameObject.AddComponent<Rigidbody2D>();
|
||||
#if USE_RIGIDBODY_BODY_TYPE
|
||||
rb.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
|
||||
#else
|
||||
rb.isKinematic = isKinematic;
|
||||
#endif
|
||||
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 () {
|
||||
Skeleton 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 (skeletonGraphic != null) {
|
||||
positionScale = skeletonGraphic.MeshScale;
|
||||
lastPositionScale = positionScale;
|
||||
if (boneRoot) {
|
||||
positionOffset = skeletonGraphic.MeshOffset;
|
||||
if (positionOffset != Vector2.zero) {
|
||||
boneRoot.localPosition = positionOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateToMeshScaleAndOffset (MeshGeneratorBuffers ignoredParameter) {
|
||||
if (skeletonGraphic == null) return;
|
||||
|
||||
positionScale = skeletonGraphic.MeshScale;
|
||||
if (boneRoot) {
|
||||
positionOffset = skeletonGraphic.MeshOffset;
|
||||
if (positionOffset != Vector2.zero) {
|
||||
boneRoot.localPosition = positionOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: skeletonGraphic.MeshScale and MeshOffset can be one frame behind in Update() above.
|
||||
// Unfortunately update order is:
|
||||
// 1. SkeletonGraphic.Update updating skeleton bones and calling UpdateWorld callback,
|
||||
// calling SkeletonUtilityBone.DoUpdate() reading hierarchy.PositionScale.
|
||||
// 2. Layout change triggers SkeletonGraphic.Rebuild, updating MeshScale and MeshOffset.
|
||||
// Thus to prevent a one-frame-behind offset after a layout change affecting mesh scale,
|
||||
// we have to re-evaluate the callbacks via the lines below.
|
||||
if (lastPositionScale != positionScale) {
|
||||
UpdateLocal(skeletonGraphic);
|
||||
UpdateWorld(skeletonGraphic);
|
||||
UpdateComplete(skeletonGraphic);
|
||||
}
|
||||
}
|
||||
|
||||
[HideInInspector] public SkeletonRenderer skeletonRenderer;
|
||||
[HideInInspector] public SkeletonGraphic skeletonGraphic;
|
||||
|
||||
private ISkeletonRenderer skeletonComponent;
|
||||
[System.NonSerialized] public List<SkeletonUtilityBone> boneComponents = new List<SkeletonUtilityBone>();
|
||||
[System.NonSerialized] public List<SkeletonUtilityConstraint> constraintComponents = new List<SkeletonUtilityConstraint>();
|
||||
|
||||
|
||||
public ISkeletonComponent SkeletonComponent { get { return this.SkeletonRenderer; } }
|
||||
|
||||
public ISkeletonRenderer SkeletonRenderer {
|
||||
get {
|
||||
if (skeletonComponent == null) {
|
||||
skeletonComponent = skeletonRenderer != null ? skeletonRenderer :
|
||||
skeletonGraphic != null ? skeletonGraphic :
|
||||
GetComponent<ISkeletonRenderer>();
|
||||
}
|
||||
return skeletonComponent;
|
||||
}
|
||||
}
|
||||
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
if (SkeletonComponent == null)
|
||||
return null;
|
||||
return skeletonComponent.Skeleton;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid {
|
||||
get {
|
||||
ISkeletonRenderer skeletonComponent = this.SkeletonRenderer;
|
||||
return (skeletonComponent != null && skeletonComponent.IsValid);
|
||||
}
|
||||
}
|
||||
|
||||
public float PositionScale { get { return positionScale; } }
|
||||
public Vector2 PositionOffset { get { return positionOffset; } }
|
||||
|
||||
float positionScale = 1.0f;
|
||||
float lastPositionScale = 1.0f;
|
||||
Vector2 positionOffset = Vector2.zero;
|
||||
bool hasOverrideBones;
|
||||
bool hasConstraintTargetBones;
|
||||
bool needToReprocessBones;
|
||||
|
||||
public void OnUtilityBoneChanged () {
|
||||
needToReprocessBones = true;
|
||||
}
|
||||
|
||||
public void ResubscribeEvents () {
|
||||
ResubscribeIndependentEvents();
|
||||
ResubscribeDependentEvents();
|
||||
}
|
||||
|
||||
void ResubscribeIndependentEvents () {
|
||||
ISkeletonRenderer skeletonComponent = this.SkeletonRenderer;
|
||||
if (skeletonComponent != null) {
|
||||
skeletonComponent.OnRebuild -= HandleRendererReset;
|
||||
skeletonComponent.OnRebuild += HandleRendererReset;
|
||||
}
|
||||
if (skeletonGraphic != null) {
|
||||
skeletonGraphic.OnPostProcessVertices -= UpdateToMeshScaleAndOffset;
|
||||
skeletonGraphic.OnPostProcessVertices += UpdateToMeshScaleAndOffset;
|
||||
}
|
||||
}
|
||||
|
||||
void ResubscribeDependentEvents () {
|
||||
ISkeletonRenderer skeletonComponent = this.SkeletonRenderer;
|
||||
if (skeletonComponent != null) {
|
||||
skeletonComponent.UpdateLocal -= UpdateLocal;
|
||||
skeletonComponent.UpdateWorld -= UpdateWorld;
|
||||
skeletonComponent.UpdateComplete -= UpdateComplete;
|
||||
|
||||
bool hasConstraintComponents = constraintComponents.Count > 0;
|
||||
if (hasOverrideBones || !hasConstraintTargetBones)
|
||||
skeletonComponent.UpdateLocal += UpdateLocal;
|
||||
if (hasOverrideBones || hasConstraintComponents)
|
||||
skeletonComponent.UpdateWorld += UpdateWorld;
|
||||
if (hasConstraintTargetBones)
|
||||
skeletonComponent.UpdateComplete += UpdateComplete;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (skeletonRenderer == null) {
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
}
|
||||
if (skeletonGraphic == null) {
|
||||
skeletonGraphic = GetComponent<SkeletonGraphic>();
|
||||
}
|
||||
CollectBones();
|
||||
ResubscribeEvents();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
void Awake () {
|
||||
if (!Application.isPlaying && !wasUpgradedTo43) {
|
||||
UpgradeTo43();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Start () {
|
||||
//recollect because order of operations failure when switching between game mode and edit mode...
|
||||
CollectBones();
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
ISkeletonRenderer skeletonComponent = this.SkeletonRenderer;
|
||||
if (skeletonComponent != null) {
|
||||
skeletonComponent.OnRebuild -= HandleRendererReset;
|
||||
skeletonComponent.UpdateLocal -= UpdateLocal;
|
||||
skeletonComponent.UpdateWorld -= UpdateWorld;
|
||||
skeletonComponent.UpdateComplete -= UpdateComplete;
|
||||
}
|
||||
if (skeletonGraphic) {
|
||||
skeletonGraphic.OnPostProcessVertices -= UpdateToMeshScaleAndOffset;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRendererReset (ISkeletonRenderer r) {
|
||||
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 () {
|
||||
ISkeletonRenderer skeletonComponent = this.SkeletonRenderer;
|
||||
if (skeletonComponent == null) return;
|
||||
Skeleton skeleton = skeletonComponent.Skeleton;
|
||||
if (skeleton == null) return;
|
||||
|
||||
if (boneRoot != null) {
|
||||
hasOverrideBones = false;
|
||||
hasConstraintTargetBones = false;
|
||||
|
||||
List<Bone> constrainedBones = new List<Bone>();
|
||||
ExposedList<IConstraint> constraints = skeleton.Constraints;
|
||||
for (int i = 0, n = constraints.Count; i < n; i++) {
|
||||
IConstraint constraint = constraints.Items[i];
|
||||
ExposedList<BonePose> bones = null;
|
||||
if (constraint is IkConstraint)
|
||||
bones = ((IkConstraint)constraint).Bones;
|
||||
else if (constraint is TransformConstraint)
|
||||
bones = ((TransformConstraint)constraint).Bones;
|
||||
else if (constraint is PathConstraint)
|
||||
bones = ((PathConstraint)constraint).Bones;
|
||||
if (bones != null) {
|
||||
for (int j = 0, m = bones.Count; j < m; j++)
|
||||
constrainedBones.Add(bones.Items[j].bone);
|
||||
}
|
||||
}
|
||||
|
||||
List<SkeletonUtilityBone> boneComponents = this.boneComponents;
|
||||
for (int i = 0, n = boneComponents.Count; i < n; i++) {
|
||||
SkeletonUtilityBone b = boneComponents[i];
|
||||
if (b.bone == null) {
|
||||
b.DoUpdate(SkeletonUtilityBone.UpdatePhase.Local);
|
||||
if (b.bone == null) continue;
|
||||
}
|
||||
hasOverrideBones |= (b.mode == SkeletonUtilityBone.Mode.Override);
|
||||
hasConstraintTargetBones |= constrainedBones.Contains(b.bone);
|
||||
}
|
||||
|
||||
needToReprocessBones = false;
|
||||
} else {
|
||||
boneComponents.Clear();
|
||||
constraintComponents.Clear();
|
||||
}
|
||||
ResubscribeDependentEvents();
|
||||
}
|
||||
|
||||
void UpdateLocal (ISkeletonRenderer skeletonRenderer) {
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Local);
|
||||
}
|
||||
|
||||
void UpdateWorld (ISkeletonRenderer skeletonRenderer) {
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.World);
|
||||
for (int i = 0, n = constraintComponents.Count; i < n; i++)
|
||||
constraintComponents[i].DoUpdate();
|
||||
}
|
||||
|
||||
void UpdateComplete (ISkeletonRenderer skeletonRenderer) {
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Complete);
|
||||
}
|
||||
|
||||
void UpdateAllBones (SkeletonUtilityBone.UpdatePhase phase) {
|
||||
if (boneRoot == null || needToReprocessBones)
|
||||
CollectBones();
|
||||
|
||||
List<SkeletonUtilityBone> 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;
|
||||
|
||||
GameObject 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>();
|
||||
|
||||
Transform 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) {
|
||||
var bonePose = b.bone.AppliedPose;
|
||||
if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, bonePose.Rotation);
|
||||
if (pos) goTransform.localPosition = new Vector3(
|
||||
bonePose.X * positionScale + positionOffset.x, bonePose.Y * positionScale + positionOffset.y, 0);
|
||||
goTransform.localScale = new Vector3(bonePose.ScaleX, bonePose.ScaleY, 0);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
#region Transfer of Deprecated Fields
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
public void UpgradeTo43 () {
|
||||
wasUpgradedTo43 = true;
|
||||
if (skeletonRenderer == null && skeletonGraphic == null) {
|
||||
Component previousReference = previousSkeletonRenderer != null ? previousSkeletonRenderer : this;
|
||||
skeletonRenderer = previousReference.GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null)
|
||||
Debug.LogError("Please manually re-assign SkeletonRenderer at SkeletonUtility, " +
|
||||
"automatic upgrade failed.", this);
|
||||
}
|
||||
}
|
||||
[SerializeField, HideInInspector, FormerlySerializedAs("skeletonRenderer")] Component previousSkeletonRenderer;
|
||||
[SerializeField] bool wasUpgradedTo43 = false;
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f726fb798ad621458c431cb9966d91d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 UnityEngine.Serialization;
|
||||
|
||||
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("https://esotericsoftware.com/spine-unity-utility-components#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;
|
||||
[SerializeField, FormerlySerializedAs("mode")] Mode boneMode;
|
||||
public Mode mode {
|
||||
get { return boneMode; }
|
||||
set {
|
||||
if (boneMode != value) {
|
||||
boneMode = value;
|
||||
if (hierarchy != null)
|
||||
hierarchy.OnUtilityBoneChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
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 valid;
|
||||
Transform cachedTransform;
|
||||
Transform skeletonTransform;
|
||||
|
||||
Vector3 TransformLocalPosition { get { return cachedTransform.localPosition; } }
|
||||
Quaternion TransformLocalRotation { get { return cachedTransform.localRotation; } }
|
||||
Vector3 TransformLocalScale { get { return cachedTransform.localScale; } }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
bool incompatibleTransformMode;
|
||||
public bool IncompatibleTransformMode { get { return incompatibleTransformMode; } }
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Skeleton 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;
|
||||
|
||||
Transform thisTransform = cachedTransform;
|
||||
float skeletonFlipRotation = Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY);
|
||||
if (mode == Mode.Follow) {
|
||||
var bonePose = bone.Pose;
|
||||
switch (phase) {
|
||||
case UpdatePhase.Local:
|
||||
if (position)
|
||||
thisTransform.localPosition = new Vector3(bonePose.X * positionScale, bonePose.Y * positionScale,
|
||||
zPosition ? 0 : thisTransform.localPosition.z);
|
||||
|
||||
if (rotation) {
|
||||
if (bone.Data.GetSetupPose().Inherit.InheritsRotation()) {
|
||||
thisTransform.localRotation = Quaternion.Euler(0, 0, bonePose.Rotation);
|
||||
} else {
|
||||
Vector3 euler = skeletonTransform.rotation.eulerAngles;
|
||||
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.AppliedPose.WorldRotationX * skeletonFlipRotation));
|
||||
}
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
thisTransform.localScale = new Vector3(bonePose.ScaleX, bonePose.ScaleY, 1f);
|
||||
#if UNITY_EDITOR
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case UpdatePhase.World:
|
||||
case UpdatePhase.Complete:
|
||||
var appliedPose = bone.AppliedPose;
|
||||
appliedPose.ValidateLocalTransform(skeleton);
|
||||
if (position)
|
||||
thisTransform.localPosition = new Vector3(appliedPose.X * positionScale, appliedPose.Y * positionScale,
|
||||
zPosition ? 0 : thisTransform.localPosition.z);
|
||||
|
||||
if (rotation) {
|
||||
if (bone.Data.GetSetupPose().Inherit.InheritsRotation()) {
|
||||
thisTransform.localRotation = Quaternion.Euler(0, 0, appliedPose.Rotation);
|
||||
} else {
|
||||
Vector3 euler = skeletonTransform.rotation.eulerAngles;
|
||||
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (appliedPose.WorldRotationX * skeletonFlipRotation));
|
||||
}
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
thisTransform.localScale = new Vector3(appliedPose.ScaleX, appliedPose.ScaleY, 1f);
|
||||
#if UNITY_EDITOR
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (mode == Mode.Override) {
|
||||
if (phase != UpdatePhase.Local)
|
||||
return;
|
||||
var bonePose = bone.Pose;
|
||||
if (parentReference == null) {
|
||||
if (position) {
|
||||
Vector3 clp = TransformLocalPosition / positionScale;
|
||||
bonePose.X = Mathf.Lerp(bonePose.X, clp.x, overrideAlpha);
|
||||
bonePose.Y = Mathf.Lerp(bonePose.Y, clp.y, overrideAlpha);
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
float angle = Mathf.LerpAngle(bonePose.Rotation, TransformLocalRotation.eulerAngles.z, overrideAlpha);
|
||||
bonePose.Rotation = angle;
|
||||
bone.AppliedPose.Rotation = angle;
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
Vector3 cls = TransformLocalScale;
|
||||
bonePose.ScaleX = Mathf.Lerp(bonePose.ScaleX, cls.x, overrideAlpha);
|
||||
bonePose.ScaleY = Mathf.Lerp(bonePose.ScaleY, cls.y, overrideAlpha);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (position) {
|
||||
Vector3 pos = parentReference.InverseTransformPoint(thisTransform.position) / positionScale;
|
||||
bonePose.X = Mathf.Lerp(bonePose.X, pos.x, overrideAlpha);
|
||||
bonePose.Y = Mathf.Lerp(bonePose.Y, pos.y, overrideAlpha);
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
float angle = Mathf.LerpAngle(bonePose.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(thisTransform.up)).eulerAngles.z, overrideAlpha);
|
||||
bonePose.Rotation = angle;
|
||||
bone.AppliedPose.Rotation = angle;
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
Vector3 cls = TransformLocalScale;
|
||||
bonePose.ScaleX = Mathf.Lerp(bonePose.ScaleX, cls.x, overrideAlpha);
|
||||
bonePose.ScaleY = Mathf.Lerp(bonePose.ScaleY, cls.y, overrideAlpha);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool BoneTransformModeIncompatible (Bone bone) {
|
||||
return !bone.Data.GetSetupPose().Inherit.InheritsScale();
|
||||
}
|
||||
|
||||
public void AddBoundingBox (Skeleton skeleton, string skinName, string slotName, string attachmentName) {
|
||||
SkeletonUtility.AddBoneRigidbody2D(transform.gameObject);
|
||||
SkeletonUtility.AddBoundingBoxGameObject(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
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b238dfcde8209044b97d23f62bcaadf6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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("https://esotericsoftware.com/spine-unity-utility-components#SkeletonUtilityConstraint")]
|
||||
public abstract class SkeletonUtilityConstraint : MonoBehaviour {
|
||||
|
||||
protected SkeletonUtilityBone bone;
|
||||
protected SkeletonUtility hierarchy;
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
bone = GetComponent<SkeletonUtilityBone>();
|
||||
hierarchy = transform.GetComponentInParent<SkeletonUtility>();
|
||||
hierarchy.RegisterBone(bone); // prevent update order issues
|
||||
hierarchy.RegisterConstraint(this);
|
||||
}
|
||||
|
||||
protected virtual void OnDisable () {
|
||||
hierarchy.UnregisterConstraint(this);
|
||||
}
|
||||
|
||||
public abstract void DoUpdate ();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 522dbfcc6c916df4396f14f35048d185
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,204 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2026, 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 !SPINE_AUTO_UPGRADE_COMPONENTS_OFF
|
||||
#define AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
public enum UpdateMode {
|
||||
Nothing = 0,
|
||||
OnlyAnimationStatus,
|
||||
OnlyEventTimelines = 4, // added as index 4 to keep scene behavior unchanged.
|
||||
EverythingExceptMesh = 2,
|
||||
FullUpdate,
|
||||
//Reserved 4 for OnlyEventTimelines
|
||||
};
|
||||
|
||||
public enum UpdateTiming {
|
||||
ManualUpdate = 0,
|
||||
InUpdate,
|
||||
InFixedUpdate,
|
||||
InLateUpdate
|
||||
}
|
||||
|
||||
public delegate void SkeletonAnimationDelegate (ISkeletonAnimation animated);
|
||||
public delegate void SkeletonRendererDelegate (ISkeletonRenderer skeletonRenderer);
|
||||
public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
|
||||
|
||||
public interface ISpineComponent { }
|
||||
public static class ISpineComponentExtensions {
|
||||
public static bool IsNullOrDestroyed (this ISpineComponent component) {
|
||||
if (component == null) return true;
|
||||
return (UnityEngine.Object)component == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that animates a Skeleton but not necessarily with a Spine.AnimationState.</summary>
|
||||
public interface ISkeletonAnimation : IHasSkeletonRenderer, ISpineComponent {
|
||||
event SkeletonAnimationDelegate OnAnimationRebuild;
|
||||
event SkeletonAnimationDelegate BeforeUpdate;
|
||||
event SkeletonAnimationDelegate BeforeApply;
|
||||
event SkeletonRendererDelegate UpdateLocal;
|
||||
event SkeletonRendererDelegate UpdateWorld;
|
||||
event SkeletonRendererDelegate UpdateComplete;
|
||||
MonoBehaviour Component { get; }
|
||||
Skeleton Skeleton { get; }
|
||||
|
||||
bool IsValid { get; }
|
||||
UpdateMode UpdateMode { get; set; }
|
||||
UpdateTiming UpdateTiming { get; set; }
|
||||
|
||||
void EnsureRendererEventsSubscribed ();
|
||||
void Initialize (bool overwrite, bool quiet = false, bool calledFromRendererCallback = false);
|
||||
void InitializeAnimationComponent ();
|
||||
void ClearAnimationState ();
|
||||
void ApplyAnimation (bool calledFromMainThread = true);
|
||||
CoroutineIterator ApplyAnimationSplit (CoroutineIterator coroutineIterator);
|
||||
void UpdateOncePerFrame (float deltaTime);
|
||||
void Update (float deltaTime);
|
||||
void OnBecameVisibleFromMode (UpdateMode previousUpdateMode);
|
||||
}
|
||||
|
||||
/// <summary>Holds a reference to a SkeletonDataAsset.</summary>
|
||||
public interface IHasSkeletonDataAsset : ISpineComponent {
|
||||
/// <summary>Gets the SkeletonDataAsset of the Spine Component.</summary>
|
||||
SkeletonDataAsset SkeletonDataAsset { get; }
|
||||
}
|
||||
|
||||
public interface IHasModifyableSkeletonDataAsset : IHasSkeletonDataAsset, ISpineComponent {
|
||||
/// <summary>Set the SkeletonDataAsset of the Spine Component.
|
||||
/// Initialize(overwrite:true) has to be called at the target component
|
||||
/// afterwards.</summary>
|
||||
new SkeletonDataAsset SkeletonDataAsset { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that manages a Spine.Skeleton instance, instantiated from a SkeletonDataAsset.</summary>
|
||||
public interface ISkeletonComponent : IHasModifyableSkeletonDataAsset, ISpineComponent {
|
||||
/// <summary>Accesses the Spine.Skeleton instance of the Spine Component. This is equivalent to SkeletonRenderer's .skeleton.</summary>
|
||||
Skeleton Skeleton { get; set; }
|
||||
|
||||
MonoBehaviour Component { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that uses a Spine.AnimationState to animate its skeleton.</summary>
|
||||
public interface IAnimationStateComponent : ISpineComponent {
|
||||
/// <summary>Gets the Spine.AnimationState of the animated Spine Component. This is equivalent to SkeletonAnimation.state.</summary>
|
||||
AnimationState AnimationState { get; }
|
||||
|
||||
/// <summary>If enabled, AnimationState time is advanced by Unscaled Game Time
|
||||
/// (<c>Time.unscaledDeltaTime</c> instead of the default Game Time(<c>Time.deltaTime</c>).
|
||||
/// to animate independent of game <c>Time.timeScale</c>.
|
||||
/// Instance SkeletonGraphic.timeScale and SkeletonAnimation.timeScale will still be applied.</summary>
|
||||
bool UnscaledTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that holds a reference to a skeleton renderer.</summary>
|
||||
public interface IHasSkeletonRenderer : ISpineComponent {
|
||||
ISkeletonRenderer Renderer { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that holds a reference to an ISkeletonComponent.</summary>
|
||||
public interface IHasSkeletonComponent : ISpineComponent {
|
||||
ISkeletonComponent SkeletonComponent { get; }
|
||||
}
|
||||
|
||||
public interface ISkeletonRendererEvents {
|
||||
event SkeletonRendererDelegate UpdateLocal;
|
||||
event SkeletonRendererDelegate UpdateWorld;
|
||||
event SkeletonRendererDelegate UpdateComplete;
|
||||
|
||||
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
|
||||
event SkeletonRendererDelegate OnRebuild;
|
||||
|
||||
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
}
|
||||
|
||||
public interface IThreadedRenderer {
|
||||
bool IsUpdatedExternally { get; set; }
|
||||
bool RequiresMeshBufferAssignmentMainThread { get; }
|
||||
void UpdateMeshAndMaterialsToBuffers ();
|
||||
void MainThreadPrepareLateUpdateInternal();
|
||||
void LateUpdateImplementation (bool calledFromMainThread);
|
||||
}
|
||||
|
||||
// combined interfaces
|
||||
public interface ISkeletonRenderer : ISkeletonComponent, ISkeletonRendererEvents,
|
||||
IHasModifyableSkeletonDataAsset, IThreadedRenderer {
|
||||
|
||||
ISkeletonAnimation Animation { get; set; }
|
||||
Dictionary<Slot, Material> CustomSlotMaterials { get; }
|
||||
bool EnableSeparatorSlots { get; set; }
|
||||
List<Slot> SeparatorSlots { get; }
|
||||
bool Freeze { get; }
|
||||
bool HasGenerateMeshOverride { get; }
|
||||
string InitialSkinName { get; set; }
|
||||
bool IsValid { get; }
|
||||
MeshGenerator.Settings MeshSettings { get; set; }
|
||||
UpdateMode UpdateMode { get; set; }
|
||||
UpdateMode UpdateWhenInvisible { get; set; }
|
||||
float MeshScale { get; }
|
||||
Vector2 MeshOffset { get; }
|
||||
Vector2 PhysicsPositionInheritanceFactor { get; set; }
|
||||
float PhysicsRotationInheritanceFactor { get; set; }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
bool EditorSkipSkinSync { get; set; }
|
||||
#endif
|
||||
|
||||
event Spine.Unity.InstructionDelegate GenerateMeshOverride;
|
||||
event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
|
||||
|
||||
void ClearSkeletonState ();
|
||||
void ClearState ();
|
||||
void Initialize (bool overwrite, bool quiet = false);
|
||||
void GatherTransformMovementForPhysics ();
|
||||
void ApplyTransformMovementToPhysics();
|
||||
void AfterAnimationApplied (bool calledFromMainThread = true);
|
||||
CoroutineIterator AfterAnimationAppliedSplit (CoroutineIterator coroutineIterator);
|
||||
void LateUpdate ();
|
||||
void UpdateMesh (bool calledFromMainThread = true);
|
||||
void UpdateMaterials ();
|
||||
void FindAndApplySeparatorSlots (Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false);
|
||||
void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false);
|
||||
void ReapplySeparatorSlotNames ();
|
||||
}
|
||||
|
||||
public interface IUpgradable {
|
||||
#if UNITY_EDITOR && AUTO_UPGRADE_TO_43_COMPONENTS
|
||||
void UpgradeTo43 ();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7b480b941568134891f411137bfbf55
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43897010c7e77c54897cb98c1ddf84f1
|
||||
folderAsset: yes
|
||||
timeCreated: 1455128695
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3a20550c5d72724ba4dc07eb0c12fb0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e179aebfa743e6c43aa4a8a7de5f9172
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SkeletonGraphicAdditive-CanvasGroup
|
||||
m_Shader: {fileID: 4800000, guid: a1e4ab34b07f81e4e94f0d241c416669, type: 3}
|
||||
m_ShaderKeywords: _CANVAS_GROUP_COMPATIBLE
|
||||
m_LightmapFlags: 5
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _AlphaTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Floats:
|
||||
- PixelSnap: 0
|
||||
- _BumpScale: 1
|
||||
- _CanvasGroupCompatible: 1
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _EnableExternalAlpha: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _OutlineMipLevel: 0
|
||||
- _OutlineOpaqueAlpha: 1
|
||||
- _OutlineReferenceTexWidth: 1024
|
||||
- _OutlineSmoothness: 1
|
||||
- _OutlineWidth: 3
|
||||
- _Parallax: 0.02
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _StraightAlphaInput: 0
|
||||
- _ThresholdEnd: 0.25
|
||||
- _UVSec: 0
|
||||
- _Use8Neighbourhood: 1
|
||||
- _UseUIAlphaClip: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _Flip: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _OutlineColor: {r: 1, g: 1, b: 0, a: 1}
|
||||
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e764190161692c419740074bda0982c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SkeletonGraphicDefault-CanvasGroup
|
||||
m_Shader: {fileID: 4800000, guid: fa95b0fb6983c0f40a152e6f9aa82bfb, type: 3}
|
||||
m_ShaderKeywords: _CANVAS_GROUP_COMPATIBLE
|
||||
m_LightmapFlags: 5
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _AlphaTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Floats:
|
||||
- PixelSnap: 0
|
||||
- _BumpScale: 1
|
||||
- _CanvasGroupCompatible: 1
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _EnableExternalAlpha: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _OutlineMipLevel: 0
|
||||
- _OutlineOpaqueAlpha: 1
|
||||
- _OutlineReferenceTexWidth: 1024
|
||||
- _OutlineSmoothness: 1
|
||||
- _OutlineWidth: 3
|
||||
- _Parallax: 0.02
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _StraightAlphaInput: 0
|
||||
- _ThresholdEnd: 0.25
|
||||
- _UVSec: 0
|
||||
- _Use8Neighbourhood: 1
|
||||
- _UseUIAlphaClip: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _Flip: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _OutlineColor: {r: 1, g: 1, b: 0, a: 1}
|
||||
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 066d2ecbc1c3c0d4292b48cd9b457715
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user