added simple UI
This commit is contained in:
9
Assets/Spine/Runtime/spine-unity/Asset Types.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Asset Types.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dc4b7c23385e8c43ad19d01cbed78ce
|
||||
folderAsset: yes
|
||||
timeCreated: 1455489521
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#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; } }
|
||||
|
||||
public Animation Animation {
|
||||
get {
|
||||
#if AUTOINIT_SPINEREFERENCE
|
||||
if (animation == null)
|
||||
Initialize();
|
||||
#endif
|
||||
|
||||
return animation;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
if (skeletonDataAsset == null) return;
|
||||
this.animation = skeletonDataAsset.GetSkeletonData(AnimationReferenceAsset.QuietSkeletonData).FindAnimation(animationName);
|
||||
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:
|
||||
@@ -0,0 +1,44 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.Collections;
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 787a36933c1c6e14db2104c01ed92dcb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using Spine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[System.Serializable]
|
||||
public class BlendModeMaterials {
|
||||
|
||||
[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; } }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void TransferSettingsFrom (BlendModeMaterialsAsset modifierAsset) {
|
||||
applyAdditiveMaterial = modifierAsset.applyAdditiveMaterial;
|
||||
}
|
||||
|
||||
public bool UpdateBlendmodeMaterialsRequiredState (SkeletonData skeletonData) {
|
||||
requiresBlendModeMaterials = false;
|
||||
|
||||
if (skeletonData == null) throw new ArgumentNullException("skeletonData");
|
||||
|
||||
var skinEntries = new List<Skin.SkinEntry>();
|
||||
var slotsItems = skeletonData.Slots.Items;
|
||||
for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
|
||||
var slot = slotsItems[slotIndex];
|
||||
if (slot.blendMode == BlendMode.Normal) continue;
|
||||
if (!applyAdditiveMaterial && slot.blendMode == BlendMode.Additive) continue;
|
||||
|
||||
skinEntries.Clear();
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (var entry in skinEntries) {
|
||||
if (entry.Attachment is IHasRendererObject) {
|
||||
requiresBlendModeMaterials = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
public void ApplyMaterials (SkeletonData skeletonData) {
|
||||
if (skeletonData == null) throw new ArgumentNullException("skeletonData");
|
||||
if (!requiresBlendModeMaterials)
|
||||
return;
|
||||
|
||||
var skinEntries = new List<Skin.SkinEntry>();
|
||||
var slotsItems = skeletonData.Slots.Items;
|
||||
for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
|
||||
var 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 (var skin in skeletonData.Skins)
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (var entry in skinEntries) {
|
||||
var renderableAttachment = entry.Attachment as IHasRendererObject;
|
||||
if (renderableAttachment != null) {
|
||||
renderableAttachment.RendererObject = CloneAtlasRegionWithMaterial(
|
||||
(AtlasRegion)renderableAttachment.RendererObject, replacementMaterials);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected AtlasRegion CloneAtlasRegionWithMaterial (AtlasRegion originalRegion, List<ReplacementMaterial> replacementMaterials) {
|
||||
var newRegion = originalRegion.Clone();
|
||||
Material material = null;
|
||||
foreach (var replacement in replacementMaterials) {
|
||||
if (replacement.pageName == originalRegion.page.name) {
|
||||
material = replacement.material;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AtlasPage originalPage = originalRegion.page;
|
||||
var 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 January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#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,83 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
public class RegionlessAttachmentLoader : AttachmentLoader {
|
||||
|
||||
static AtlasRegion emptyRegion;
|
||||
static AtlasRegion EmptyRegion {
|
||||
get {
|
||||
if (emptyRegion == null) {
|
||||
emptyRegion = new AtlasRegion {
|
||||
name = "Empty AtlasRegion",
|
||||
page = new AtlasPage {
|
||||
name = "Empty AtlasPage",
|
||||
rendererObject = new Material(Shader.Find("Spine/Special/HiddenPass")) { name = "NoRender Material" }
|
||||
}
|
||||
};
|
||||
}
|
||||
return emptyRegion;
|
||||
}
|
||||
}
|
||||
|
||||
public RegionAttachment NewRegionAttachment (Skin skin, string name, string path) {
|
||||
RegionAttachment attachment = new RegionAttachment(name) {
|
||||
RendererObject = EmptyRegion
|
||||
};
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public MeshAttachment NewMeshAttachment (Skin skin, string name, string path) {
|
||||
MeshAttachment attachment = new MeshAttachment(name) {
|
||||
RendererObject = EmptyRegion
|
||||
};
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, string name) {
|
||||
return new BoundingBoxAttachment(name);
|
||||
}
|
||||
|
||||
public PathAttachment NewPathAttachment (Skin skin, string name) {
|
||||
return new PathAttachment(name);
|
||||
}
|
||||
|
||||
public PointAttachment NewPointAttachment (Skin skin, string name) {
|
||||
return new PointAttachment(name);
|
||||
}
|
||||
|
||||
public ClippingAttachment NewClippingAttachment (Skin skin, string name) {
|
||||
return new ClippingAttachment(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,262 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
[CreateAssetMenu(fileName = "New SkeletonDataAsset", menuName = "Spine/SkeletonData Asset")]
|
||||
public class SkeletonDataAsset : ScriptableObject {
|
||||
#region Inspector
|
||||
public AtlasAssetBase[] atlasAssets = new AtlasAssetBase[0];
|
||||
|
||||
#if SPINE_TK2D
|
||||
public tk2dSpriteCollectionData spriteCollection;
|
||||
public float scale = 1f;
|
||||
#else
|
||||
public float scale = 0.01f;
|
||||
#endif
|
||||
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;
|
||||
|
||||
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.</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;
|
||||
}
|
||||
#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. Pass false to prevent direct errors from being logged.</summary>
|
||||
public SkeletonData GetSkeletonData (bool quiet) {
|
||||
if (skeletonJSON == null) {
|
||||
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 !SPINE_TK2D
|
||||
// if (atlasAssets.Length == 0) {
|
||||
// Clear();
|
||||
// return null;
|
||||
// }
|
||||
// #else
|
||||
// if (atlasAssets.Length == 0 && spriteCollection == null) {
|
||||
// Clear();
|
||||
// return null;
|
||||
// }
|
||||
// #endif
|
||||
|
||||
if (skeletonData != null)
|
||||
return skeletonData;
|
||||
|
||||
AttachmentLoader attachmentLoader;
|
||||
float skeletonDataScale;
|
||||
Atlas[] atlasArray = this.GetAtlasArray();
|
||||
|
||||
#if !SPINE_TK2D
|
||||
attachmentLoader = (atlasArray.Length == 0) ? (AttachmentLoader)new RegionlessAttachmentLoader() : (AttachmentLoader)new AtlasAttachmentLoader(atlasArray);
|
||||
skeletonDataScale = scale;
|
||||
#else
|
||||
if (spriteCollection != null) {
|
||||
attachmentLoader = new Spine.Unity.TK2D.SpriteCollectionAttachmentLoader(spriteCollection);
|
||||
skeletonDataScale = (1.0f / (spriteCollection.invOrthoSize * spriteCollection.halfTargetHeight) * scale);
|
||||
} else {
|
||||
if (atlasArray.Length == 0) {
|
||||
Reset();
|
||||
if (!quiet) Debug.LogError("Atlas not set for SkeletonData asset: " + name, this);
|
||||
return null;
|
||||
}
|
||||
attachmentLoader = new AtlasAttachmentLoader(atlasArray);
|
||||
skeletonDataScale = scale;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool hasBinaryExtension = skeletonJSON.name.ToLower().Contains(".skel");
|
||||
SkeletonData loadedSkeletonData = null;
|
||||
|
||||
try {
|
||||
if (hasBinaryExtension)
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes, attachmentLoader, skeletonDataScale);
|
||||
else
|
||||
loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.text, attachmentLoader, skeletonDataScale);
|
||||
} catch (Exception ex) {
|
||||
if (!quiet)
|
||||
Debug.LogError("Error reading skeleton JSON file for SkeletonData asset: " + name + "\n" + ex.Message + "\n" + 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 (var 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 () {
|
||||
if (stateData != null) {
|
||||
stateData.defaultMix = defaultMix;
|
||||
|
||||
for (int i = 0, n = fromAnimation.Length; i < n; i++) {
|
||||
if (fromAnimation[i].Length == 0 || toAnimation[i].Length == 0)
|
||||
continue;
|
||||
stateData.SetMix(fromAnimation[i], toAnimation[i], duration[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Atlas[] GetAtlasArray () {
|
||||
var returnList = new System.Collections.Generic.List<Atlas>(atlasAssets.Length);
|
||||
for (int i = 0; i < atlasAssets.Length; i++) {
|
||||
var aa = atlasAssets[i];
|
||||
if (aa == null) continue;
|
||||
var a = aa.GetAtlas();
|
||||
if (a == null) continue;
|
||||
returnList.Add(a);
|
||||
}
|
||||
return returnList.ToArray();
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (byte[] bytes, AttachmentLoader attachmentLoader, float scale) {
|
||||
using (var input = new MemoryStream(bytes)) {
|
||||
var binary = new SkeletonBinary(attachmentLoader) {
|
||||
Scale = scale
|
||||
};
|
||||
return binary.ReadSkeletonData(input);
|
||||
}
|
||||
}
|
||||
|
||||
internal static SkeletonData ReadSkeletonData (string text, AttachmentLoader attachmentLoader, float scale) {
|
||||
var input = new StringReader(text);
|
||||
var 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,215 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
#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[] { 3, 8, 0 } };
|
||||
static readonly int[][] compatibleJsonVersions = { new[] { 3, 8, 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 {
|
||||
using (var memStream = new MemoryStream(asset.bytes)) {
|
||||
fileVersion.rawVersion = SkeletonBinary.GetVersionString(memStream);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
var 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")) {
|
||||
var 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;
|
||||
}
|
||||
|
||||
var versionSplit = fileVersion.rawVersion.Split('.');
|
||||
try {
|
||||
fileVersion.version = new[]{ int.Parse(versionSplit[0], CultureInfo.InvariantCulture),
|
||||
int.Parse(versionSplit[1], CultureInfo.InvariantCulture) };
|
||||
}
|
||||
catch (System.Exception e) {
|
||||
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) {
|
||||
byte[] content = file.bytes;
|
||||
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;
|
||||
for (; i < numCharsToCheck; ++i) {
|
||||
char c = (char)content[i];
|
||||
if (char.IsWhiteSpace(c))
|
||||
continue;
|
||||
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 (var 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 January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using 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:
|
||||
245
Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs
Normal file
245
Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using Spine;
|
||||
|
||||
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;
|
||||
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>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText, Material[] materials, bool initialize) {
|
||||
SpineAtlasAsset atlasAsset = ScriptableObject.CreateInstance<SpineAtlasAsset>();
|
||||
atlasAsset.Reset();
|
||||
atlasAsset.atlasFile = atlasText;
|
||||
atlasAsset.materials = materials;
|
||||
|
||||
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. <seealso cref="Spine.Unity.SpineAtlasAsset.CreateRuntimeInstance(TextAsset, Material[], bool)"/></summary>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText, Texture2D[] textures, Material materialPropertySource, bool initialize) {
|
||||
// Get atlas page names.
|
||||
string atlasString = atlasText.text;
|
||||
atlasString = atlasString.Replace("\r", "");
|
||||
string[] atlasLines = atlasString.Split('\n');
|
||||
var pages = new List<string>();
|
||||
for (int i = 0; i < atlasLines.Length - 1; i++) {
|
||||
if (atlasLines[i].Trim().Length == 0)
|
||||
pages.Add(atlasLines[i + 1].Trim().Replace(".png", ""));
|
||||
}
|
||||
|
||||
// Populate Materials[] by matching texture names with page names.
|
||||
var 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];
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a runtime AtlasAsset. Only providing the textures is slower because it has to search for atlas page matches. <seealso cref="Spine.Unity.AtlasAssetBase.CreateRuntimeInstance(TextAsset, Material[], bool)"/></summary>
|
||||
public static SpineAtlasAsset CreateRuntimeInstance (TextAsset atlasText, Texture2D[] textures, Shader shader, bool initialize) {
|
||||
if (shader == null)
|
||||
shader = Shader.Find("Spine/Skeleton");
|
||||
|
||||
Material materialProperySource = new Material(shader);
|
||||
var oa = CreateRuntimeInstance(atlasText, textures, materialProperySource, initialize);
|
||||
|
||||
return oa;
|
||||
}
|
||||
#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 () {
|
||||
if (atlasFile == null) {
|
||||
Debug.LogError("Atlas file not set for atlas asset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (materials == null || materials.Length == 0) {
|
||||
Debug.LogError("Materials not set for atlas asset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (atlas != null) return atlas;
|
||||
|
||||
try {
|
||||
atlas = new Atlas(new StringReader(atlasFile.text), "", new MaterialsTextureLoader(this));
|
||||
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.rotate) {
|
||||
uvs[0] = new Vector2(u, v2);
|
||||
uvs[1] = new Vector2(u, v);
|
||||
uvs[2] = new Vector2(u2, v);
|
||||
uvs[3] = new Vector2(u2, v2);
|
||||
} else {
|
||||
uvs[0] = new Vector2(u2, v2);
|
||||
uvs[1] = new Vector2(u, v2);
|
||||
uvs[2] = new Vector2(u, v);
|
||||
uvs[3] = new Vector2(u2, v);
|
||||
}
|
||||
|
||||
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 MaterialsTextureLoader : TextureLoader {
|
||||
SpineAtlasAsset atlasAsset;
|
||||
|
||||
public MaterialsTextureLoader (SpineAtlasAsset atlasAsset) {
|
||||
this.atlasAsset = atlasAsset;
|
||||
}
|
||||
|
||||
public void Load (AtlasPage page, string path) {
|
||||
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;
|
||||
}
|
||||
if (other.mainTexture.name == 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,397 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_2_OR_NEWER
|
||||
#define EXPOSES_SPRITE_ATLAS_UTILITIES
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using Spine;
|
||||
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 () {
|
||||
if (spriteAtlasFile == null) {
|
||||
Debug.LogError("SpriteAtlas file not set for SpineSpriteAtlasAsset: " + name, this);
|
||||
Clear();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (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 (var region in usedAtlas) {
|
||||
var savedRegion = savedRegions[i];
|
||||
var page = region.page;
|
||||
|
||||
region.degrees = savedRegion.packingRotation == SpritePackingRotation.None ? 0 : 90;
|
||||
region.rotate = region.degrees != 0;
|
||||
|
||||
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.rotate) {
|
||||
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
|
||||
var 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.
|
||||
// var 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) {
|
||||
var 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.rotate = region.degrees != 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
var 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 (var guid in guids) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
var 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 (var guid in guids) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
var 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");
|
||||
var method = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (method != null) {
|
||||
object retval = method.Invoke(null, new object[] { spriteAtlasFile });
|
||||
var 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 (var region in atlas) {
|
||||
var sprite = sprites[i];
|
||||
var rect = sprite.textureRect;
|
||||
float x = rect.min.x;
|
||||
float y = rect.min.y;
|
||||
float width = rect.width;
|
||||
float height = rect.height;
|
||||
|
||||
var 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 });
|
||||
var 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 });
|
||||
var 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:
|
||||
9
Assets/Spine/Runtime/spine-unity/Components.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Components.meta
Normal file
@@ -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: b0fd98019ca00c74f929c6d1f7ee3544
|
||||
folderAsset: yes
|
||||
timeCreated: 1563290418
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,217 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>Sets a GameObject's transform to match a bone on a Spine skeleton.</summary>
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[AddComponentMenu("Spine/BoneFollower")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#BoneFollower")]
|
||||
public class BoneFollower : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonRenderer SkeletonRenderer {
|
||||
get { return skeletonRenderer; }
|
||||
set {
|
||||
skeletonRenderer = value;
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>If a bone isn't set in code, boneName is used to find the bone at the beginning. For runtime switching by name, use SetBoneByName. You can also set the BoneFollower.bone field directly.</summary>
|
||||
[SpineBone(dataField: "skeletonRenderer")]
|
||||
public string boneName;
|
||||
|
||||
public bool followXYPosition = true;
|
||||
public bool followZPosition = true;
|
||||
public bool followBoneRotation = true;
|
||||
|
||||
[Tooltip("Follows the skeleton's flip state by controlling this Transform's local scale.")]
|
||||
public bool followSkeletonFlip = true;
|
||||
|
||||
[Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")]
|
||||
public bool followLocalScale = false;
|
||||
|
||||
public enum AxisOrientation {
|
||||
XAxis = 1,
|
||||
YAxis
|
||||
}
|
||||
[Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled."
|
||||
+ " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted"
|
||||
+ " instead of its scale to follow the bone orientation. When one of the axes is flipped, "
|
||||
+ " only one axis can be followed, either the X or the Y axis, which is selected here.")]
|
||||
public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis;
|
||||
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("resetOnAwake")]
|
||||
public bool initializeOnAwake = true;
|
||||
#endregion
|
||||
|
||||
[NonSerialized] public bool valid;
|
||||
[NonSerialized] public Bone bone;
|
||||
|
||||
Transform skeletonTransform;
|
||||
bool skeletonTransformIsParent;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target bone by its bone name. Returns false if no bone was found. To set the bone by reference, use BoneFollower.bone directly.</summary>
|
||||
public bool SetBone (string name) {
|
||||
bone = skeletonRenderer.skeleton.FindBone(name);
|
||||
if (bone == null) {
|
||||
Debug.LogError("Bone not found: " + name, this);
|
||||
return false;
|
||||
}
|
||||
boneName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Awake () {
|
||||
if (initializeOnAwake) Initialize();
|
||||
}
|
||||
|
||||
public void HandleRebuildRenderer (SkeletonRenderer skeletonRenderer) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
bone = null;
|
||||
valid = skeletonRenderer != null && skeletonRenderer.valid;
|
||||
if (!valid) return;
|
||||
|
||||
skeletonTransform = skeletonRenderer.transform;
|
||||
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
|
||||
skeletonRenderer.OnRebuild += HandleRebuildRenderer;
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
|
||||
if (!string.IsNullOrEmpty(boneName))
|
||||
bone = skeletonRenderer.skeleton.FindBone(boneName);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
LateUpdate();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
|
||||
}
|
||||
|
||||
public void LateUpdate () {
|
||||
if (!valid) {
|
||||
Initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
#endif
|
||||
|
||||
if (bone == null) {
|
||||
if (string.IsNullOrEmpty(boneName)) return;
|
||||
bone = skeletonRenderer.skeleton.FindBone(boneName);
|
||||
if (!SetBone(boneName)) return;
|
||||
}
|
||||
|
||||
Transform thisTransform = this.transform;
|
||||
float additionalFlipScale = 1;
|
||||
if (skeletonTransformIsParent) {
|
||||
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
|
||||
thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX : thisTransform.localPosition.x,
|
||||
followXYPosition ? bone.worldY : thisTransform.localPosition.y,
|
||||
followZPosition ? 0f : thisTransform.localPosition.z);
|
||||
if (followBoneRotation) {
|
||||
float halfRotation = Mathf.Atan2(bone.c, bone.a) * 0.5f;
|
||||
if (followLocalScale && bone.scaleX < 0) // Negate rotation from negative scaleX. Don't use negative determinant. local scaleY doesn't factor into used rotation.
|
||||
halfRotation += Mathf.PI * 0.5f;
|
||||
|
||||
var q = default(Quaternion);
|
||||
q.z = Mathf.Sin(halfRotation);
|
||||
q.w = Mathf.Cos(halfRotation);
|
||||
thisTransform.localRotation = q;
|
||||
}
|
||||
} else {
|
||||
// For special cases: Use transform world properties if transform relationship is complicated
|
||||
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY, 0f));
|
||||
if (!followZPosition) targetWorldPosition.z = thisTransform.position.z;
|
||||
if (!followXYPosition) {
|
||||
targetWorldPosition.x = thisTransform.position.x;
|
||||
targetWorldPosition.y = thisTransform.position.y;
|
||||
}
|
||||
|
||||
Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
|
||||
Transform transformParent = thisTransform.parent;
|
||||
Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
|
||||
if (followBoneRotation) {
|
||||
float boneWorldRotation = bone.WorldRotationX;
|
||||
|
||||
if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
|
||||
boneWorldRotation = -boneWorldRotation;
|
||||
|
||||
if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) {
|
||||
if ((skeletonLossyScale.x * parentLossyScale.x < 0))
|
||||
boneWorldRotation += 180f;
|
||||
}
|
||||
else {
|
||||
if ((skeletonLossyScale.y * parentLossyScale.y < 0))
|
||||
boneWorldRotation += 180f;
|
||||
}
|
||||
|
||||
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
|
||||
if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
|
||||
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
|
||||
} else {
|
||||
thisTransform.position = targetWorldPosition;
|
||||
}
|
||||
|
||||
additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x
|
||||
* skeletonLossyScale.y * parentLossyScale.y);
|
||||
}
|
||||
|
||||
Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f);
|
||||
if (followSkeletonFlip)
|
||||
localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale;
|
||||
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1fd8daaed7b64148a34acb96ba14ce1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,196 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace Spine.Unity {
|
||||
using AxisOrientation = BoneFollower.AxisOrientation;
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(RectTransform)), DisallowMultipleComponent]
|
||||
[AddComponentMenu("Spine/UI/BoneFollowerGraphic")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#BoneFollowerGraphic")]
|
||||
public class BoneFollowerGraphic : MonoBehaviour {
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
public SkeletonGraphic SkeletonGraphic {
|
||||
get { return skeletonGraphic; }
|
||||
set {
|
||||
skeletonGraphic = value;
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public bool initializeOnAwake = true;
|
||||
|
||||
/// <summary>If a bone isn't set in code, boneName is used to find the bone at the beginning. For runtime switching by name, use SetBoneByName. You can also set the BoneFollower.bone field directly.</summary>
|
||||
[SpineBone(dataField: "skeletonGraphic")]
|
||||
public string boneName;
|
||||
|
||||
public bool followBoneRotation = true;
|
||||
[Tooltip("Follows the skeleton's flip state by controlling this Transform's local scale.")]
|
||||
public bool followSkeletonFlip = true;
|
||||
[Tooltip("Follows the target bone's local scale. BoneFollower cannot inherit world/skewed scale because of UnityEngine.Transform property limitations.")]
|
||||
public bool followLocalScale = false;
|
||||
public bool followXYPosition = true;
|
||||
public bool followZPosition = true;
|
||||
[Tooltip("Applies when 'Follow Skeleton Flip' is disabled but 'Follow Bone Rotation' is enabled."
|
||||
+ " When flipping the skeleton by scaling its Transform, this follower's rotation is adjusted"
|
||||
+ " instead of its scale to follow the bone orientation. When one of the axes is flipped, "
|
||||
+ " only one axis can be followed, either the X or the Y axis, which is selected here.")]
|
||||
public AxisOrientation maintainedAxisOrientation = AxisOrientation.XAxis;
|
||||
|
||||
[System.NonSerialized] public Bone bone;
|
||||
|
||||
Transform skeletonTransform;
|
||||
bool skeletonTransformIsParent;
|
||||
|
||||
[System.NonSerialized] public bool valid;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the target bone by its bone name. Returns false if no bone was found.</summary>
|
||||
public bool SetBone (string name) {
|
||||
bone = skeletonGraphic.Skeleton.FindBone(name);
|
||||
if (bone == null) {
|
||||
Debug.LogError("Bone not found: " + name, this);
|
||||
return false;
|
||||
}
|
||||
boneName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Awake () {
|
||||
if (initializeOnAwake) Initialize();
|
||||
}
|
||||
|
||||
public void Initialize () {
|
||||
bone = null;
|
||||
valid = skeletonGraphic != null && skeletonGraphic.IsValid;
|
||||
if (!valid) return;
|
||||
|
||||
skeletonTransform = skeletonGraphic.transform;
|
||||
// skeletonGraphic.OnRebuild -= HandleRebuildRenderer;
|
||||
// skeletonGraphic.OnRebuild += HandleRebuildRenderer;
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
|
||||
if (!string.IsNullOrEmpty(boneName))
|
||||
bone = skeletonGraphic.Skeleton.FindBone(boneName);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor) {
|
||||
LateUpdate();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void LateUpdate () {
|
||||
if (!valid) {
|
||||
Initialize();
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
#endif
|
||||
|
||||
if (bone == null) {
|
||||
if (string.IsNullOrEmpty(boneName)) return;
|
||||
bone = skeletonGraphic.Skeleton.FindBone(boneName);
|
||||
if (!SetBone(boneName)) return;
|
||||
}
|
||||
|
||||
var thisTransform = this.transform as RectTransform;
|
||||
if (thisTransform == null) return;
|
||||
|
||||
var canvas = skeletonGraphic.canvas;
|
||||
if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
|
||||
float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
|
||||
|
||||
float additionalFlipScale = 1;
|
||||
if (skeletonTransformIsParent) {
|
||||
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
|
||||
thisTransform.localPosition = new Vector3(followXYPosition ? bone.worldX * scale : thisTransform.localPosition.x,
|
||||
followXYPosition ? bone.worldY * scale : thisTransform.localPosition.y,
|
||||
followZPosition ? 0f : thisTransform.localPosition.z);
|
||||
if (followBoneRotation) thisTransform.localRotation = bone.GetQuaternion();
|
||||
} else {
|
||||
// For special cases: Use transform world properties if transform relationship is complicated
|
||||
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(bone.worldX * scale, bone.worldY * scale, 0f));
|
||||
if (!followZPosition) targetWorldPosition.z = thisTransform.position.z;
|
||||
if (!followXYPosition) {
|
||||
targetWorldPosition.x = thisTransform.position.x;
|
||||
targetWorldPosition.y = thisTransform.position.y;
|
||||
}
|
||||
|
||||
Vector3 skeletonLossyScale = skeletonTransform.lossyScale;
|
||||
Transform transformParent = thisTransform.parent;
|
||||
Vector3 parentLossyScale = transformParent != null ? transformParent.lossyScale : Vector3.one;
|
||||
if (followBoneRotation) {
|
||||
float boneWorldRotation = bone.WorldRotationX;
|
||||
|
||||
if ((skeletonLossyScale.x * skeletonLossyScale.y) < 0)
|
||||
boneWorldRotation = -boneWorldRotation;
|
||||
|
||||
if (followSkeletonFlip || maintainedAxisOrientation == AxisOrientation.XAxis) {
|
||||
if ((skeletonLossyScale.x * parentLossyScale.x < 0))
|
||||
boneWorldRotation += 180f;
|
||||
}
|
||||
else {
|
||||
if ((skeletonLossyScale.y * parentLossyScale.y < 0))
|
||||
boneWorldRotation += 180f;
|
||||
}
|
||||
|
||||
Vector3 worldRotation = skeletonTransform.rotation.eulerAngles;
|
||||
if (followLocalScale && bone.scaleX < 0) boneWorldRotation += 180f;
|
||||
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z + boneWorldRotation));
|
||||
} else {
|
||||
thisTransform.position = targetWorldPosition;
|
||||
}
|
||||
|
||||
additionalFlipScale = Mathf.Sign(skeletonLossyScale.x * parentLossyScale.x
|
||||
* skeletonLossyScale.y * parentLossyScale.y);
|
||||
}
|
||||
|
||||
Vector3 localScale = followLocalScale ? new Vector3(bone.scaleX, bone.scaleY, 1f) : new Vector3(1f, 1f, 1f);
|
||||
if (followSkeletonFlip)
|
||||
localScale.y *= Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY) * additionalFlipScale;
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b42a195b47491d34b9bcbc40898bcb29
|
||||
timeCreated: 1499211965
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,253 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollower")]
|
||||
public class BoundingBoxFollower : MonoBehaviour {
|
||||
internal static bool DebugMessages = true;
|
||||
|
||||
#region Inspector
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
[SpineSlot(dataField: "skeletonRenderer", containsBoundingBoxes: true)]
|
||||
public string slotName;
|
||||
public bool isTrigger;
|
||||
public bool clearStateOnDisable = true;
|
||||
#endregion
|
||||
|
||||
Slot slot;
|
||||
BoundingBoxAttachment currentAttachment;
|
||||
string currentAttachmentName;
|
||||
PolygonCollider2D currentCollider;
|
||||
|
||||
public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
|
||||
public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
|
||||
|
||||
public Slot Slot { get { return slot; } }
|
||||
public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
|
||||
public string CurrentAttachmentName { get { return currentAttachmentName; } }
|
||||
public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
|
||||
public bool IsTrigger { get { return isTrigger; } }
|
||||
|
||||
void Start () {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.OnRebuild -= HandleRebuild;
|
||||
skeletonRenderer.OnRebuild += HandleRebuild;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void HandleRebuild (SkeletonRenderer sr) {
|
||||
//if (BoundingBoxFollower.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollower.");
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize and instantiate the BoundingBoxFollower colliders. This is method checks if the BoundingBoxFollower has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
|
||||
public void Initialize (bool overwrite = false) {
|
||||
if (skeletonRenderer == null)
|
||||
return;
|
||||
|
||||
skeletonRenderer.Initialize(false);
|
||||
|
||||
if (string.IsNullOrEmpty(slotName))
|
||||
return;
|
||||
|
||||
// Don't reinitialize if the setup did not change.
|
||||
if (!overwrite
|
||||
&&
|
||||
colliderTable.Count > 0 && slot != null // Slot is set and colliders already populated.
|
||||
&&
|
||||
skeletonRenderer.skeleton == slot.Skeleton // Skeleton object did not change.
|
||||
&&
|
||||
slotName == slot.data.name // Slot object did not change.
|
||||
)
|
||||
return;
|
||||
|
||||
slot = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
colliderTable.Clear();
|
||||
nameTable.Clear();
|
||||
|
||||
var skeleton = skeletonRenderer.skeleton;
|
||||
if (skeleton == null)
|
||||
return;
|
||||
slot = skeleton.FindSlot(slotName);
|
||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||
|
||||
if (slot == null) {
|
||||
if (BoundingBoxFollower.DebugMessages)
|
||||
Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollower on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
|
||||
return;
|
||||
}
|
||||
|
||||
int requiredCollidersCount = 0;
|
||||
var colliders = GetComponents<PolygonCollider2D>();
|
||||
if (this.gameObject.activeInHierarchy) {
|
||||
foreach (var skin in skeleton.Data.Skins)
|
||||
AddCollidersForSkin(skin, slotIndex, colliders, ref requiredCollidersCount);
|
||||
|
||||
if (skeleton.skin != null)
|
||||
AddCollidersForSkin(skeleton.skin, slotIndex, colliders, ref requiredCollidersCount);
|
||||
}
|
||||
DisposeExcessCollidersAfter(requiredCollidersCount);
|
||||
|
||||
if (BoundingBoxFollower.DebugMessages) {
|
||||
bool valid = colliderTable.Count != 0;
|
||||
if (!valid) {
|
||||
if (this.gameObject.activeInHierarchy)
|
||||
Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
|
||||
else
|
||||
Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, ref int collidersCount) {
|
||||
if (skin == null) return;
|
||||
var skinEntries = new List<Skin.SkinEntry>();
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (var entry in skinEntries) {
|
||||
var attachment = skin.GetAttachment(slotIndex, entry.Name);
|
||||
var boundingBoxAttachment = attachment as BoundingBoxAttachment;
|
||||
|
||||
if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
|
||||
Debug.Log("BoundingBoxFollower tried to follow a slot that contains non-boundingbox attachments: " + slotName);
|
||||
|
||||
if (boundingBoxAttachment != null) {
|
||||
if (!colliderTable.ContainsKey(boundingBoxAttachment)) {
|
||||
var bbCollider = collidersCount < previousColliders.Length ?
|
||||
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
|
||||
++collidersCount;
|
||||
SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment);
|
||||
bbCollider.isTrigger = isTrigger;
|
||||
bbCollider.enabled = false;
|
||||
bbCollider.hideFlags = HideFlags.NotEditable;
|
||||
bbCollider.isTrigger = IsTrigger;
|
||||
colliderTable.Add(boundingBoxAttachment, bbCollider);
|
||||
nameTable.Add(boundingBoxAttachment, entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (clearStateOnDisable)
|
||||
ClearState();
|
||||
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.OnRebuild -= HandleRebuild;
|
||||
}
|
||||
|
||||
public void ClearState () {
|
||||
if (colliderTable != null)
|
||||
foreach (var col in colliderTable.Values)
|
||||
col.enabled = false;
|
||||
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
}
|
||||
|
||||
void DisposeExcessCollidersAfter (int requiredCount) {
|
||||
var colliders = GetComponents<PolygonCollider2D>();
|
||||
if (colliders.Length == 0) return;
|
||||
|
||||
for (int i = requiredCount; i < colliders.Length; ++i) {
|
||||
var collider = colliders[i];
|
||||
if (collider != null) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
DestroyImmediate(collider);
|
||||
else
|
||||
#endif
|
||||
Destroy(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
if (slot != null && slot.Attachment != currentAttachment)
|
||||
MatchAttachment(slot.Attachment);
|
||||
}
|
||||
|
||||
/// <summary>Sets the current collider to match attachment.</summary>
|
||||
/// <param name="attachment">If the attachment is not a bounding box, it will be treated as null.</param>
|
||||
void MatchAttachment (Attachment attachment) {
|
||||
var bbAttachment = attachment as BoundingBoxAttachment;
|
||||
|
||||
if (BoundingBoxFollower.DebugMessages && attachment != null && bbAttachment == null)
|
||||
Debug.LogWarning("BoundingBoxFollower tried to match a non-boundingbox attachment. It will treat it as null.");
|
||||
|
||||
if (currentCollider != null)
|
||||
currentCollider.enabled = false;
|
||||
|
||||
if (bbAttachment == null) {
|
||||
currentCollider = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
} else {
|
||||
PolygonCollider2D foundCollider;
|
||||
colliderTable.TryGetValue(bbAttachment, out foundCollider);
|
||||
if (foundCollider != null) {
|
||||
currentCollider = foundCollider;
|
||||
currentCollider.enabled = true;
|
||||
currentAttachment = bbAttachment;
|
||||
currentAttachmentName = nameTable[bbAttachment];
|
||||
} else {
|
||||
currentCollider = null;
|
||||
currentAttachment = bbAttachment;
|
||||
currentAttachmentName = null;
|
||||
if (BoundingBoxFollower.DebugMessages) Debug.LogFormat("Collider for BoundingBoxAttachment named '{0}' was not initialized. It is possibly from a new skin. currentAttachmentName will be null. You may need to call BoundingBoxFollower.Initialize(overwrite: true);", bbAttachment.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0317ee9ba6e1b1e49a030268e026d372
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,257 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#BoundingBoxFollowerGraphic")]
|
||||
public class BoundingBoxFollowerGraphic : MonoBehaviour {
|
||||
internal static bool DebugMessages = true;
|
||||
|
||||
#region Inspector
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
[SpineSlot(dataField: "skeletonGraphic", containsBoundingBoxes: true)]
|
||||
public string slotName;
|
||||
public bool isTrigger;
|
||||
public bool clearStateOnDisable = true;
|
||||
#endregion
|
||||
|
||||
Slot slot;
|
||||
BoundingBoxAttachment currentAttachment;
|
||||
string currentAttachmentName;
|
||||
PolygonCollider2D currentCollider;
|
||||
|
||||
public readonly Dictionary<BoundingBoxAttachment, PolygonCollider2D> colliderTable = new Dictionary<BoundingBoxAttachment, PolygonCollider2D>();
|
||||
public readonly Dictionary<BoundingBoxAttachment, string> nameTable = new Dictionary<BoundingBoxAttachment, string>();
|
||||
|
||||
public Slot Slot { get { return slot; } }
|
||||
public BoundingBoxAttachment CurrentAttachment { get { return currentAttachment; } }
|
||||
public string CurrentAttachmentName { get { return currentAttachmentName; } }
|
||||
public PolygonCollider2D CurrentCollider { get { return currentCollider; } }
|
||||
public bool IsTrigger { get { return isTrigger; } }
|
||||
|
||||
void Start () {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (skeletonGraphic != null) {
|
||||
skeletonGraphic.OnRebuild -= HandleRebuild;
|
||||
skeletonGraphic.OnRebuild += HandleRebuild;
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void HandleRebuild (SkeletonGraphic sr) {
|
||||
//if (BoundingBoxFollowerGraphic.DebugMessages) Debug.Log("Skeleton was rebuilt. Repopulating BoundingBoxFollowerGraphic.");
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize and instantiate the BoundingBoxFollowerGraphic colliders. This is method checks if the BoundingBoxFollowerGraphic has already been initialized for the skeleton instance and slotName and prevents overwriting unless it detects a new setup.</summary>
|
||||
public void Initialize (bool overwrite = false) {
|
||||
if (skeletonGraphic == null)
|
||||
return;
|
||||
|
||||
skeletonGraphic.Initialize(false);
|
||||
|
||||
if (string.IsNullOrEmpty(slotName))
|
||||
return;
|
||||
|
||||
// Don't reinitialize if the setup did not change.
|
||||
if (!overwrite
|
||||
&&
|
||||
colliderTable.Count > 0 && slot != null // Slot is set and colliders already populated.
|
||||
&&
|
||||
skeletonGraphic.Skeleton == slot.Skeleton // Skeleton object did not change.
|
||||
&&
|
||||
slotName == slot.data.name // Slot object did not change.
|
||||
)
|
||||
return;
|
||||
|
||||
slot = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
colliderTable.Clear();
|
||||
nameTable.Clear();
|
||||
|
||||
var skeleton = skeletonGraphic.Skeleton;
|
||||
if (skeleton == null)
|
||||
return;
|
||||
slot = skeleton.FindSlot(slotName);
|
||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||
|
||||
if (slot == null) {
|
||||
if (BoundingBoxFollowerGraphic.DebugMessages)
|
||||
Debug.LogWarning(string.Format("Slot '{0}' not found for BoundingBoxFollowerGraphic on '{1}'. (Previous colliders were disposed.)", slotName, this.gameObject.name));
|
||||
return;
|
||||
}
|
||||
|
||||
int requiredCollidersCount = 0;
|
||||
var colliders = GetComponents<PolygonCollider2D>();
|
||||
if (this.gameObject.activeInHierarchy) {
|
||||
var canvas = skeletonGraphic.canvas;
|
||||
if (canvas == null) canvas = skeletonGraphic.GetComponentInParent<Canvas>();
|
||||
float scale = canvas != null ? canvas.referencePixelsPerUnit : 100.0f;
|
||||
|
||||
foreach (var skin in skeleton.Data.Skins)
|
||||
AddCollidersForSkin(skin, slotIndex, colliders, scale, ref requiredCollidersCount);
|
||||
|
||||
if (skeleton.skin != null)
|
||||
AddCollidersForSkin(skeleton.skin, slotIndex, colliders, scale, ref requiredCollidersCount);
|
||||
}
|
||||
DisposeExcessCollidersAfter(requiredCollidersCount);
|
||||
|
||||
if (BoundingBoxFollowerGraphic.DebugMessages) {
|
||||
bool valid = colliderTable.Count != 0;
|
||||
if (!valid) {
|
||||
if (this.gameObject.activeInHierarchy)
|
||||
Debug.LogWarning("Bounding Box Follower not valid! Slot [" + slotName + "] does not contain any Bounding Box Attachments!");
|
||||
else
|
||||
Debug.LogWarning("Bounding Box Follower tried to rebuild as a prefab.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddCollidersForSkin (Skin skin, int slotIndex, PolygonCollider2D[] previousColliders, float scale, ref int collidersCount) {
|
||||
if (skin == null) return;
|
||||
var skinEntries = new List<Skin.SkinEntry>();
|
||||
skin.GetAttachments(slotIndex, skinEntries);
|
||||
|
||||
foreach (var entry in skinEntries) {
|
||||
var attachment = skin.GetAttachment(slotIndex, entry.Name);
|
||||
var boundingBoxAttachment = attachment as BoundingBoxAttachment;
|
||||
|
||||
if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && boundingBoxAttachment == null)
|
||||
Debug.Log("BoundingBoxFollowerGraphic tried to follow a slot that contains non-boundingbox attachments: " + slotName);
|
||||
|
||||
if (boundingBoxAttachment != null) {
|
||||
if (!colliderTable.ContainsKey(boundingBoxAttachment)) {
|
||||
var bbCollider = collidersCount < previousColliders.Length ?
|
||||
previousColliders[collidersCount] : gameObject.AddComponent<PolygonCollider2D>();
|
||||
++collidersCount;
|
||||
SkeletonUtility.SetColliderPointsLocal(bbCollider, slot, boundingBoxAttachment, scale);
|
||||
bbCollider.isTrigger = isTrigger;
|
||||
bbCollider.enabled = false;
|
||||
bbCollider.hideFlags = HideFlags.NotEditable;
|
||||
bbCollider.isTrigger = IsTrigger;
|
||||
colliderTable.Add(boundingBoxAttachment, bbCollider);
|
||||
nameTable.Add(boundingBoxAttachment, entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (clearStateOnDisable)
|
||||
ClearState();
|
||||
|
||||
if (skeletonGraphic != null)
|
||||
skeletonGraphic.OnRebuild -= HandleRebuild;
|
||||
}
|
||||
|
||||
public void ClearState () {
|
||||
if (colliderTable != null)
|
||||
foreach (var col in colliderTable.Values)
|
||||
col.enabled = false;
|
||||
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
currentCollider = null;
|
||||
}
|
||||
|
||||
void DisposeExcessCollidersAfter (int requiredCount) {
|
||||
var colliders = GetComponents<PolygonCollider2D>();
|
||||
if (colliders.Length == 0) return;
|
||||
|
||||
for (int i = requiredCount; i < colliders.Length; ++i) {
|
||||
var collider = colliders[i];
|
||||
if (collider != null) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
DestroyImmediate(collider);
|
||||
else
|
||||
#endif
|
||||
Destroy(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
if (slot != null && slot.Attachment != currentAttachment)
|
||||
MatchAttachment(slot.Attachment);
|
||||
}
|
||||
|
||||
/// <summary>Sets the current collider to match attachment.</summary>
|
||||
/// <param name="attachment">If the attachment is not a bounding box, it will be treated as null.</param>
|
||||
void MatchAttachment (Attachment attachment) {
|
||||
var bbAttachment = attachment as BoundingBoxAttachment;
|
||||
|
||||
if (BoundingBoxFollowerGraphic.DebugMessages && attachment != null && bbAttachment == null)
|
||||
Debug.LogWarning("BoundingBoxFollowerGraphic tried to match a non-boundingbox attachment. It will treat it as null.");
|
||||
|
||||
if (currentCollider != null)
|
||||
currentCollider.enabled = false;
|
||||
|
||||
if (bbAttachment == null) {
|
||||
currentCollider = null;
|
||||
currentAttachment = null;
|
||||
currentAttachmentName = null;
|
||||
} else {
|
||||
PolygonCollider2D foundCollider;
|
||||
colliderTable.TryGetValue(bbAttachment, out foundCollider);
|
||||
if (foundCollider != null) {
|
||||
currentCollider = foundCollider;
|
||||
currentCollider.enabled = true;
|
||||
currentAttachment = bbAttachment;
|
||||
currentAttachmentName = nameTable[bbAttachment];
|
||||
} else {
|
||||
currentCollider = null;
|
||||
currentAttachment = bbAttachment;
|
||||
currentAttachmentName = null;
|
||||
if (BoundingBoxFollowerGraphic.DebugMessages) Debug.LogFormat("Collider for BoundingBoxAttachment named '{0}' was not initialized. It is possibly from a new skin. currentAttachmentName will be null. You may need to call BoundingBoxFollowerGraphic.Initialize(overwrite: true);", bbAttachment.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0bf7b497af9f74280040d96cdf88da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,164 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[AddComponentMenu("Spine/Point Follower")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#PointFollower")]
|
||||
public class PointFollower : MonoBehaviour, IHasSkeletonRenderer, IHasSkeletonComponent {
|
||||
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonRenderer SkeletonRenderer { get { return this.skeletonRenderer; } }
|
||||
public ISkeletonComponent SkeletonComponent { get { return skeletonRenderer as ISkeletonComponent; } }
|
||||
|
||||
[SpineSlot(dataField:"skeletonRenderer", includeNone: true)]
|
||||
public string slotName;
|
||||
|
||||
[SpineAttachment(slotField:"slotName", dataField: "skeletonRenderer", fallbackToTextField:true, includeNone: true)]
|
||||
public string pointAttachmentName;
|
||||
|
||||
public bool followRotation = true;
|
||||
public bool followSkeletonFlip = true;
|
||||
public bool followSkeletonZPosition = false;
|
||||
|
||||
Transform skeletonTransform;
|
||||
bool skeletonTransformIsParent;
|
||||
PointAttachment point;
|
||||
Bone bone;
|
||||
bool valid;
|
||||
public bool IsValid { get { return valid; } }
|
||||
|
||||
public void Initialize () {
|
||||
valid = skeletonRenderer != null && skeletonRenderer.valid;
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
UpdateReferences();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor) LateUpdate();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleRebuildRenderer (SkeletonRenderer skeletonRenderer) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void UpdateReferences () {
|
||||
skeletonTransform = skeletonRenderer.transform;
|
||||
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
|
||||
skeletonRenderer.OnRebuild += HandleRebuildRenderer;
|
||||
skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
|
||||
bone = null;
|
||||
point = null;
|
||||
if (!string.IsNullOrEmpty(pointAttachmentName)) {
|
||||
var skeleton = skeletonRenderer.Skeleton;
|
||||
|
||||
int slotIndex = skeleton.FindSlotIndex(slotName);
|
||||
if (slotIndex >= 0) {
|
||||
var slot = skeleton.slots.Items[slotIndex];
|
||||
bone = slot.bone;
|
||||
point = skeleton.GetAttachment(slotIndex, pointAttachmentName) as PointAttachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.OnRebuild -= HandleRebuildRenderer;
|
||||
}
|
||||
|
||||
public void LateUpdate () {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) skeletonTransformIsParent = Transform.ReferenceEquals(skeletonTransform, transform.parent);
|
||||
#endif
|
||||
|
||||
if (point == null) {
|
||||
if (string.IsNullOrEmpty(pointAttachmentName)) return;
|
||||
UpdateReferences();
|
||||
if (point == null) return;
|
||||
}
|
||||
|
||||
Vector2 worldPos;
|
||||
point.ComputeWorldPosition(bone, out worldPos.x, out worldPos.y);
|
||||
float rotation = point.ComputeWorldRotation(bone);
|
||||
|
||||
Transform thisTransform = this.transform;
|
||||
if (skeletonTransformIsParent) {
|
||||
// Recommended setup: Use local transform properties if Spine GameObject is the immediate parent
|
||||
thisTransform.localPosition = new Vector3(worldPos.x, worldPos.y, followSkeletonZPosition ? 0f : thisTransform.localPosition.z);
|
||||
if (followRotation) {
|
||||
float halfRotation = rotation * 0.5f * Mathf.Deg2Rad;
|
||||
|
||||
var q = default(Quaternion);
|
||||
q.z = Mathf.Sin(halfRotation);
|
||||
q.w = Mathf.Cos(halfRotation);
|
||||
thisTransform.localRotation = q;
|
||||
}
|
||||
} else {
|
||||
// For special cases: Use transform world properties if transform relationship is complicated
|
||||
Vector3 targetWorldPosition = skeletonTransform.TransformPoint(new Vector3(worldPos.x, worldPos.y, 0f));
|
||||
if (!followSkeletonZPosition)
|
||||
targetWorldPosition.z = thisTransform.position.z;
|
||||
|
||||
Transform transformParent = thisTransform.parent;
|
||||
if (transformParent != null) {
|
||||
Matrix4x4 m = transformParent.localToWorldMatrix;
|
||||
if (m.m00 * m.m11 - m.m01 * m.m10 < 0) // Determinant2D is negative
|
||||
rotation = -rotation;
|
||||
}
|
||||
|
||||
if (followRotation) {
|
||||
Vector3 transformWorldRotation = skeletonTransform.rotation.eulerAngles;
|
||||
thisTransform.SetPositionAndRotation(targetWorldPosition, Quaternion.Euler(transformWorldRotation.x, transformWorldRotation.y, transformWorldRotation.z + rotation));
|
||||
} else {
|
||||
thisTransform.position = targetWorldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (followSkeletonFlip) {
|
||||
Vector3 localScale = thisTransform.localScale;
|
||||
localScale.y = Mathf.Abs(localScale.y) * Mathf.Sign(bone.skeleton.ScaleX * bone.skeleton.ScaleY);
|
||||
thisTransform.localScale = localScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daf461e4341180341a648c07e1899528
|
||||
timeCreated: 1518094986
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b957aa69dae9f948bacdeec549d28ea
|
||||
folderAsset: yes
|
||||
timeCreated: 1593173800
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,121 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Spine.Unity.AnimationTools;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Add this component to a SkeletonMecanim GameObject
|
||||
/// to turn motion of a selected root bone into Transform or RigidBody motion.
|
||||
/// Local bone translation movement is used as motion.
|
||||
/// All top-level bones of the skeleton are moved to compensate the root
|
||||
/// motion bone location, keeping the distance relationship between bones intact.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only compatible with <c>SkeletonMecanim</c>.
|
||||
/// For <c>SkeletonAnimation</c> or <c>SkeletonGraphic</c> please use
|
||||
/// <see cref="SkeletonRootMotion">SkeletonRootMotion</see> instead.
|
||||
/// </remarks>
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonMecanimRootMotion")]
|
||||
public class SkeletonMecanimRootMotion : SkeletonRootMotionBase {
|
||||
#region Inspector
|
||||
const int DefaultMecanimLayerFlags = -1;
|
||||
public int mecanimLayerFlags = DefaultMecanimLayerFlags;
|
||||
#endregion
|
||||
|
||||
protected Vector2 movementDelta;
|
||||
|
||||
SkeletonMecanim skeletonMecanim;
|
||||
public SkeletonMecanim SkeletonMecanim {
|
||||
get {
|
||||
return skeletonMecanim ? skeletonMecanim : skeletonMecanim = GetComponent<SkeletonMecanim>();
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 GetRemainingRootMotion (int layerIndex) {
|
||||
var pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
|
||||
var animation = pair.Key;
|
||||
var time = pair.Value;
|
||||
if (animation == null)
|
||||
return Vector2.zero;
|
||||
|
||||
float start = time;
|
||||
float end = animation.duration;
|
||||
return GetAnimationRootMotion(start, end, animation);
|
||||
}
|
||||
|
||||
public override RootMotionInfo GetRootMotionInfo (int layerIndex) {
|
||||
var pair = skeletonMecanim.Translator.GetActiveAnimationAndTime(layerIndex);
|
||||
var animation = pair.Key;
|
||||
var time = pair.Value;
|
||||
if (animation == null)
|
||||
return new RootMotionInfo();
|
||||
return GetAnimationRootMotionInfo(animation, time);
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
mecanimLayerFlags = DefaultMecanimLayerFlags;
|
||||
}
|
||||
|
||||
protected override void Start () {
|
||||
base.Start();
|
||||
skeletonMecanim = GetComponent<SkeletonMecanim>();
|
||||
if (skeletonMecanim) {
|
||||
skeletonMecanim.Translator.OnClipApplied -= OnClipApplied;
|
||||
skeletonMecanim.Translator.OnClipApplied += OnClipApplied;
|
||||
}
|
||||
}
|
||||
|
||||
void OnClipApplied(Spine.Animation animation, int layerIndex, float weight,
|
||||
float time, float lastTime, bool playsBackward) {
|
||||
|
||||
if (((mecanimLayerFlags & 1<<layerIndex) == 0) || weight == 0)
|
||||
return;
|
||||
|
||||
if (!playsBackward) {
|
||||
movementDelta += weight * GetAnimationRootMotion(lastTime, time, animation);
|
||||
}
|
||||
else {
|
||||
movementDelta -= weight * GetAnimationRootMotion(time, lastTime, animation);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateAnimationsMovementDelta () {
|
||||
// Note: movement delta is not gather after animation but
|
||||
// in OnClipApplied after every applied animation.
|
||||
Vector2 result = movementDelta;
|
||||
movementDelta = Vector2.zero;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95813afe390494344a6ce2cbc8bfb7d1
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,157 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Spine.Unity.AnimationTools;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Add this component to a SkeletonAnimation or SkeletonGraphic GameObject
|
||||
/// to turn motion of a selected root bone into Transform or RigidBody motion.
|
||||
/// Local bone translation movement is used as motion.
|
||||
/// All top-level bones of the skeleton are moved to compensate the root
|
||||
/// motion bone location, keeping the distance relationship between bones intact.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only compatible with SkeletonAnimation (or other components that implement
|
||||
/// ISkeletonComponent, ISkeletonAnimation and IAnimationStateComponent).
|
||||
/// For <c>SkeletonMecanim</c> please use
|
||||
/// <see cref="SkeletonMecanimRootMotion">SkeletonMecanimRootMotion</see> instead.
|
||||
/// </remarks>
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRootMotion")]
|
||||
public class SkeletonRootMotion : SkeletonRootMotionBase {
|
||||
#region Inspector
|
||||
const int DefaultAnimationTrackFlags = -1;
|
||||
public int animationTrackFlags = DefaultAnimationTrackFlags;
|
||||
#endregion
|
||||
|
||||
AnimationState animationState;
|
||||
Canvas canvas;
|
||||
|
||||
public override Vector2 GetRemainingRootMotion (int trackIndex) {
|
||||
TrackEntry track = animationState.GetCurrent(trackIndex);
|
||||
if (track == null)
|
||||
return Vector2.zero;
|
||||
|
||||
var animation = track.Animation;
|
||||
float start = track.AnimationTime;
|
||||
float end = animation.duration;
|
||||
return GetAnimationRootMotion(start, end, animation);
|
||||
}
|
||||
|
||||
public override RootMotionInfo GetRootMotionInfo (int trackIndex) {
|
||||
TrackEntry track = animationState.GetCurrent(trackIndex);
|
||||
if (track == null)
|
||||
return new RootMotionInfo();
|
||||
|
||||
var animation = track.Animation;
|
||||
float time = track.AnimationTime;
|
||||
return GetAnimationRootMotionInfo(track.Animation, time);
|
||||
}
|
||||
|
||||
protected override float AdditionalScale {
|
||||
get {
|
||||
return canvas ? canvas.referencePixelsPerUnit: 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
base.Reset();
|
||||
animationTrackFlags = DefaultAnimationTrackFlags;
|
||||
}
|
||||
|
||||
protected override void Start () {
|
||||
base.Start();
|
||||
var animstateComponent = skeletonComponent as IAnimationStateComponent;
|
||||
this.animationState = (animstateComponent != null) ? animstateComponent.AnimationState : null;
|
||||
|
||||
if (this.GetComponent<CanvasRenderer>() != null) {
|
||||
canvas = this.GetComponentInParent<Canvas>();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateAnimationsMovementDelta () {
|
||||
Vector2 localDelta = Vector2.zero;
|
||||
int trackCount = animationState.Tracks.Count;
|
||||
|
||||
for (int trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
|
||||
// note: animationTrackFlags != -1 below covers trackIndex >= 32,
|
||||
// with -1 corresponding to entry "everything" of the dropdown list.
|
||||
if (animationTrackFlags != -1 && (animationTrackFlags & 1 << trackIndex) == 0)
|
||||
continue;
|
||||
|
||||
TrackEntry track = animationState.GetCurrent(trackIndex);
|
||||
TrackEntry next = null;
|
||||
while (track != null) {
|
||||
var animation = track.Animation;
|
||||
float start = track.animationLast;
|
||||
float end = track.AnimationTime;
|
||||
var currentDelta = GetAnimationRootMotion(start, end, animation);
|
||||
if (currentDelta != Vector2.zero) {
|
||||
ApplyMixAlphaToDelta(ref currentDelta, next, track);
|
||||
localDelta += currentDelta;
|
||||
}
|
||||
|
||||
// Traverse mixingFrom chain.
|
||||
next = track;
|
||||
track = track.mixingFrom;
|
||||
}
|
||||
}
|
||||
return localDelta;
|
||||
}
|
||||
|
||||
void ApplyMixAlphaToDelta (ref Vector2 currentDelta, TrackEntry next, TrackEntry track) {
|
||||
// Apply mix alpha to the delta position (based on AnimationState.cs).
|
||||
float mix;
|
||||
if (next != null) {
|
||||
if (next.mixDuration == 0) { // Single frame mix to undo mixingFrom changes.
|
||||
mix = 1;
|
||||
}
|
||||
else {
|
||||
mix = next.mixTime / next.mixDuration;
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
float mixAndAlpha = track.alpha * next.interruptAlpha * (1 - mix);
|
||||
currentDelta *= mixAndAlpha;
|
||||
}
|
||||
else {
|
||||
if (track.mixDuration == 0) {
|
||||
mix = 1;
|
||||
}
|
||||
else {
|
||||
mix = track.alpha * (track.mixTime / track.mixDuration);
|
||||
if (mix > 1) mix = 1;
|
||||
}
|
||||
currentDelta *= mix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f21c9538588898a45a3da22bf4779ab3
|
||||
timeCreated: 1591121072
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,322 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Spine.Unity.AnimationTools;
|
||||
using System;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Base class for skeleton root motion components.
|
||||
/// </summary>
|
||||
abstract public class SkeletonRootMotionBase : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
[SpineBone]
|
||||
[SerializeField]
|
||||
protected string rootMotionBoneName = "root";
|
||||
public bool transformPositionX = true;
|
||||
public bool transformPositionY = true;
|
||||
|
||||
public float rootMotionScaleX = 1;
|
||||
public float rootMotionScaleY = 1;
|
||||
/// <summary>Skeleton space X translation per skeleton space Y translation root motion.</summary>
|
||||
public float rootMotionTranslateXPerY = 0;
|
||||
/// <summary>Skeleton space Y translation per skeleton space X translation root motion.</summary>
|
||||
public float rootMotionTranslateYPerX = 0;
|
||||
|
||||
[Header("Optional")]
|
||||
public Rigidbody2D rigidBody2D;
|
||||
public Rigidbody rigidBody;
|
||||
|
||||
public bool UsesRigidbody {
|
||||
get { return rigidBody != null || rigidBody2D != null; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected ISkeletonComponent skeletonComponent;
|
||||
protected Bone rootMotionBone;
|
||||
protected int rootMotionBoneIndex;
|
||||
protected List<Bone> topLevelBones = new List<Bone>();
|
||||
protected Vector2 initialOffset = Vector2.zero;
|
||||
protected Vector2 tempSkeletonDisplacement;
|
||||
protected Vector2 rigidbodyDisplacement;
|
||||
|
||||
protected virtual void Reset () {
|
||||
FindRigidbodyComponent();
|
||||
}
|
||||
|
||||
protected virtual void Start () {
|
||||
skeletonComponent = GetComponent<ISkeletonComponent>();
|
||||
GatherTopLevelBones();
|
||||
SetRootMotionBone(rootMotionBoneName);
|
||||
if (rootMotionBone != null)
|
||||
initialOffset = new Vector2(rootMotionBone.x, rootMotionBone.y);
|
||||
|
||||
var skeletonAnimation = skeletonComponent as ISkeletonAnimation;
|
||||
if (skeletonAnimation != null) {
|
||||
skeletonAnimation.UpdateLocal -= HandleUpdateLocal;
|
||||
skeletonAnimation.UpdateLocal += HandleUpdateLocal;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate () {
|
||||
if (!this.isActiveAndEnabled)
|
||||
return; // Root motion is only applied when component is enabled.
|
||||
|
||||
if (rigidBody2D != null) {
|
||||
rigidBody2D.MovePosition(new Vector2(transform.position.x, transform.position.y)
|
||||
+ rigidbodyDisplacement);
|
||||
}
|
||||
if (rigidBody != null) {
|
||||
rigidBody.MovePosition(transform.position
|
||||
+ new Vector3(rigidbodyDisplacement.x, rigidbodyDisplacement.y, 0));
|
||||
}
|
||||
Vector2 parentBoneScale;
|
||||
GetScaleAffectingRootMotion(out parentBoneScale);
|
||||
ClearEffectiveBoneOffsets(parentBoneScale);
|
||||
rigidbodyDisplacement = Vector2.zero;
|
||||
tempSkeletonDisplacement = Vector2.zero;
|
||||
}
|
||||
|
||||
protected virtual void OnDisable () {
|
||||
rigidbodyDisplacement = Vector2.zero;
|
||||
tempSkeletonDisplacement = Vector2.zero;
|
||||
}
|
||||
|
||||
protected void FindRigidbodyComponent () {
|
||||
rigidBody2D = this.GetComponent<Rigidbody2D>();
|
||||
if (!rigidBody2D)
|
||||
rigidBody = this.GetComponent<Rigidbody>();
|
||||
|
||||
if (!rigidBody2D && !rigidBody) {
|
||||
rigidBody2D = this.GetComponentInParent<Rigidbody2D>();
|
||||
if (!rigidBody2D)
|
||||
rigidBody = this.GetComponentInParent<Rigidbody>();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual float AdditionalScale { get { return 1.0f; } }
|
||||
abstract protected Vector2 CalculateAnimationsMovementDelta ();
|
||||
abstract public Vector2 GetRemainingRootMotion (int trackIndex = 0);
|
||||
|
||||
public struct RootMotionInfo {
|
||||
public Vector2 start;
|
||||
public Vector2 current;
|
||||
public Vector2 mid;
|
||||
public Vector2 end;
|
||||
public bool timeIsPastMid;
|
||||
};
|
||||
abstract public RootMotionInfo GetRootMotionInfo (int trackIndex = 0);
|
||||
|
||||
public void SetRootMotionBone (string name) {
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
int index = skeleton.FindBoneIndex(name);
|
||||
if (index >= 0) {
|
||||
this.rootMotionBoneIndex = index;
|
||||
this.rootMotionBone = skeleton.bones.Items[index];
|
||||
}
|
||||
else {
|
||||
Debug.Log("Bone named \"" + name + "\" could not be found.");
|
||||
this.rootMotionBoneIndex = 0;
|
||||
this.rootMotionBone = skeleton.RootBone;
|
||||
}
|
||||
}
|
||||
|
||||
public void AdjustRootMotionToDistance (Vector2 distanceToTarget, int trackIndex = 0, bool adjustX = true, bool adjustY = true,
|
||||
float minX = 0, float maxX = float.MaxValue, float minY = 0, float maxY = float.MaxValue,
|
||||
bool allowXTranslation = false, bool allowYTranslation = false) {
|
||||
|
||||
Vector2 distanceToTargetSkeletonSpace = (Vector2)transform.InverseTransformVector(distanceToTarget);
|
||||
Vector2 scaleAffectingRootMotion = GetScaleAffectingRootMotion();
|
||||
if (UsesRigidbody)
|
||||
distanceToTargetSkeletonSpace -= tempSkeletonDisplacement;
|
||||
|
||||
Vector2 remainingRootMotionSkeletonSpace = GetRemainingRootMotion(trackIndex);
|
||||
remainingRootMotionSkeletonSpace.Scale(scaleAffectingRootMotion);
|
||||
if (remainingRootMotionSkeletonSpace.x == 0)
|
||||
remainingRootMotionSkeletonSpace.x = 0.0001f;
|
||||
if (remainingRootMotionSkeletonSpace.y == 0)
|
||||
remainingRootMotionSkeletonSpace.y = 0.0001f;
|
||||
|
||||
if (adjustX)
|
||||
rootMotionScaleX = Math.Min(maxX, Math.Max(minX, distanceToTargetSkeletonSpace.x / remainingRootMotionSkeletonSpace.x));
|
||||
if (adjustY)
|
||||
rootMotionScaleY = Math.Min(maxY, Math.Max(minY, distanceToTargetSkeletonSpace.y / remainingRootMotionSkeletonSpace.y));
|
||||
|
||||
if (allowXTranslation)
|
||||
rootMotionTranslateXPerY = (distanceToTargetSkeletonSpace.x - remainingRootMotionSkeletonSpace.x * rootMotionScaleX) / remainingRootMotionSkeletonSpace.y;
|
||||
if (allowYTranslation)
|
||||
rootMotionTranslateYPerX = (distanceToTargetSkeletonSpace.y - remainingRootMotionSkeletonSpace.y * rootMotionScaleY) / remainingRootMotionSkeletonSpace.x;
|
||||
}
|
||||
|
||||
public Vector2 GetAnimationRootMotion (Animation animation) {
|
||||
return GetAnimationRootMotion(0, animation.duration, animation);
|
||||
}
|
||||
|
||||
public Vector2 GetAnimationRootMotion (float startTime, float endTime,
|
||||
Animation animation) {
|
||||
|
||||
var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
|
||||
if (timeline != null) {
|
||||
return GetTimelineMovementDelta(startTime, endTime, timeline, animation);
|
||||
}
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
public RootMotionInfo GetAnimationRootMotionInfo (Animation animation, float currentTime) {
|
||||
RootMotionInfo rootMotion = new RootMotionInfo();
|
||||
var timeline = animation.FindTranslateTimelineForBone(rootMotionBoneIndex);
|
||||
if (timeline != null) {
|
||||
float duration = animation.duration;
|
||||
float mid = duration * 0.5f;
|
||||
rootMotion.start = timeline.Evaluate(0);
|
||||
rootMotion.current = timeline.Evaluate(currentTime);
|
||||
rootMotion.mid = timeline.Evaluate(mid);
|
||||
rootMotion.end = timeline.Evaluate(duration);
|
||||
rootMotion.timeIsPastMid = currentTime > mid;
|
||||
}
|
||||
return rootMotion;
|
||||
}
|
||||
|
||||
Vector2 GetTimelineMovementDelta (float startTime, float endTime,
|
||||
TranslateTimeline timeline, Animation animation) {
|
||||
|
||||
Vector2 currentDelta;
|
||||
if (startTime > endTime) // Looped
|
||||
currentDelta = (timeline.Evaluate(animation.duration) - timeline.Evaluate(startTime))
|
||||
+ (timeline.Evaluate(endTime) - timeline.Evaluate(0));
|
||||
else if (startTime != endTime) // Non-looped
|
||||
currentDelta = timeline.Evaluate(endTime) - timeline.Evaluate(startTime);
|
||||
else
|
||||
currentDelta = Vector2.zero;
|
||||
return currentDelta;
|
||||
}
|
||||
|
||||
void GatherTopLevelBones () {
|
||||
topLevelBones.Clear();
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
foreach (var bone in skeleton.Bones) {
|
||||
if (bone.Parent == null)
|
||||
topLevelBones.Add(bone);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleUpdateLocal (ISkeletonAnimation animatedSkeletonComponent) {
|
||||
if (!this.isActiveAndEnabled)
|
||||
return; // Root motion is only applied when component is enabled.
|
||||
|
||||
var boneLocalDelta = CalculateAnimationsMovementDelta();
|
||||
Vector2 parentBoneScale;
|
||||
Vector2 skeletonDelta = GetSkeletonSpaceMovementDelta(boneLocalDelta, out parentBoneScale);
|
||||
ApplyRootMotion(skeletonDelta, parentBoneScale);
|
||||
}
|
||||
|
||||
void ApplyRootMotion (Vector2 skeletonDelta, Vector2 parentBoneScale) {
|
||||
// Apply root motion to Transform or RigidBody;
|
||||
if (UsesRigidbody) {
|
||||
rigidbodyDisplacement += (Vector2)transform.TransformVector(skeletonDelta);
|
||||
|
||||
// Accumulated displacement is applied on the next Physics update in FixedUpdate.
|
||||
// Until the next Physics update, tempBoneDisplacement is offsetting bone locations
|
||||
// to prevent stutter which would otherwise occur if we don't move every Update.
|
||||
tempSkeletonDisplacement += skeletonDelta;
|
||||
SetEffectiveBoneOffsetsTo(tempSkeletonDisplacement, parentBoneScale);
|
||||
}
|
||||
else {
|
||||
transform.position += transform.TransformVector(skeletonDelta);
|
||||
ClearEffectiveBoneOffsets(parentBoneScale);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetScaleAffectingRootMotion () {
|
||||
Vector2 parentBoneScale;
|
||||
return GetScaleAffectingRootMotion(out parentBoneScale);
|
||||
}
|
||||
|
||||
Vector2 GetScaleAffectingRootMotion (out Vector2 parentBoneScale) {
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
Vector2 totalScale = Vector2.one;
|
||||
totalScale.x *= skeleton.ScaleX;
|
||||
totalScale.y *= skeleton.ScaleY;
|
||||
|
||||
parentBoneScale = Vector2.one;
|
||||
Bone scaleBone = rootMotionBone;
|
||||
while ((scaleBone = scaleBone.parent) != null) {
|
||||
parentBoneScale.x *= scaleBone.ScaleX;
|
||||
parentBoneScale.y *= scaleBone.ScaleY;
|
||||
}
|
||||
totalScale = Vector2.Scale(totalScale, parentBoneScale);
|
||||
totalScale *= AdditionalScale;
|
||||
return totalScale;
|
||||
}
|
||||
|
||||
Vector2 GetSkeletonSpaceMovementDelta (Vector2 boneLocalDelta, out Vector2 parentBoneScale) {
|
||||
Vector2 skeletonDelta = boneLocalDelta;
|
||||
Vector2 totalScale = GetScaleAffectingRootMotion(out parentBoneScale);
|
||||
skeletonDelta.Scale(totalScale);
|
||||
|
||||
Vector2 rootMotionTranslation = new Vector2(
|
||||
rootMotionTranslateXPerY * skeletonDelta.y,
|
||||
rootMotionTranslateYPerX * skeletonDelta.x);
|
||||
|
||||
skeletonDelta.x *= rootMotionScaleX;
|
||||
skeletonDelta.y *= rootMotionScaleY;
|
||||
skeletonDelta.x += rootMotionTranslation.x;
|
||||
skeletonDelta.y += rootMotionTranslation.y;
|
||||
|
||||
if (!transformPositionX) skeletonDelta.x = 0f;
|
||||
if (!transformPositionY) skeletonDelta.y = 0f;
|
||||
return skeletonDelta;
|
||||
}
|
||||
|
||||
void SetEffectiveBoneOffsetsTo (Vector2 displacementSkeletonSpace, Vector2 parentBoneScale) {
|
||||
// Move top level bones in opposite direction of the root motion bone
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
foreach (var topLevelBone in topLevelBones) {
|
||||
if (topLevelBone == rootMotionBone) {
|
||||
if (transformPositionX) topLevelBone.x = displacementSkeletonSpace.x / skeleton.ScaleX;
|
||||
if (transformPositionY) topLevelBone.y = displacementSkeletonSpace.y / skeleton.ScaleY;
|
||||
}
|
||||
else {
|
||||
float offsetX = (initialOffset.x - rootMotionBone.x) * parentBoneScale.x;
|
||||
float offsetY = (initialOffset.y - rootMotionBone.y) * parentBoneScale.y;
|
||||
if (transformPositionX) topLevelBone.x = (displacementSkeletonSpace.x / skeleton.ScaleX) + offsetX;
|
||||
if (transformPositionY) topLevelBone.y = (displacementSkeletonSpace.y / skeleton.ScaleY) + offsetY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearEffectiveBoneOffsets (Vector2 parentBoneScale) {
|
||||
SetEffectiveBoneOffsetsTo(Vector2.zero, parentBoneScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc23a4f220b20024ab0592719f92587d
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
251
Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs
Normal file
251
Assets/Spine/Runtime/spine-unity/Components/SkeletonAnimation.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[AddComponentMenu("Spine/SkeletonAnimation")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonAnimation-Component")]
|
||||
public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation, IAnimationStateComponent {
|
||||
|
||||
#region IAnimationStateComponent
|
||||
/// <summary>
|
||||
/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it.
|
||||
/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
|
||||
public Spine.AnimationState state;
|
||||
/// <summary>
|
||||
/// This is the Spine.AnimationState object of this SkeletonAnimation. You can control animations through it.
|
||||
/// Note that this object, like .skeleton, is not guaranteed to exist in Awake. Do all accesses and caching to it in Start</summary>
|
||||
public Spine.AnimationState AnimationState {
|
||||
get {
|
||||
Initialize(false);
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
private bool wasUpdatedAfterInit = true;
|
||||
#endregion
|
||||
|
||||
#region Bone Callbacks ISkeletonAnimation
|
||||
protected event UpdateBonesDelegate _BeforeApply;
|
||||
protected event UpdateBonesDelegate _UpdateLocal;
|
||||
protected event UpdateBonesDelegate _UpdateWorld;
|
||||
protected event UpdateBonesDelegate _UpdateComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before the animations are applied.
|
||||
/// Use this callback when you want to change the skeleton state before animations are applied on top.
|
||||
/// </summary>
|
||||
public event UpdateBonesDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the animations are applied and before world space values are resolved.
|
||||
/// Use this callback when you want to set bone local values.
|
||||
/// </summary>
|
||||
public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
|
||||
/// Using this callback will cause the world space values to be solved an extra time.
|
||||
/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
|
||||
public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
|
||||
/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
|
||||
/// This callback can also be used when setting world position and the bone matrix.</summary>
|
||||
public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
|
||||
#endregion
|
||||
|
||||
#region Serialized state and Beginner API
|
||||
[SerializeField]
|
||||
[SpineAnimation]
|
||||
private string _animationName;
|
||||
|
||||
/// <summary>
|
||||
/// Setting this property sets the animation of the skeleton. If invalid, it will store the animation name for the next time the skeleton is properly initialized.
|
||||
/// Getting this property gets the name of the currently playing animation. If invalid, it will return the last stored animation name set through this property.</summary>
|
||||
public string AnimationName {
|
||||
get {
|
||||
if (!valid) {
|
||||
return _animationName;
|
||||
} else {
|
||||
TrackEntry entry = state.GetCurrent(0);
|
||||
return entry == null ? null : entry.Animation.Name;
|
||||
}
|
||||
}
|
||||
set {
|
||||
Initialize(false);
|
||||
if (_animationName == value) {
|
||||
TrackEntry entry = state.GetCurrent(0);
|
||||
if (entry != null && entry.loop == loop)
|
||||
return;
|
||||
}
|
||||
_animationName = value;
|
||||
|
||||
if (string.IsNullOrEmpty(value)) {
|
||||
state.ClearTrack(0);
|
||||
} else {
|
||||
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(value);
|
||||
if (animationObject != null)
|
||||
state.SetAnimation(0, animationObject, loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Whether or not <see cref="AnimationName"/> should loop. This only applies to the initial animation specified in the inspector, or any subsequent Animations played through .AnimationName. Animations set through state.SetAnimation are unaffected.</summary>
|
||||
public bool loop;
|
||||
|
||||
/// <summary>
|
||||
/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.</summary>
|
||||
/// <remarks>AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.</remarks>
|
||||
public float timeScale = 1;
|
||||
#endregion
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>Adds and prepares a SkeletonAnimation component to a GameObject at runtime.</summary>
|
||||
/// <returns>The newly instantiated SkeletonAnimation</returns>
|
||||
public static SkeletonAnimation AddToGameObject (GameObject gameObject, SkeletonDataAsset skeletonDataAsset,
|
||||
bool quiet = false) {
|
||||
return SkeletonRenderer.AddSpineComponent<SkeletonAnimation>(gameObject, skeletonDataAsset, quiet);
|
||||
}
|
||||
|
||||
/// <summary>Instantiates a new UnityEngine.GameObject and adds a prepared SkeletonAnimation component to it.</summary>
|
||||
/// <returns>The newly instantiated SkeletonAnimation component.</returns>
|
||||
public static SkeletonAnimation NewSkeletonAnimationGameObject (SkeletonDataAsset skeletonDataAsset,
|
||||
bool quiet = false) {
|
||||
return SkeletonRenderer.NewSpineGameObject<SkeletonAnimation>(skeletonDataAsset, quiet);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh, resets the skeleton's pose, and clears all previously active animations.</summary>
|
||||
public override void ClearState () {
|
||||
base.ClearState();
|
||||
if (state != null) state.ClearTracks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this component. Attempts to load the SkeletonData and creates the internal Spine objects and buffers.</summary>
|
||||
/// <param name="overwrite">If set to <c>true</c>, force overwrite an already initialized object.</param>
|
||||
public override void Initialize (bool overwrite, bool quiet = false) {
|
||||
if (valid && !overwrite)
|
||||
return;
|
||||
base.Initialize(overwrite, quiet);
|
||||
|
||||
if (!valid)
|
||||
return;
|
||||
state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
|
||||
wasUpdatedAfterInit = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(_animationName)) {
|
||||
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
|
||||
if (animationObject != null) {
|
||||
state.SetAnimation(0, animationObject, loop);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
Update(0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Update () {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
Update(0f);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
Update(Time.deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>Progresses the AnimationState according to the given deltaTime, and applies it to the Skeleton. Use Time.deltaTime to update manually. Use deltaTime 0 to update without progressing the time.</summary>
|
||||
public void Update (float deltaTime) {
|
||||
if (!valid || state == null)
|
||||
return;
|
||||
|
||||
wasUpdatedAfterInit = true;
|
||||
if (updateMode < UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
UpdateAnimationStatus(deltaTime);
|
||||
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
ApplyAnimation();
|
||||
}
|
||||
|
||||
protected void UpdateAnimationStatus (float deltaTime) {
|
||||
deltaTime *= timeScale;
|
||||
skeleton.Update(deltaTime);
|
||||
state.Update(deltaTime);
|
||||
}
|
||||
|
||||
protected void ApplyAnimation () {
|
||||
if (_BeforeApply != null)
|
||||
_BeforeApply(this);
|
||||
|
||||
if (updateMode != UpdateMode.OnlyEventTimelines)
|
||||
state.Apply(skeleton);
|
||||
else
|
||||
state.ApplyEventTimelinesOnly(skeleton);
|
||||
|
||||
if (_UpdateLocal != null)
|
||||
_UpdateLocal(this);
|
||||
|
||||
skeleton.UpdateWorldTransform();
|
||||
|
||||
if (_UpdateWorld != null) {
|
||||
_UpdateWorld(this);
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
if (_UpdateComplete != null) {
|
||||
_UpdateComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void LateUpdate () {
|
||||
// instantiation can happen from Update() after this component, leading to a missing Update() call.
|
||||
if (!wasUpdatedAfterInit) Update(0);
|
||||
base.LateUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d247ba06193faa74d9335f5481b2b56c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
825
Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
Normal file
825
Assets/Spine/Runtime/spine-unity/Components/SkeletonGraphic.cs
Normal file
@@ -0,0 +1,825 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(CanvasRenderer), typeof(RectTransform)), DisallowMultipleComponent]
|
||||
[AddComponentMenu("Spine/SkeletonGraphic (Unity UI Canvas)")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonGraphic-Component")]
|
||||
public class SkeletonGraphic : MaskableGraphic, ISkeletonComponent, IAnimationStateComponent, ISkeletonAnimation, IHasSkeletonDataAsset {
|
||||
|
||||
#region Inspector
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } }
|
||||
|
||||
[SpineSkin(dataField:"skeletonDataAsset", defaultAsEmptyString:true)]
|
||||
public string initialSkinName;
|
||||
public bool initialFlipX, initialFlipY;
|
||||
|
||||
[SpineAnimation(dataField:"skeletonDataAsset")]
|
||||
public string startingAnimation;
|
||||
public bool startingLoop;
|
||||
public float timeScale = 1f;
|
||||
public bool freeze;
|
||||
|
||||
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
|
||||
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
|
||||
protected UpdateMode updateMode = UpdateMode.FullUpdate;
|
||||
|
||||
/// <summary>Update mode used when the MeshRenderer becomes invisible
|
||||
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
|
||||
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
|
||||
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
|
||||
|
||||
public bool unscaledTime;
|
||||
public bool allowMultipleCanvasRenderers = false;
|
||||
public List<CanvasRenderer> canvasRenderers = new List<CanvasRenderer>();
|
||||
protected List<RawImage> rawImages = new List<RawImage>();
|
||||
protected int usedRenderersCount = 0;
|
||||
|
||||
// Submesh Separation
|
||||
public const string SeparatorPartGameObjectName = "Part";
|
||||
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
|
||||
[SerializeField] [SpineSlot] protected string[] separatorSlotNames = new string[0];
|
||||
|
||||
/// <summary>Slots that determine where the render is split. This is used by components such as SkeletonRenderSeparator so that the skeleton can be rendered by two separate renderers on different GameObjects.</summary>
|
||||
[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
|
||||
public bool enableSeparatorSlots = false;
|
||||
[SerializeField] protected List<Transform> separatorParts = new List<Transform>();
|
||||
public List<Transform> SeparatorParts { get { return separatorParts; } }
|
||||
public bool updateSeparatorPartLocation = true;
|
||||
|
||||
private bool wasUpdatedAfterInit = true;
|
||||
private Texture baseTexture = null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void OnValidate () {
|
||||
// This handles Scene View preview.
|
||||
base.OnValidate ();
|
||||
if (this.IsValid) {
|
||||
if (skeletonDataAsset == null) {
|
||||
Clear();
|
||||
} else if (skeletonDataAsset.skeletonJSON == null) {
|
||||
Clear();
|
||||
} else if (skeletonDataAsset.GetSkeletonData(true) != skeleton.data) {
|
||||
Clear();
|
||||
Initialize(true);
|
||||
if (!allowMultipleCanvasRenderers && (skeletonDataAsset.atlasAssets.Length > 1 || skeletonDataAsset.atlasAssets[0].MaterialCount > 1))
|
||||
Debug.LogError("Unity UI does not support multiple textures per Renderer. Please enable 'Advanced - Multiple CanvasRenderers' to generate the required CanvasRenderer GameObjects. Otherwise your skeleton will not be rendered correctly.", this);
|
||||
} else {
|
||||
if (freeze) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(initialSkinName)) {
|
||||
var skin = skeleton.data.FindSkin(initialSkinName);
|
||||
if (skin != null) {
|
||||
if (skin == skeleton.data.defaultSkin)
|
||||
skeleton.SetSkin((Skin)null);
|
||||
else
|
||||
skeleton.SetSkin(skin);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Only provide visual feedback to inspector changes in Unity Editor Edit mode.
|
||||
if (!Application.isPlaying) {
|
||||
skeleton.ScaleX = this.initialFlipX ? -1 : 1;
|
||||
skeleton.ScaleY = this.initialFlipY ? -1 : 1;
|
||||
|
||||
state.ClearTrack(0);
|
||||
skeleton.SetToSetupPose();
|
||||
if (!string.IsNullOrEmpty(startingAnimation)) {
|
||||
state.SetAnimation(0, startingAnimation, startingLoop);
|
||||
Update(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Under some circumstances (e.g. sometimes on the first import) OnValidate is called
|
||||
// before SpineEditorUtilities.ImportSpineContent, causing an unnecessary exception.
|
||||
// The (skeletonDataAsset.skeletonJSON != null) condition serves to prevent this exception.
|
||||
if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON != null)
|
||||
Initialize(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset () {
|
||||
|
||||
base.Reset();
|
||||
if (material == null || material.shader != Shader.Find("Spine/SkeletonGraphic"))
|
||||
Debug.LogWarning("SkeletonGraphic works best with the SkeletonGraphic material.");
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>Create a new GameObject with a SkeletonGraphic component.</summary>
|
||||
/// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
|
||||
public static SkeletonGraphic NewSkeletonGraphicGameObject (SkeletonDataAsset skeletonDataAsset, Transform parent, Material material) {
|
||||
var sg = SkeletonGraphic.AddSkeletonGraphicComponent(new GameObject("New Spine GameObject"), skeletonDataAsset, material);
|
||||
if (parent != null) sg.transform.SetParent(parent, false);
|
||||
return sg;
|
||||
}
|
||||
|
||||
/// <summary>Add a SkeletonGraphic component to a GameObject.</summary>
|
||||
/// <param name="material">Material for the canvas renderer to use. Usually, the default SkeletonGraphic material will work.</param>
|
||||
public static SkeletonGraphic AddSkeletonGraphicComponent (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, Material material) {
|
||||
var c = gameObject.AddComponent<SkeletonGraphic>();
|
||||
if (skeletonDataAsset != null) {
|
||||
c.material = material;
|
||||
c.skeletonDataAsset = skeletonDataAsset;
|
||||
c.Initialize(false);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
[System.NonSerialized] readonly Dictionary<Texture, Texture> customTextureOverride = new Dictionary<Texture, Texture>();
|
||||
/// <summary>Use this Dictionary to override a Texture with a different Texture.</summary>
|
||||
public Dictionary<Texture, Texture> CustomTextureOverride { get { return customTextureOverride; } }
|
||||
|
||||
[System.NonSerialized] readonly Dictionary<Texture, Material> customMaterialOverride = new Dictionary<Texture, Material>();
|
||||
/// <summary>Use this Dictionary to override the Material where the Texture was used at the original atlas.</summary>
|
||||
public Dictionary<Texture, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
|
||||
|
||||
// This is used by the UI system to determine what to put in the MaterialPropertyBlock.
|
||||
Texture overrideTexture;
|
||||
public Texture OverrideTexture {
|
||||
get { return overrideTexture; }
|
||||
set {
|
||||
overrideTexture = value;
|
||||
canvasRenderer.SetTexture(this.mainTexture); // Refresh canvasRenderer's texture. Make sure it handles null.
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internals
|
||||
public override Texture mainTexture {
|
||||
get {
|
||||
if (overrideTexture != null) return overrideTexture;
|
||||
return baseTexture;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Awake () {
|
||||
|
||||
base.Awake ();
|
||||
this.onCullStateChanged.AddListener(OnCullStateChanged);
|
||||
|
||||
SyncRawImagesWithCanvasRenderers();
|
||||
if (!this.IsValid) {
|
||||
#if UNITY_EDITOR
|
||||
// workaround for special import case of open scene where OnValidate and Awake are
|
||||
// called in wrong order, before setup of Spine assets.
|
||||
if (!Application.isPlaying) {
|
||||
if (this.skeletonDataAsset != null && this.skeletonDataAsset.skeletonJSON == null)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
Initialize(false);
|
||||
Rebuild(CanvasUpdate.PreRender);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy () {
|
||||
Clear();
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
public override void Rebuild (CanvasUpdate update) {
|
||||
base.Rebuild(update);
|
||||
if (canvasRenderer.cull) return;
|
||||
if (update == CanvasUpdate.PreRender) UpdateMesh(keepRendererCount : true);
|
||||
if (allowMultipleCanvasRenderers) canvasRenderer.Clear();
|
||||
}
|
||||
|
||||
protected override void OnDisable () {
|
||||
base.OnDisable();
|
||||
foreach (var canvasRenderer in canvasRenderers) {
|
||||
canvasRenderer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Update () {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
Update(0f);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (freeze) return;
|
||||
Update(unscaledTime ? Time.unscaledDeltaTime : Time.deltaTime);
|
||||
}
|
||||
|
||||
public virtual void Update (float deltaTime) {
|
||||
if (!this.IsValid) return;
|
||||
|
||||
wasUpdatedAfterInit = true;
|
||||
if (updateMode < UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
UpdateAnimationStatus(deltaTime);
|
||||
|
||||
if (updateMode == UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
ApplyAnimation();
|
||||
}
|
||||
|
||||
protected void SyncRawImagesWithCanvasRenderers () {
|
||||
rawImages.Clear();
|
||||
foreach (var canvasRenderer in canvasRenderers) {
|
||||
var rawImage = canvasRenderer.GetComponent<RawImage>();
|
||||
if (rawImage == null) {
|
||||
rawImage = canvasRenderer.gameObject.AddComponent<RawImage>();
|
||||
rawImage.maskable = this.maskable;
|
||||
rawImage.raycastTarget = false;
|
||||
}
|
||||
rawImages.Add(rawImage);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateAnimationStatus (float deltaTime) {
|
||||
deltaTime *= timeScale;
|
||||
skeleton.Update(deltaTime);
|
||||
state.Update(deltaTime);
|
||||
}
|
||||
|
||||
protected void ApplyAnimation () {
|
||||
if (BeforeApply != null)
|
||||
BeforeApply(this);
|
||||
|
||||
if (updateMode != UpdateMode.OnlyEventTimelines)
|
||||
state.Apply(skeleton);
|
||||
else
|
||||
state.ApplyEventTimelinesOnly(skeleton);
|
||||
|
||||
if (UpdateLocal != null)
|
||||
UpdateLocal(this);
|
||||
|
||||
skeleton.UpdateWorldTransform();
|
||||
|
||||
if (UpdateWorld != null) {
|
||||
UpdateWorld(this);
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
if (UpdateComplete != null)
|
||||
UpdateComplete(this);
|
||||
}
|
||||
|
||||
public void LateUpdate () {
|
||||
// instantiation can happen from Update() after this component, leading to a missing Update() call.
|
||||
if (!wasUpdatedAfterInit) Update(0);
|
||||
if (freeze) return;
|
||||
if (updateMode != UpdateMode.FullUpdate) return;
|
||||
|
||||
UpdateMesh();
|
||||
}
|
||||
|
||||
protected void OnCullStateChanged (bool culled) {
|
||||
if (culled)
|
||||
OnBecameInvisible();
|
||||
else
|
||||
OnBecameVisible();
|
||||
}
|
||||
|
||||
public void OnBecameVisible () {
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
}
|
||||
|
||||
public void OnBecameInvisible () {
|
||||
updateMode = updateWhenInvisible;
|
||||
}
|
||||
|
||||
public void ReapplySeparatorSlotNames () {
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
|
||||
string slotName = separatorSlotNames[i];
|
||||
if (slotName == "")
|
||||
continue;
|
||||
var slot = skeleton.FindSlot(slotName);
|
||||
if (slot != null) {
|
||||
separatorSlots.Add(slot);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(slotName + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
UpdateSeparatorPartParents();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region API
|
||||
protected Skeleton skeleton;
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton;
|
||||
}
|
||||
set {
|
||||
skeleton = value;
|
||||
}
|
||||
}
|
||||
public SkeletonData SkeletonData { get { return skeleton == null ? null : skeleton.data; } }
|
||||
public bool IsValid { get { return skeleton != null; } }
|
||||
|
||||
public delegate void SkeletonRendererDelegate (SkeletonGraphic skeletonGraphic);
|
||||
|
||||
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
|
||||
public event SkeletonRendererDelegate OnRebuild;
|
||||
|
||||
/// <summary>OnMeshAndMaterialsUpdated is at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
|
||||
protected Spine.AnimationState state;
|
||||
public Spine.AnimationState AnimationState {
|
||||
get {
|
||||
Initialize(false);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField] protected Spine.Unity.MeshGenerator meshGenerator = new MeshGenerator();
|
||||
public Spine.Unity.MeshGenerator MeshGenerator { get { return this.meshGenerator; } }
|
||||
DoubleBuffered<Spine.Unity.MeshRendererBuffers.SmartMesh> meshBuffers;
|
||||
SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
|
||||
readonly ExposedList<Mesh> meshes = new ExposedList<Mesh>();
|
||||
|
||||
public Mesh GetLastMesh () {
|
||||
return meshBuffers.GetCurrent().mesh;
|
||||
}
|
||||
|
||||
public bool MatchRectTransformWithBounds () {
|
||||
UpdateMesh();
|
||||
|
||||
if (!this.allowMultipleCanvasRenderers)
|
||||
return MatchRectTransformSingleRenderer();
|
||||
else
|
||||
return MatchRectTransformMultipleRenderers();
|
||||
}
|
||||
|
||||
protected bool MatchRectTransformSingleRenderer () {
|
||||
Mesh mesh = this.GetLastMesh();
|
||||
if (mesh == null) {
|
||||
return false;
|
||||
}
|
||||
if (mesh.vertexCount == 0) {
|
||||
this.rectTransform.sizeDelta = new Vector2(50f, 50f);
|
||||
this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
return false;
|
||||
}
|
||||
mesh.RecalculateBounds();
|
||||
SetRectTransformBounds(mesh.bounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool MatchRectTransformMultipleRenderers () {
|
||||
bool anyBoundsAdded = false;
|
||||
Bounds combinedBounds = new Bounds();
|
||||
for (int i = 0; i < canvasRenderers.Count; ++i) {
|
||||
var canvasRenderer = canvasRenderers[i];
|
||||
if (!canvasRenderer.gameObject.activeSelf)
|
||||
continue;
|
||||
|
||||
Mesh mesh = meshes.Items[i];
|
||||
if (mesh == null || mesh.vertexCount == 0)
|
||||
continue;
|
||||
|
||||
mesh.RecalculateBounds();
|
||||
var bounds = mesh.bounds;
|
||||
if (anyBoundsAdded)
|
||||
combinedBounds.Encapsulate(bounds);
|
||||
else {
|
||||
anyBoundsAdded = true;
|
||||
combinedBounds = bounds;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyBoundsAdded) {
|
||||
this.rectTransform.sizeDelta = new Vector2(50f, 50f);
|
||||
this.rectTransform.pivot = new Vector2(0.5f, 0.5f);
|
||||
return false;
|
||||
}
|
||||
|
||||
SetRectTransformBounds(combinedBounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetRectTransformBounds (Bounds combinedBounds) {
|
||||
var size = combinedBounds.size;
|
||||
var center = combinedBounds.center;
|
||||
var p = new Vector2(
|
||||
0.5f - (center.x / size.x),
|
||||
0.5f - (center.y / size.y)
|
||||
);
|
||||
|
||||
this.rectTransform.sizeDelta = size;
|
||||
this.rectTransform.pivot = p;
|
||||
}
|
||||
|
||||
public event UpdateBonesDelegate BeforeApply;
|
||||
public event UpdateBonesDelegate UpdateLocal;
|
||||
public event UpdateBonesDelegate UpdateWorld;
|
||||
public event UpdateBonesDelegate UpdateComplete;
|
||||
|
||||
/// <summary> Occurs after the vertex data populated every frame, before the vertices are pushed into the mesh.</summary>
|
||||
public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
|
||||
|
||||
public void Clear () {
|
||||
skeleton = null;
|
||||
canvasRenderer.Clear();
|
||||
|
||||
for (int i = 0; i < canvasRenderers.Count; ++i)
|
||||
canvasRenderers[i].Clear();
|
||||
DestroyMeshes();
|
||||
DisposeMeshBuffers();
|
||||
}
|
||||
|
||||
public void TrimRenderers () {
|
||||
var newList = new List<CanvasRenderer>();
|
||||
foreach (var canvasRenderer in canvasRenderers) {
|
||||
if (canvasRenderer.gameObject.activeSelf) {
|
||||
newList.Add(canvasRenderer);
|
||||
}
|
||||
else {
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
DestroyImmediate(canvasRenderer.gameObject);
|
||||
else
|
||||
Destroy(canvasRenderer.gameObject);
|
||||
}
|
||||
}
|
||||
canvasRenderers = newList;
|
||||
SyncRawImagesWithCanvasRenderers();
|
||||
}
|
||||
|
||||
public void Initialize (bool overwrite) {
|
||||
if (this.IsValid && !overwrite) return;
|
||||
|
||||
if (this.skeletonDataAsset == null) return;
|
||||
var skeletonData = this.skeletonDataAsset.GetSkeletonData(false);
|
||||
if (skeletonData == null) return;
|
||||
|
||||
if (skeletonDataAsset.atlasAssets.Length <= 0 || skeletonDataAsset.atlasAssets[0].MaterialCount <= 0) return;
|
||||
|
||||
this.state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
|
||||
if (state == null) {
|
||||
Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.skeleton = new Skeleton(skeletonData) {
|
||||
ScaleX = this.initialFlipX ? -1 : 1,
|
||||
ScaleY = this.initialFlipY ? -1 : 1
|
||||
};
|
||||
|
||||
InitMeshBuffers();
|
||||
baseTexture = skeletonDataAsset.atlasAssets[0].PrimaryMaterial.mainTexture;
|
||||
canvasRenderer.SetTexture(this.mainTexture); // Needed for overwriting initializations.
|
||||
|
||||
// Set the initial Skin and Animation
|
||||
if (!string.IsNullOrEmpty(initialSkinName))
|
||||
skeleton.SetSkin(initialSkinName);
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0; i < separatorSlotNames.Length; i++)
|
||||
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
|
||||
|
||||
wasUpdatedAfterInit = false;
|
||||
if (!string.IsNullOrEmpty(startingAnimation)) {
|
||||
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(startingAnimation);
|
||||
if (animationObject != null) {
|
||||
state.SetAnimation(0, animationObject, startingLoop);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
Update(0f);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (OnRebuild != null)
|
||||
OnRebuild(this);
|
||||
}
|
||||
|
||||
public void UpdateMesh (bool keepRendererCount = false) {
|
||||
if (!this.IsValid) return;
|
||||
|
||||
skeleton.SetColor(this.color);
|
||||
|
||||
var currentInstructions = this.currentInstructions;
|
||||
if (!this.allowMultipleCanvasRenderers) {
|
||||
UpdateMeshSingleCanvasRenderer();
|
||||
}
|
||||
else {
|
||||
UpdateMeshMultipleCanvasRenderers(currentInstructions, keepRendererCount);
|
||||
}
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public bool HasMultipleSubmeshInstructions () {
|
||||
if (!IsValid)
|
||||
return false;
|
||||
return MeshGenerator.RequiresMultipleSubmeshesByDrawOrder(skeleton);
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected void InitMeshBuffers () {
|
||||
if (meshBuffers != null) {
|
||||
meshBuffers.GetNext().Clear();
|
||||
meshBuffers.GetNext().Clear();
|
||||
}
|
||||
else {
|
||||
meshBuffers = new DoubleBuffered<MeshRendererBuffers.SmartMesh>();
|
||||
}
|
||||
}
|
||||
|
||||
protected void DisposeMeshBuffers () {
|
||||
if (meshBuffers != null) {
|
||||
meshBuffers.GetNext().Dispose();
|
||||
meshBuffers.GetNext().Dispose();
|
||||
meshBuffers = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateMeshSingleCanvasRenderer () {
|
||||
if (canvasRenderers.Count > 0)
|
||||
DisableUnusedCanvasRenderers(usedCount : 0);
|
||||
|
||||
var smartMesh = meshBuffers.GetNext();
|
||||
MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, null);
|
||||
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
|
||||
|
||||
meshGenerator.Begin();
|
||||
if (currentInstructions.hasActiveClipping && currentInstructions.submeshInstructions.Count > 0) {
|
||||
meshGenerator.AddSubmesh(currentInstructions.submeshInstructions.Items[0], updateTriangles);
|
||||
}
|
||||
else {
|
||||
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
|
||||
}
|
||||
|
||||
if (canvas != null) meshGenerator.ScaleVertexData(canvas.referencePixelsPerUnit);
|
||||
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
|
||||
|
||||
var mesh = smartMesh.mesh;
|
||||
meshGenerator.FillVertexData(mesh);
|
||||
if (updateTriangles) meshGenerator.FillTriangles(mesh);
|
||||
meshGenerator.FillLateVertexData(mesh);
|
||||
|
||||
canvasRenderer.SetMesh(mesh);
|
||||
smartMesh.instructionUsed.Set(currentInstructions);
|
||||
|
||||
if (currentInstructions.submeshInstructions.Count > 0) {
|
||||
var material = currentInstructions.submeshInstructions.Items[0].material;
|
||||
if (material != null && baseTexture != material.mainTexture) {
|
||||
baseTexture = material.mainTexture;
|
||||
if (overrideTexture == null)
|
||||
canvasRenderer.SetTexture(this.mainTexture);
|
||||
}
|
||||
}
|
||||
|
||||
//this.UpdateMaterial(); // note: This would allocate memory.
|
||||
usedRenderersCount = 0;
|
||||
}
|
||||
|
||||
protected void UpdateMeshMultipleCanvasRenderers (SkeletonRendererInstruction currentInstructions, bool keepRendererCount) {
|
||||
MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, null,
|
||||
enableSeparatorSlots ? separatorSlots : null,
|
||||
enableSeparatorSlots ? separatorSlots.Count > 0 : false,
|
||||
false);
|
||||
|
||||
int submeshCount = currentInstructions.submeshInstructions.Count;
|
||||
if (keepRendererCount && submeshCount != usedRenderersCount)
|
||||
return;
|
||||
EnsureCanvasRendererCount(submeshCount);
|
||||
EnsureMeshesCount(submeshCount);
|
||||
EnsureSeparatorPartCount();
|
||||
|
||||
var c = canvas;
|
||||
float scale = (c == null) ? 100 : c.referencePixelsPerUnit;
|
||||
|
||||
// Generate meshes.
|
||||
var meshesItems = meshes.Items;
|
||||
bool useOriginalTextureAndMaterial = (customMaterialOverride.Count == 0 && customTextureOverride.Count == 0);
|
||||
int separatorSlotGroupIndex = 0;
|
||||
Transform parent = this.separatorSlots.Count == 0 ? this.transform : this.separatorParts[0];
|
||||
|
||||
if (updateSeparatorPartLocation) {
|
||||
for (int p = 0; p < this.separatorParts.Count; ++p) {
|
||||
separatorParts[p].position = this.transform.position;
|
||||
separatorParts[p].rotation = this.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
int targetSiblingIndex = 0;
|
||||
for (int i = 0; i < submeshCount; i++) {
|
||||
var submeshInstructionItem = currentInstructions.submeshInstructions.Items[i];
|
||||
meshGenerator.Begin();
|
||||
meshGenerator.AddSubmesh(submeshInstructionItem);
|
||||
|
||||
var targetMesh = meshesItems[i];
|
||||
meshGenerator.ScaleVertexData(scale);
|
||||
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
|
||||
meshGenerator.FillVertexData(targetMesh);
|
||||
meshGenerator.FillTriangles(targetMesh);
|
||||
meshGenerator.FillLateVertexData(targetMesh);
|
||||
|
||||
var submeshMaterial = submeshInstructionItem.material;
|
||||
var canvasRenderer = canvasRenderers[i];
|
||||
if (i >= usedRenderersCount)
|
||||
canvasRenderer.gameObject.SetActive(true);
|
||||
|
||||
canvasRenderer.SetMesh(targetMesh);
|
||||
canvasRenderer.materialCount = 1;
|
||||
|
||||
if (canvasRenderer.transform.parent != parent.transform) {
|
||||
canvasRenderer.transform.SetParent(parent.transform, false);
|
||||
canvasRenderer.transform.localPosition = Vector3.zero;
|
||||
}
|
||||
canvasRenderer.transform.SetSiblingIndex(targetSiblingIndex++);
|
||||
if (submeshInstructionItem.forceSeparate) {
|
||||
targetSiblingIndex = 0;
|
||||
parent = separatorParts[++separatorSlotGroupIndex];
|
||||
}
|
||||
|
||||
if (useOriginalTextureAndMaterial)
|
||||
canvasRenderer.SetMaterial(this.materialForRendering, submeshMaterial.mainTexture);
|
||||
else {
|
||||
var originalTexture = submeshMaterial.mainTexture;
|
||||
Material usedMaterial;
|
||||
Texture usedTexture;
|
||||
if (!customMaterialOverride.TryGetValue(originalTexture, out usedMaterial))
|
||||
usedMaterial = material;
|
||||
if (!customTextureOverride.TryGetValue(originalTexture, out usedTexture))
|
||||
usedTexture = originalTexture;
|
||||
canvasRenderer.SetMaterial(usedMaterial, usedTexture);
|
||||
}
|
||||
}
|
||||
|
||||
DisableUnusedCanvasRenderers(usedCount : submeshCount);
|
||||
usedRenderersCount = submeshCount;
|
||||
}
|
||||
|
||||
protected void EnsureCanvasRendererCount (int targetCount) {
|
||||
#if UNITY_EDITOR
|
||||
RemoveNullCanvasRenderers();
|
||||
#endif
|
||||
int currentCount = canvasRenderers.Count;
|
||||
for (int i = currentCount; i < targetCount; ++i) {
|
||||
var go = new GameObject(string.Format("Renderer{0}", i), typeof(RectTransform));
|
||||
go.transform.SetParent(this.transform, false);
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
var canvasRenderer = go.AddComponent<CanvasRenderer>();
|
||||
canvasRenderers.Add(canvasRenderer);
|
||||
var rawImage = go.AddComponent<RawImage>();
|
||||
rawImage.maskable = this.maskable;
|
||||
rawImage.raycastTarget = false;
|
||||
rawImages.Add(rawImage);
|
||||
}
|
||||
}
|
||||
|
||||
protected void DisableUnusedCanvasRenderers (int usedCount) {
|
||||
#if UNITY_EDITOR
|
||||
RemoveNullCanvasRenderers();
|
||||
#endif
|
||||
for (int i = usedCount; i < canvasRenderers.Count; i++) {
|
||||
canvasRenderers[i].Clear();
|
||||
canvasRenderers[i].gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void RemoveNullCanvasRenderers () {
|
||||
if (Application.isEditor && !Application.isPlaying) {
|
||||
for (int i = canvasRenderers.Count - 1; i >= 0; --i) {
|
||||
if (canvasRenderers[i] == null) {
|
||||
canvasRenderers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
protected void EnsureMeshesCount (int targetCount) {
|
||||
int oldCount = meshes.Count;
|
||||
meshes.EnsureCapacity(targetCount);
|
||||
for (int i = oldCount; i < targetCount; i++)
|
||||
meshes.Add(SpineMesh.NewSkeletonMesh());
|
||||
}
|
||||
|
||||
protected void DestroyMeshes () {
|
||||
foreach (var mesh in meshes) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
UnityEngine.Object.DestroyImmediate(mesh);
|
||||
else
|
||||
UnityEngine.Object.Destroy(mesh);
|
||||
#else
|
||||
UnityEngine.Object.Destroy(mesh);
|
||||
#endif
|
||||
}
|
||||
meshes.Clear();
|
||||
}
|
||||
|
||||
protected void EnsureSeparatorPartCount () {
|
||||
#if UNITY_EDITOR
|
||||
RemoveNullSeparatorParts();
|
||||
#endif
|
||||
int targetCount = separatorSlots.Count + 1;
|
||||
if (targetCount == 1)
|
||||
return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying) {
|
||||
for (int i = separatorParts.Count-1; i >= 0; --i) {
|
||||
if (separatorParts[i] == null) {
|
||||
separatorParts.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
int currentCount = separatorParts.Count;
|
||||
for (int i = currentCount; i < targetCount; ++i) {
|
||||
var go = new GameObject(string.Format("{0}[{1}]", SeparatorPartGameObjectName, i), typeof(RectTransform));
|
||||
go.transform.SetParent(this.transform, false);
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
separatorParts.Add(go.transform);
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateSeparatorPartParents () {
|
||||
int usedCount = separatorSlots.Count + 1;
|
||||
if (usedCount == 1) {
|
||||
usedCount = 0; // placed directly at the SkeletonGraphic parent
|
||||
for (int i = 0; i < canvasRenderers.Count; ++i) {
|
||||
var canvasRenderer = canvasRenderers[i];
|
||||
if (canvasRenderer.transform.parent.name.Contains(SeparatorPartGameObjectName)) {
|
||||
canvasRenderer.transform.SetParent(this.transform, false);
|
||||
canvasRenderer.transform.localPosition = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < separatorParts.Count; ++i) {
|
||||
bool isUsed = i < usedCount;
|
||||
separatorParts[i].gameObject.SetActive(isUsed);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void RemoveNullSeparatorParts () {
|
||||
if (Application.isEditor && !Application.isPlaying) {
|
||||
for (int i = separatorParts.Count - 1; i >= 0; --i) {
|
||||
if (separatorParts[i] == null) {
|
||||
separatorParts.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d85b887af7e6c3f45a2e2d2920d641bc
|
||||
timeCreated: 1455576193
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_Material: {fileID: 2100000, guid: b66cf7a186d13054989b33a5c90044e4, type: 2}
|
||||
- skeletonDataAsset: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
661
Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs
Normal file
661
Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs
Normal file
@@ -0,0 +1,661 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[RequireComponent(typeof(Animator))]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonMecanim-Component")]
|
||||
public class SkeletonMecanim : SkeletonRenderer, ISkeletonAnimation {
|
||||
|
||||
[SerializeField] protected MecanimTranslator translator;
|
||||
public MecanimTranslator Translator { get { return translator; } }
|
||||
private bool wasUpdatedAfterInit = true;
|
||||
|
||||
#region Bone Callbacks (ISkeletonAnimation)
|
||||
protected event UpdateBonesDelegate _BeforeApply;
|
||||
protected event UpdateBonesDelegate _UpdateLocal;
|
||||
protected event UpdateBonesDelegate _UpdateWorld;
|
||||
protected event UpdateBonesDelegate _UpdateComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs before the animations are applied.
|
||||
/// Use this callback when you want to change the skeleton state before animations are applied on top.
|
||||
/// </summary>
|
||||
public event UpdateBonesDelegate BeforeApply { add { _BeforeApply += value; } remove { _BeforeApply -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the animations are applied and before world space values are resolved.
|
||||
/// Use this callback when you want to set bone local values.</summary>
|
||||
public event UpdateBonesDelegate UpdateLocal { add { _UpdateLocal += value; } remove { _UpdateLocal -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
|
||||
/// Using this callback will cause the world space values to be solved an extra time.
|
||||
/// Use this callback if want to use bone world space values, and also set bone local values.</summary>
|
||||
public event UpdateBonesDelegate UpdateWorld { add { _UpdateWorld += value; } remove { _UpdateWorld -= value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
|
||||
/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
|
||||
/// This callback can also be used when setting world position and the bone matrix.</summary>
|
||||
public event UpdateBonesDelegate UpdateComplete { add { _UpdateComplete += value; } remove { _UpdateComplete -= value; } }
|
||||
#endregion
|
||||
|
||||
public override void Initialize (bool overwrite, bool quiet = false) {
|
||||
if (valid && !overwrite)
|
||||
return;
|
||||
|
||||
base.Initialize(overwrite, quiet);
|
||||
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
if (translator == null) translator = new MecanimTranslator();
|
||||
translator.Initialize(GetComponent<Animator>(), this.skeletonDataAsset);
|
||||
wasUpdatedAfterInit = false;
|
||||
}
|
||||
|
||||
public void Update () {
|
||||
if (!valid) return;
|
||||
|
||||
wasUpdatedAfterInit = true;
|
||||
// animation status is kept by Mecanim Animator component
|
||||
if (updateMode <= UpdateMode.OnlyAnimationStatus)
|
||||
return;
|
||||
ApplyAnimation();
|
||||
}
|
||||
|
||||
protected void ApplyAnimation () {
|
||||
if (_BeforeApply != null)
|
||||
_BeforeApply(this);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var translatorAnimator = translator.Animator;
|
||||
if (translatorAnimator != null && !translatorAnimator.isInitialized)
|
||||
translatorAnimator.Rebind();
|
||||
|
||||
if (Application.isPlaying) {
|
||||
translator.Apply(skeleton);
|
||||
}
|
||||
else {
|
||||
if (translatorAnimator != null && translatorAnimator.isInitialized &&
|
||||
translatorAnimator.isActiveAndEnabled && translatorAnimator.runtimeAnimatorController != null) {
|
||||
// Note: Rebind is required to prevent warning "Animator is not playing an AnimatorController" with prefabs
|
||||
translatorAnimator.Rebind();
|
||||
translator.Apply(skeleton);
|
||||
}
|
||||
}
|
||||
#else
|
||||
translator.Apply(skeleton);
|
||||
#endif
|
||||
|
||||
// UpdateWorldTransform and Bone Callbacks
|
||||
{
|
||||
if (_UpdateLocal != null)
|
||||
_UpdateLocal(this);
|
||||
|
||||
skeleton.UpdateWorldTransform();
|
||||
|
||||
if (_UpdateWorld != null) {
|
||||
_UpdateWorld(this);
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
if (_UpdateComplete != null)
|
||||
_UpdateComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void LateUpdate () {
|
||||
// instantiation can happen from Update() after this component, leading to a missing Update() call.
|
||||
if (!wasUpdatedAfterInit) Update();
|
||||
base.LateUpdate();
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class MecanimTranslator {
|
||||
|
||||
const float WeightEpsilon = 0.0001f;
|
||||
|
||||
#region Inspector
|
||||
public bool autoReset = true;
|
||||
public bool useCustomMixMode = true;
|
||||
public MixMode[] layerMixModes = new MixMode[0];
|
||||
public MixBlend[] layerBlendModes = new MixBlend[0];
|
||||
#endregion
|
||||
|
||||
public delegate void OnClipAppliedDelegate (Spine.Animation clip, int layerIndex, float weight,
|
||||
float time, float lastTime, bool playsBackward);
|
||||
protected event OnClipAppliedDelegate _OnClipApplied;
|
||||
|
||||
public event OnClipAppliedDelegate OnClipApplied { add { _OnClipApplied += value; } remove { _OnClipApplied -= value; } }
|
||||
|
||||
public enum MixMode { AlwaysMix, MixNext, Hard }
|
||||
|
||||
readonly Dictionary<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>(IntEqualityComparer.Instance);
|
||||
readonly Dictionary<AnimationClip, int> clipNameHashCodeTable = new Dictionary<AnimationClip, int>(AnimationClipEqualityComparer.Instance);
|
||||
readonly List<Animation> previousAnimations = new List<Animation>();
|
||||
|
||||
protected class ClipInfos {
|
||||
public bool isInterruptionActive = false;
|
||||
public bool isLastFrameOfInterruption = false;
|
||||
|
||||
public int clipInfoCount = 0;
|
||||
public int nextClipInfoCount = 0;
|
||||
public int interruptingClipInfoCount = 0;
|
||||
public readonly List<AnimatorClipInfo> clipInfos = new List<AnimatorClipInfo>();
|
||||
public readonly List<AnimatorClipInfo> nextClipInfos = new List<AnimatorClipInfo>();
|
||||
public readonly List<AnimatorClipInfo> interruptingClipInfos = new List<AnimatorClipInfo>();
|
||||
|
||||
public AnimatorStateInfo stateInfo;
|
||||
public AnimatorStateInfo nextStateInfo;
|
||||
public AnimatorStateInfo interruptingStateInfo;
|
||||
|
||||
public float interruptingClipTimeAddition = 0;
|
||||
}
|
||||
protected ClipInfos[] layerClipInfos = new ClipInfos[0];
|
||||
|
||||
Animator animator;
|
||||
public Animator Animator { get { return this.animator; } }
|
||||
|
||||
public int MecanimLayerCount {
|
||||
get {
|
||||
if (!animator)
|
||||
return 0;
|
||||
return animator.layerCount;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] MecanimLayerNames {
|
||||
get {
|
||||
if (!animator)
|
||||
return new string[0];
|
||||
string[] layerNames = new string[animator.layerCount];
|
||||
for (int i = 0; i < animator.layerCount; ++i) {
|
||||
layerNames[i] = animator.GetLayerName(i);
|
||||
}
|
||||
return layerNames;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize(Animator animator, SkeletonDataAsset skeletonDataAsset) {
|
||||
this.animator = animator;
|
||||
|
||||
previousAnimations.Clear();
|
||||
|
||||
animationTable.Clear();
|
||||
var data = skeletonDataAsset.GetSkeletonData(true);
|
||||
foreach (var a in data.Animations)
|
||||
animationTable.Add(a.Name.GetHashCode(), a);
|
||||
|
||||
clipNameHashCodeTable.Clear();
|
||||
ClearClipInfosForLayers();
|
||||
}
|
||||
|
||||
private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useClipWeight1 = false) {
|
||||
float weight = info.weight * layerWeight;
|
||||
if (weight < WeightEpsilon)
|
||||
return false;
|
||||
|
||||
var clip = GetAnimation(info.clip);
|
||||
if (clip == null)
|
||||
return false;
|
||||
|
||||
var time = AnimationTime(stateInfo.normalizedTime, info.clip.length,
|
||||
info.clip.isLooping, stateInfo.speed < 0);
|
||||
weight = useClipWeight1 ? layerWeight : weight;
|
||||
clip.Apply(skeleton, 0, time, info.clip.isLooping, null,
|
||||
weight, layerBlendMode, MixDirection.In);
|
||||
if (_OnClipApplied != null)
|
||||
OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ApplyInterruptionAnimation (Skeleton skeleton,
|
||||
bool interpolateWeightTo1, AnimatorClipInfo info, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float layerWeight, MixBlend layerBlendMode, float interruptingClipTimeAddition,
|
||||
bool useClipWeight1 = false) {
|
||||
|
||||
float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
|
||||
float weight = clipWeight * layerWeight;
|
||||
if (weight < WeightEpsilon)
|
||||
return false;
|
||||
|
||||
var clip = GetAnimation(info.clip);
|
||||
if (clip == null)
|
||||
return false;
|
||||
|
||||
var time = AnimationTime(stateInfo.normalizedTime + interruptingClipTimeAddition,
|
||||
info.clip.length, stateInfo.speed < 0);
|
||||
weight = useClipWeight1 ? layerWeight : weight;
|
||||
clip.Apply(skeleton, 0, time, info.clip.isLooping, null,
|
||||
weight, layerBlendMode, MixDirection.In);
|
||||
if (_OnClipApplied != null) {
|
||||
OnClipAppliedCallback(clip, stateInfo, layerIndex, time, info.clip.isLooping, weight);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnClipAppliedCallback (Spine.Animation clip, AnimatorStateInfo stateInfo,
|
||||
int layerIndex, float time, bool isLooping, float weight) {
|
||||
|
||||
float speedFactor = stateInfo.speedMultiplier * stateInfo.speed;
|
||||
float lastTime = time - (Time.deltaTime * speedFactor);
|
||||
if (isLooping && clip.duration != 0) {
|
||||
time %= clip.duration;
|
||||
lastTime %= clip.duration;
|
||||
}
|
||||
_OnClipApplied(clip, layerIndex, weight, time, lastTime, speedFactor < 0);
|
||||
}
|
||||
|
||||
public void Apply (Skeleton skeleton) {
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
GetLayerBlendModes();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (layerMixModes.Length < animator.layerCount) {
|
||||
int oldSize = layerMixModes.Length;
|
||||
System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
|
||||
for (int layer = oldSize; layer < animator.layerCount; ++layer) {
|
||||
bool isAdditiveLayer = false;
|
||||
if (layer < layerBlendModes.Length)
|
||||
isAdditiveLayer = layerBlendModes[layer] == MixBlend.Add;
|
||||
layerMixModes[layer] = isAdditiveLayer ? MixMode.AlwaysMix : MixMode.MixNext;
|
||||
}
|
||||
}
|
||||
|
||||
InitClipInfosForLayers();
|
||||
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
|
||||
GetStateUpdatesFromAnimator(layer);
|
||||
}
|
||||
|
||||
// Clear Previous
|
||||
if (autoReset) {
|
||||
var previousAnimations = this.previousAnimations;
|
||||
for (int i = 0, n = previousAnimations.Count; i < n; i++)
|
||||
previousAnimations[i].SetKeyedItemsToSetupPose(skeleton);
|
||||
|
||||
previousAnimations.Clear();
|
||||
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
|
||||
float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
|
||||
if (layerWeight <= 0) continue;
|
||||
|
||||
AnimatorStateInfo nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
|
||||
|
||||
bool hasNext = nextStateInfo.fullPathHash != 0;
|
||||
|
||||
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
|
||||
IList<AnimatorClipInfo> clipInfo, nextClipInfo, interruptingClipInfo;
|
||||
bool isInterruptionActive, shallInterpolateWeightTo1;
|
||||
GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
|
||||
out clipInfo, out nextClipInfo, out interruptingClipInfo, out shallInterpolateWeightTo1);
|
||||
|
||||
for (int c = 0; c < clipInfoCount; c++) {
|
||||
var info = clipInfo[c];
|
||||
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
var clip = GetAnimation(info.clip);
|
||||
if (clip != null)
|
||||
previousAnimations.Add(clip);
|
||||
}
|
||||
|
||||
if (hasNext) {
|
||||
for (int c = 0; c < nextClipInfoCount; c++) {
|
||||
var info = nextClipInfo[c];
|
||||
float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
var clip = GetAnimation(info.clip);
|
||||
if (clip != null)
|
||||
previousAnimations.Add(clip);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInterruptionActive) {
|
||||
for (int c = 0; c < interruptingClipInfoCount; c++)
|
||||
{
|
||||
var info = interruptingClipInfo[c];
|
||||
float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight;
|
||||
float weight = clipWeight * layerWeight; if (weight < WeightEpsilon) continue;
|
||||
var clip = GetAnimation(info.clip);
|
||||
if (clip != null)
|
||||
previousAnimations.Add(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply
|
||||
for (int layer = 0, n = animator.layerCount; layer < n; layer++) {
|
||||
float layerWeight = (layer == 0) ? 1 : animator.GetLayerWeight(layer); // Animator.GetLayerWeight always returns 0 on the first layer. Should be interpreted as 1.
|
||||
|
||||
bool isInterruptionActive;
|
||||
AnimatorStateInfo stateInfo;
|
||||
AnimatorStateInfo nextStateInfo;
|
||||
AnimatorStateInfo interruptingStateInfo;
|
||||
float interruptingClipTimeAddition;
|
||||
GetAnimatorStateInfos(layer, out isInterruptionActive, out stateInfo, out nextStateInfo, out interruptingStateInfo, out interruptingClipTimeAddition);
|
||||
|
||||
bool hasNext = nextStateInfo.fullPathHash != 0;
|
||||
|
||||
int clipInfoCount, nextClipInfoCount, interruptingClipInfoCount;
|
||||
IList<AnimatorClipInfo> clipInfo, nextClipInfo, interruptingClipInfo;
|
||||
bool interpolateWeightTo1;
|
||||
GetAnimatorClipInfos(layer, out isInterruptionActive, out clipInfoCount, out nextClipInfoCount, out interruptingClipInfoCount,
|
||||
out clipInfo, out nextClipInfo, out interruptingClipInfo, out interpolateWeightTo1);
|
||||
|
||||
MixBlend layerBlendMode = (layer < layerBlendModes.Length) ? layerBlendModes[layer] : MixBlend.Replace;
|
||||
MixMode mode = GetMixMode(layer, layerBlendMode);
|
||||
if (mode == MixMode.AlwaysMix) {
|
||||
// Always use Mix instead of Applying the first non-zero weighted clip.
|
||||
for (int c = 0; c < clipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode);
|
||||
}
|
||||
if (hasNext) {
|
||||
for (int c = 0; c < nextClipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode);
|
||||
}
|
||||
}
|
||||
if (isInterruptionActive) {
|
||||
for (int c = 0; c < interruptingClipInfoCount; c++)
|
||||
{
|
||||
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition);
|
||||
}
|
||||
}
|
||||
} else { // case MixNext || Hard
|
||||
// Apply first non-zero weighted clip
|
||||
int c = 0;
|
||||
for (; c < clipInfoCount; c++) {
|
||||
if (!ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true))
|
||||
continue;
|
||||
++c; break;
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < clipInfoCount; c++) {
|
||||
ApplyAnimation(skeleton, clipInfo[c], stateInfo, layer, layerWeight, layerBlendMode);
|
||||
}
|
||||
|
||||
c = 0;
|
||||
if (hasNext) {
|
||||
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
|
||||
if (mode == MixMode.Hard) {
|
||||
for (; c < nextClipInfoCount; c++) {
|
||||
if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode, useClipWeight1:true))
|
||||
continue;
|
||||
++c; break;
|
||||
}
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < nextClipInfoCount; c++) {
|
||||
if (!ApplyAnimation(skeleton, nextClipInfo[c], nextStateInfo, layer, layerWeight, layerBlendMode))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
c = 0;
|
||||
if (isInterruptionActive) {
|
||||
// Apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
|
||||
if (mode == MixMode.Hard) {
|
||||
for (; c < interruptingClipInfoCount; c++) {
|
||||
if (ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition, useClipWeight1:true)) {
|
||||
|
||||
++c; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Mix the rest
|
||||
for (; c < interruptingClipInfoCount; c++) {
|
||||
ApplyInterruptionAnimation(skeleton, interpolateWeightTo1,
|
||||
interruptingClipInfo[c], interruptingStateInfo,
|
||||
layer, layerWeight, layerBlendMode, interruptingClipTimeAddition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public KeyValuePair<Spine.Animation, float> GetActiveAnimationAndTime (int layer) {
|
||||
if (layer >= layerClipInfos.Length)
|
||||
return new KeyValuePair<Spine.Animation, float>(null, 0);
|
||||
|
||||
var layerInfos = layerClipInfos[layer];
|
||||
bool isInterruptionActive = layerInfos.isInterruptionActive;
|
||||
AnimationClip clip = null;
|
||||
Spine.Animation animation = null;
|
||||
AnimatorStateInfo stateInfo;
|
||||
if (isInterruptionActive && layerInfos.interruptingClipInfoCount > 0) {
|
||||
clip = layerInfos.interruptingClipInfos[0].clip;
|
||||
stateInfo = layerInfos.interruptingStateInfo;
|
||||
}
|
||||
else {
|
||||
clip = layerInfos.clipInfos[0].clip;
|
||||
stateInfo = layerInfos.stateInfo;
|
||||
}
|
||||
animation = GetAnimation(clip);
|
||||
float time = AnimationTime(stateInfo.normalizedTime, clip.length,
|
||||
clip.isLooping, stateInfo.speed < 0);
|
||||
return new KeyValuePair<Animation, float>(animation, time);
|
||||
}
|
||||
|
||||
static float AnimationTime (float normalizedTime, float clipLength, bool loop, bool reversed) {
|
||||
float time = AnimationTime(normalizedTime, clipLength, reversed);
|
||||
if (loop) return time;
|
||||
const float EndSnapEpsilon = 1f / 30f; // Workaround for end-duration keys not being applied.
|
||||
return (clipLength - time < EndSnapEpsilon) ? clipLength : time; // return a time snapped to clipLength;
|
||||
}
|
||||
|
||||
static float AnimationTime (float normalizedTime, float clipLength, bool reversed) {
|
||||
if (reversed)
|
||||
normalizedTime = (1 - normalizedTime);
|
||||
if (normalizedTime < 0.0f)
|
||||
normalizedTime = (normalizedTime % 1.0f) + 1.0f;
|
||||
return normalizedTime * clipLength;
|
||||
}
|
||||
|
||||
void InitClipInfosForLayers () {
|
||||
if (layerClipInfos.Length < animator.layerCount) {
|
||||
System.Array.Resize<ClipInfos>(ref layerClipInfos, animator.layerCount);
|
||||
for (int layer = 0, n = animator.layerCount; layer < n; ++layer) {
|
||||
if (layerClipInfos[layer] == null)
|
||||
layerClipInfos[layer] = new ClipInfos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearClipInfosForLayers () {
|
||||
for (int layer = 0, n = layerClipInfos.Length; layer < n; ++layer) {
|
||||
if (layerClipInfos[layer] == null)
|
||||
layerClipInfos[layer] = new ClipInfos();
|
||||
else {
|
||||
layerClipInfos[layer].isInterruptionActive = false;
|
||||
layerClipInfos[layer].isLastFrameOfInterruption = false;
|
||||
layerClipInfos[layer].clipInfos.Clear();
|
||||
layerClipInfos[layer].nextClipInfos.Clear();
|
||||
layerClipInfos[layer].interruptingClipInfos.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MixMode GetMixMode (int layer, MixBlend layerBlendMode) {
|
||||
if (useCustomMixMode) {
|
||||
MixMode mode = layerMixModes[layer];
|
||||
// Note: at additive blending it makes no sense to use constant weight 1 at a fadeout anim add1 as
|
||||
// with override layers, so we use AlwaysMix instead to use the proper weights.
|
||||
// AlwaysMix leads to the expected result = lower_layer + lerp(add1, add2, transition_weight).
|
||||
if (layerBlendMode == MixBlend.Add && mode == MixMode.MixNext) {
|
||||
mode = MixMode.AlwaysMix;
|
||||
layerMixModes[layer] = mode;
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
else {
|
||||
return layerBlendMode == MixBlend.Add ? MixMode.AlwaysMix : MixMode.MixNext;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void GetLayerBlendModes() {
|
||||
if (layerBlendModes.Length < animator.layerCount) {
|
||||
System.Array.Resize<MixBlend>(ref layerBlendModes, animator.layerCount);
|
||||
}
|
||||
for (int layer = 0, n = animator.layerCount; layer < n; ++layer) {
|
||||
var controller = animator.runtimeAnimatorController as UnityEditor.Animations.AnimatorController;
|
||||
if (controller != null) {
|
||||
layerBlendModes[layer] = MixBlend.First;
|
||||
if (layer > 0) {
|
||||
layerBlendModes[layer] = controller.layers[layer].blendingMode == UnityEditor.Animations.AnimatorLayerBlendingMode.Additive ?
|
||||
MixBlend.Add : MixBlend.Replace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void GetStateUpdatesFromAnimator (int layer) {
|
||||
|
||||
var layerInfos = layerClipInfos[layer];
|
||||
int clipInfoCount = animator.GetCurrentAnimatorClipInfoCount(layer);
|
||||
int nextClipInfoCount = animator.GetNextAnimatorClipInfoCount(layer);
|
||||
|
||||
var clipInfos = layerInfos.clipInfos;
|
||||
var nextClipInfos = layerInfos.nextClipInfos;
|
||||
var interruptingClipInfos = layerInfos.interruptingClipInfos;
|
||||
|
||||
layerInfos.isInterruptionActive = (clipInfoCount == 0 && clipInfos.Count != 0 &&
|
||||
nextClipInfoCount == 0 && nextClipInfos.Count != 0);
|
||||
|
||||
// Note: during interruption, GetCurrentAnimatorClipInfoCount and GetNextAnimatorClipInfoCount
|
||||
// are returning 0 in calls above. Therefore we keep previous clipInfos and nextClipInfos
|
||||
// until the interruption is over.
|
||||
if (layerInfos.isInterruptionActive) {
|
||||
|
||||
// Note: The last frame of a transition interruption
|
||||
// will have fullPathHash set to 0, therefore we have to use previous
|
||||
// frame's infos about interruption clips and correct some values
|
||||
// accordingly (normalizedTime and weight).
|
||||
var interruptingStateInfo = animator.GetNextAnimatorStateInfo(layer);
|
||||
layerInfos.isLastFrameOfInterruption = interruptingStateInfo.fullPathHash == 0;
|
||||
if (!layerInfos.isLastFrameOfInterruption) {
|
||||
animator.GetNextAnimatorClipInfo(layer, interruptingClipInfos);
|
||||
layerInfos.interruptingClipInfoCount = interruptingClipInfos.Count;
|
||||
float oldTime = layerInfos.interruptingStateInfo.normalizedTime;
|
||||
float newTime = interruptingStateInfo.normalizedTime;
|
||||
layerInfos.interruptingClipTimeAddition = newTime - oldTime;
|
||||
layerInfos.interruptingStateInfo = interruptingStateInfo;
|
||||
}
|
||||
} else {
|
||||
layerInfos.clipInfoCount = clipInfoCount;
|
||||
layerInfos.nextClipInfoCount = nextClipInfoCount;
|
||||
layerInfos.interruptingClipInfoCount = 0;
|
||||
layerInfos.isLastFrameOfInterruption = false;
|
||||
|
||||
if (clipInfos.Capacity < clipInfoCount) clipInfos.Capacity = clipInfoCount;
|
||||
if (nextClipInfos.Capacity < nextClipInfoCount) nextClipInfos.Capacity = nextClipInfoCount;
|
||||
|
||||
animator.GetCurrentAnimatorClipInfo(layer, clipInfos);
|
||||
animator.GetNextAnimatorClipInfo(layer, nextClipInfos);
|
||||
|
||||
layerInfos.stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
|
||||
layerInfos.nextStateInfo = animator.GetNextAnimatorStateInfo(layer);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAnimatorClipInfos (
|
||||
int layer,
|
||||
out bool isInterruptionActive,
|
||||
out int clipInfoCount,
|
||||
out int nextClipInfoCount,
|
||||
out int interruptingClipInfoCount,
|
||||
out IList<AnimatorClipInfo> clipInfo,
|
||||
out IList<AnimatorClipInfo> nextClipInfo,
|
||||
out IList<AnimatorClipInfo> interruptingClipInfo,
|
||||
out bool shallInterpolateWeightTo1) {
|
||||
|
||||
var layerInfos = layerClipInfos[layer];
|
||||
isInterruptionActive = layerInfos.isInterruptionActive;
|
||||
|
||||
clipInfoCount = layerInfos.clipInfoCount;
|
||||
nextClipInfoCount = layerInfos.nextClipInfoCount;
|
||||
interruptingClipInfoCount = layerInfos.interruptingClipInfoCount;
|
||||
|
||||
clipInfo = layerInfos.clipInfos;
|
||||
nextClipInfo = layerInfos.nextClipInfos;
|
||||
interruptingClipInfo = isInterruptionActive ? layerInfos.interruptingClipInfos : null;
|
||||
shallInterpolateWeightTo1 = layerInfos.isLastFrameOfInterruption;
|
||||
}
|
||||
|
||||
void GetAnimatorStateInfos (
|
||||
int layer,
|
||||
out bool isInterruptionActive,
|
||||
out AnimatorStateInfo stateInfo,
|
||||
out AnimatorStateInfo nextStateInfo,
|
||||
out AnimatorStateInfo interruptingStateInfo,
|
||||
out float interruptingClipTimeAddition) {
|
||||
|
||||
var layerInfos = layerClipInfos[layer];
|
||||
isInterruptionActive = layerInfos.isInterruptionActive;
|
||||
|
||||
stateInfo = layerInfos.stateInfo;
|
||||
nextStateInfo = layerInfos.nextStateInfo;
|
||||
interruptingStateInfo = layerInfos.interruptingStateInfo;
|
||||
interruptingClipTimeAddition = layerInfos.isLastFrameOfInterruption ? layerInfos.interruptingClipTimeAddition : 0;
|
||||
}
|
||||
|
||||
Spine.Animation GetAnimation (AnimationClip clip) {
|
||||
int clipNameHashCode;
|
||||
if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
|
||||
clipNameHashCode = clip.name.GetHashCode();
|
||||
clipNameHashCodeTable.Add(clip, clipNameHashCode);
|
||||
}
|
||||
Spine.Animation animation;
|
||||
animationTable.TryGetValue(clipNameHashCode, out animation);
|
||||
return animation;
|
||||
}
|
||||
|
||||
class AnimationClipEqualityComparer : IEqualityComparer<AnimationClip> {
|
||||
internal static readonly IEqualityComparer<AnimationClip> Instance = new AnimationClipEqualityComparer();
|
||||
public bool Equals (AnimationClip x, AnimationClip y) { return x.GetInstanceID() == y.GetInstanceID(); }
|
||||
public int GetHashCode (AnimationClip o) { return o.GetInstanceID(); }
|
||||
}
|
||||
|
||||
class IntEqualityComparer : IEqualityComparer<int> {
|
||||
internal static readonly IEqualityComparer<int> Instance = new IntEqualityComparer();
|
||||
public bool Equals (int x, int y) { return x == y; }
|
||||
public int GetHashCode(int o) { return o; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9db98c60740638449864eb028fbe7ad
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a361f5ac799a5149b340f9e20da27d1
|
||||
folderAsset: yes
|
||||
timeCreated: 1457405502
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,151 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
[RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderSeparator")]
|
||||
public class SkeletonPartsRenderer : MonoBehaviour {
|
||||
|
||||
#region Properties
|
||||
MeshGenerator meshGenerator;
|
||||
public MeshGenerator MeshGenerator {
|
||||
get {
|
||||
LazyIntialize();
|
||||
return meshGenerator;
|
||||
}
|
||||
}
|
||||
|
||||
MeshRenderer meshRenderer;
|
||||
public MeshRenderer MeshRenderer {
|
||||
get {
|
||||
LazyIntialize();
|
||||
return meshRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
MeshFilter meshFilter;
|
||||
public MeshFilter MeshFilter {
|
||||
get {
|
||||
LazyIntialize();
|
||||
return meshFilter;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Callback Delegates
|
||||
public delegate void SkeletonPartsRendererDelegate (SkeletonPartsRenderer skeletonPartsRenderer);
|
||||
|
||||
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
public event SkeletonPartsRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
#endregion
|
||||
|
||||
MeshRendererBuffers buffers;
|
||||
SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
|
||||
|
||||
|
||||
void LazyIntialize () {
|
||||
if (buffers == null) {
|
||||
buffers = new MeshRendererBuffers();
|
||||
buffers.Initialize();
|
||||
|
||||
if (meshGenerator != null) return;
|
||||
meshGenerator = new MeshGenerator();
|
||||
meshFilter = GetComponent<MeshFilter>();
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
currentInstructions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearMesh () {
|
||||
LazyIntialize();
|
||||
meshFilter.sharedMesh = null;
|
||||
}
|
||||
|
||||
public void RenderParts (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
|
||||
LazyIntialize();
|
||||
|
||||
// STEP 1: Create instruction
|
||||
var smartMesh = buffers.GetNextMesh();
|
||||
currentInstructions.SetWithSubset(instructions, startSubmesh, endSubmesh);
|
||||
bool updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, smartMesh.instructionUsed);
|
||||
|
||||
// STEP 2: Generate mesh buffers.
|
||||
var currentInstructionsSubmeshesItems = currentInstructions.submeshInstructions.Items;
|
||||
meshGenerator.Begin();
|
||||
if (currentInstructions.hasActiveClipping) {
|
||||
for (int i = 0; i < currentInstructions.submeshInstructions.Count; i++)
|
||||
meshGenerator.AddSubmesh(currentInstructionsSubmeshesItems[i], updateTriangles);
|
||||
} else {
|
||||
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
|
||||
}
|
||||
|
||||
buffers.UpdateSharedMaterials(currentInstructions.submeshInstructions);
|
||||
|
||||
// STEP 3: modify mesh.
|
||||
var mesh = smartMesh.mesh;
|
||||
|
||||
if (meshGenerator.VertexCount <= 0) { // Clear an empty mesh
|
||||
updateTriangles = false;
|
||||
mesh.Clear();
|
||||
} else {
|
||||
meshGenerator.FillVertexData(mesh);
|
||||
if (updateTriangles) {
|
||||
meshGenerator.FillTriangles(mesh);
|
||||
meshRenderer.sharedMaterials = buffers.GetUpdatedSharedMaterialsArray();
|
||||
} else if (buffers.MaterialsChangedInLastUpdate()) {
|
||||
meshRenderer.sharedMaterials = buffers.GetUpdatedSharedMaterialsArray();
|
||||
}
|
||||
meshGenerator.FillLateVertexData(mesh);
|
||||
}
|
||||
|
||||
meshFilter.sharedMesh = mesh;
|
||||
smartMesh.instructionUsed.Set(currentInstructions);
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public void SetPropertyBlock (MaterialPropertyBlock block) {
|
||||
LazyIntialize();
|
||||
meshRenderer.SetPropertyBlock(block);
|
||||
}
|
||||
|
||||
public static SkeletonPartsRenderer NewPartsRendererGameObject (Transform parent, string name, int sortingOrder = 0) {
|
||||
var go = new GameObject(name, typeof(MeshFilter), typeof(MeshRenderer));
|
||||
go.transform.SetParent(parent, false);
|
||||
var returnComponent = go.AddComponent<SkeletonPartsRenderer>();
|
||||
returnComponent.MeshRenderer.sortingOrder = sortingOrder;
|
||||
|
||||
return returnComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0b968d1e7333b499e347acb644f1c1
|
||||
timeCreated: 1458045480
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,269 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
#define SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderSeparator")]
|
||||
public class SkeletonRenderSeparator : MonoBehaviour {
|
||||
public const int DefaultSortingOrderIncrement = 5;
|
||||
|
||||
#region Inspector
|
||||
[SerializeField]
|
||||
protected SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonRenderer SkeletonRenderer {
|
||||
get { return skeletonRenderer; }
|
||||
set {
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
#endif
|
||||
|
||||
skeletonRenderer = value;
|
||||
if (value == null)
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
MeshRenderer mainMeshRenderer;
|
||||
public bool copyPropertyBlock = true;
|
||||
[Tooltip("Copies MeshRenderer flags into each parts renderer")]
|
||||
public bool copyMeshRendererFlags = true;
|
||||
public List<Spine.Unity.SkeletonPartsRenderer> partsRenderers = new List<SkeletonPartsRenderer>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void Reset () {
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Callback Delegates
|
||||
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
public event SkeletonRenderer.SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
#endregion
|
||||
|
||||
#region Runtime Instantiation
|
||||
/// <summary>Adds a SkeletonRenderSeparator and child SkeletonPartsRenderer GameObjects to a given SkeletonRenderer.</summary>
|
||||
/// <returns>The to skeleton renderer.</returns>
|
||||
/// <param name="skeletonRenderer">The target SkeletonRenderer or SkeletonAnimation.</param>
|
||||
/// <param name="sortingLayerID">Sorting layer to be used for the parts renderers.</param>
|
||||
/// <param name="extraPartsRenderers">Number of additional SkeletonPartsRenderers on top of the ones determined by counting the number of separator slots.</param>
|
||||
/// <param name="sortingOrderIncrement">The integer to increment the sorting order per SkeletonPartsRenderer to separate them.</param>
|
||||
/// <param name="baseSortingOrder">The sorting order value of the first SkeletonPartsRenderer.</param>
|
||||
/// <param name="addMinimumPartsRenderers">If set to <c>true</c>, a minimum number of SkeletonPartsRenderer GameObjects (determined by separatorSlots.Count + 1) will be added.</param>
|
||||
public static SkeletonRenderSeparator AddToSkeletonRenderer (SkeletonRenderer skeletonRenderer, int sortingLayerID = 0, int extraPartsRenderers = 0, int sortingOrderIncrement = DefaultSortingOrderIncrement, int baseSortingOrder = 0, bool addMinimumPartsRenderers = true) {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.Log("Tried to add SkeletonRenderSeparator to a null SkeletonRenderer reference.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var srs = skeletonRenderer.gameObject.AddComponent<SkeletonRenderSeparator>();
|
||||
srs.skeletonRenderer = skeletonRenderer;
|
||||
|
||||
skeletonRenderer.Initialize(false);
|
||||
int count = extraPartsRenderers;
|
||||
if (addMinimumPartsRenderers)
|
||||
count = extraPartsRenderers + skeletonRenderer.separatorSlots.Count + 1;
|
||||
|
||||
var skeletonRendererTransform = skeletonRenderer.transform;
|
||||
var componentRenderers = srs.partsRenderers;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
var spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRendererTransform, i.ToString());
|
||||
var mr = spr.MeshRenderer;
|
||||
mr.sortingLayerID = sortingLayerID;
|
||||
mr.sortingOrder = baseSortingOrder + (i * sortingOrderIncrement);
|
||||
componentRenderers.Add(spr);
|
||||
}
|
||||
|
||||
srs.OnEnable();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Make sure editor updates properly in edit mode.
|
||||
if (!Application.isPlaying) {
|
||||
skeletonRenderer.enabled = false;
|
||||
skeletonRenderer.enabled = true;
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
#endif
|
||||
|
||||
return srs;
|
||||
}
|
||||
|
||||
/// <summary>Add a child SkeletonPartsRenderer GameObject to this SkeletonRenderSeparator.</summary>
|
||||
public SkeletonPartsRenderer AddPartsRenderer (int sortingOrderIncrement = DefaultSortingOrderIncrement, string name = null) {
|
||||
int sortingLayerID = 0;
|
||||
int sortingOrder = 0;
|
||||
if (partsRenderers.Count > 0) {
|
||||
var previous = partsRenderers[partsRenderers.Count - 1];
|
||||
var previousMeshRenderer = previous.MeshRenderer;
|
||||
sortingLayerID = previousMeshRenderer.sortingLayerID;
|
||||
sortingOrder = previousMeshRenderer.sortingOrder + sortingOrderIncrement;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = partsRenderers.Count.ToString();
|
||||
|
||||
var spr = SkeletonPartsRenderer.NewPartsRendererGameObject(skeletonRenderer.transform, name);
|
||||
partsRenderers.Add(spr);
|
||||
|
||||
var mr = spr.MeshRenderer;
|
||||
mr.sortingLayerID = sortingLayerID;
|
||||
mr.sortingOrder = sortingOrder;
|
||||
|
||||
return spr;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void OnEnable () {
|
||||
if (skeletonRenderer == null) return;
|
||||
if (copiedBlock == null) copiedBlock = new MaterialPropertyBlock();
|
||||
mainMeshRenderer = skeletonRenderer.GetComponent<MeshRenderer>();
|
||||
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
skeletonRenderer.GenerateMeshOverride += HandleRender;
|
||||
#endif
|
||||
|
||||
if (copyMeshRendererFlags) {
|
||||
var lightProbeUsage = mainMeshRenderer.lightProbeUsage;
|
||||
bool receiveShadows = mainMeshRenderer.receiveShadows;
|
||||
var reflectionProbeUsage = mainMeshRenderer.reflectionProbeUsage;
|
||||
var shadowCastingMode = mainMeshRenderer.shadowCastingMode;
|
||||
var motionVectorGenerationMode = mainMeshRenderer.motionVectorGenerationMode;
|
||||
var probeAnchor = mainMeshRenderer.probeAnchor;
|
||||
|
||||
for (int i = 0; i < partsRenderers.Count; i++) {
|
||||
var currentRenderer = partsRenderers[i];
|
||||
if (currentRenderer == null) continue; // skip null items.
|
||||
|
||||
var mr = currentRenderer.MeshRenderer;
|
||||
mr.lightProbeUsage = lightProbeUsage;
|
||||
mr.receiveShadows = receiveShadows;
|
||||
mr.reflectionProbeUsage = reflectionProbeUsage;
|
||||
mr.shadowCastingMode = shadowCastingMode;
|
||||
mr.motionVectorGenerationMode = motionVectorGenerationMode;
|
||||
mr.probeAnchor = probeAnchor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDisable () {
|
||||
if (skeletonRenderer == null) return;
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
skeletonRenderer.GenerateMeshOverride -= HandleRender;
|
||||
#endif
|
||||
|
||||
skeletonRenderer.LateUpdate();
|
||||
|
||||
foreach (var partsRenderer in partsRenderers) {
|
||||
if (partsRenderer != null)
|
||||
partsRenderer.ClearMesh();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialPropertyBlock copiedBlock;
|
||||
|
||||
void HandleRender (SkeletonRendererInstruction instruction) {
|
||||
int rendererCount = partsRenderers.Count;
|
||||
if (rendererCount <= 0) return;
|
||||
|
||||
if (copyPropertyBlock)
|
||||
mainMeshRenderer.GetPropertyBlock(copiedBlock);
|
||||
|
||||
var settings = new MeshGenerator.Settings {
|
||||
addNormals = skeletonRenderer.addNormals,
|
||||
calculateTangents = skeletonRenderer.calculateTangents,
|
||||
immutableTriangles = false, // parts cannot do immutable triangles.
|
||||
pmaVertexColors = skeletonRenderer.pmaVertexColors,
|
||||
tintBlack = skeletonRenderer.tintBlack,
|
||||
useClipping = true,
|
||||
zSpacing = skeletonRenderer.zSpacing
|
||||
};
|
||||
|
||||
var submeshInstructions = instruction.submeshInstructions;
|
||||
var submeshInstructionsItems = submeshInstructions.Items;
|
||||
int lastSubmeshInstruction = submeshInstructions.Count - 1;
|
||||
|
||||
int rendererIndex = 0;
|
||||
var currentRenderer = partsRenderers[rendererIndex];
|
||||
for (int si = 0, start = 0; si <= lastSubmeshInstruction; si++) {
|
||||
if (currentRenderer == null)
|
||||
continue;
|
||||
if (submeshInstructionsItems[si].forceSeparate || si == lastSubmeshInstruction) {
|
||||
// Apply properties
|
||||
var meshGenerator = currentRenderer.MeshGenerator;
|
||||
meshGenerator.settings = settings;
|
||||
|
||||
if (copyPropertyBlock)
|
||||
currentRenderer.SetPropertyBlock(copiedBlock);
|
||||
|
||||
// Render
|
||||
currentRenderer.RenderParts(instruction.submeshInstructions, start, si + 1);
|
||||
|
||||
start = si + 1;
|
||||
rendererIndex++;
|
||||
if (rendererIndex < rendererCount) {
|
||||
currentRenderer = partsRenderers[rendererIndex];
|
||||
} else {
|
||||
// Not enough renderers. Skip the rest of the instructions.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this.skeletonRenderer);
|
||||
|
||||
// Clear extra renderers if they exist.
|
||||
for (; rendererIndex < rendererCount; rendererIndex++) {
|
||||
currentRenderer = partsRenderers[rendererIndex];
|
||||
if (currentRenderer != null)
|
||||
partsRenderers[rendererIndex].ClearMesh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c70a5b35f6ff2541aed8e8346b7e4d5
|
||||
timeCreated: 1457405791
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
711
Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs
Normal file
711
Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs
Normal file
@@ -0,0 +1,711 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#define PER_MATERIAL_PROPERTY_BLOCKS
|
||||
#endif
|
||||
|
||||
#if UNITY_2017_1_OR_NEWER
|
||||
#define BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endif
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
#define CONFIGURABLE_ENTER_PLAY_MODE
|
||||
#endif
|
||||
|
||||
#define SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
#define SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Base class of animated Spine skeleton components. This component manages and renders a skeleton.</summary>
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer)), DisallowMultipleComponent]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRenderer-Component")]
|
||||
public class SkeletonRenderer : MonoBehaviour, ISkeletonComponent, IHasSkeletonDataAsset {
|
||||
public SkeletonDataAsset skeletonDataAsset;
|
||||
|
||||
#region Initialization settings
|
||||
/// <summary>Skin name to use when the Skeleton is initialized.</summary>
|
||||
[SpineSkin(defaultAsEmptyString:true)] public string initialSkinName;
|
||||
|
||||
/// <summary>Enable this parameter when overwriting the Skeleton's skin from an editor script.
|
||||
/// Otherwise any changes will be overwritten by the next inspector update.</summary>
|
||||
#if UNITY_EDITOR
|
||||
public bool EditorSkipSkinSync {
|
||||
get { return editorSkipSkinSync; }
|
||||
set { editorSkipSkinSync = value; }
|
||||
}
|
||||
protected bool editorSkipSkinSync = false;
|
||||
#endif
|
||||
/// <summary>Flip X and Y to use when the Skeleton is initialized.</summary>
|
||||
public bool initialFlipX, initialFlipY;
|
||||
#endregion
|
||||
|
||||
#region Advanced Render Settings
|
||||
|
||||
/// <summary>Update mode to optionally limit updates to e.g. only apply animations but not update the mesh.</summary>
|
||||
public UpdateMode UpdateMode { get { return updateMode; } set { updateMode = value; } }
|
||||
protected UpdateMode updateMode = UpdateMode.FullUpdate;
|
||||
|
||||
/// <summary>Update mode used when the MeshRenderer becomes invisible
|
||||
/// (when <c>OnBecameInvisible()</c> is called). Update mode is automatically
|
||||
/// reset to <c>UpdateMode.FullUpdate</c> when the mesh becomes visible again.</summary>
|
||||
public UpdateMode updateWhenInvisible = UpdateMode.FullUpdate;
|
||||
|
||||
// Submesh Separation
|
||||
/// <summary>Slot names used to populate separatorSlots list when the Skeleton is initialized. Changing this after initialization does nothing.</summary>
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("submeshSeparators")][SerializeField][SpineSlot] protected string[] separatorSlotNames = new string[0];
|
||||
|
||||
/// <summary>Slots that determine where the render is split. This is used by components such as SkeletonRenderSeparator so that the skeleton can be rendered by two separate renderers on different GameObjects.</summary>
|
||||
[System.NonSerialized] public readonly List<Slot> separatorSlots = new List<Slot>();
|
||||
|
||||
// Render Settings
|
||||
[Range(-0.1f, 0f)] public float zSpacing;
|
||||
/// <summary>Use Spine's clipping feature. If false, ClippingAttachments will be ignored.</summary>
|
||||
public bool useClipping = true;
|
||||
|
||||
/// <summary>If true, triangles will not be updated. Enable this as an optimization if the skeleton does not make use of attachment swapping or hiding, or draw order keys. Otherwise, setting this to false may cause errors in rendering.</summary>
|
||||
public bool immutableTriangles = false;
|
||||
|
||||
/// <summary>Multiply vertex color RGB with vertex color alpha. Set this to true if the shader used for rendering is a premultiplied alpha shader. Setting this to false disables single-batch additive slots.</summary>
|
||||
public bool pmaVertexColors = true;
|
||||
|
||||
/// <summary>Clears the state of the render and skeleton when this component or its GameObject is disabled. This prevents previous state from being retained when it is enabled again. When pooling your skeleton, setting this to true can be helpful.</summary>
|
||||
public bool clearStateOnDisable = false;
|
||||
|
||||
/// <summary>If true, second colors on slots will be added to the output Mesh as UV2 and UV3. A special "tint black" shader that interprets UV2 and UV3 as black point colors is required to render this properly.</summary>
|
||||
public bool tintBlack = false;
|
||||
|
||||
/// <summary>If true, the renderer assumes the skeleton only requires one Material and one submesh to render. This allows the MeshGenerator to skip checking for changes in Materials. Enable this as an optimization if the skeleton only uses one Material.</summary>
|
||||
/// <remarks>This disables SkeletonRenderSeparator functionality.</remarks>
|
||||
public bool singleSubmesh = false;
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
/// <summary> Applies only when 3+ submeshes are used (2+ materials with alternating order, e.g. "A B A").
|
||||
/// If true, GPU instancing is disabled at all materials and MaterialPropertyBlocks are assigned at each
|
||||
/// material to prevent aggressive batching of submeshes by e.g. the LWRP renderer, leading to incorrect
|
||||
/// draw order (e.g. "A1 B A2" changed to "A1A2 B").
|
||||
/// You can disable this parameter when everything is drawn correctly to save the additional performance cost.
|
||||
/// </summary>
|
||||
public bool fixDrawOrder = false;
|
||||
#endif
|
||||
|
||||
/// <summary>If true, the mesh generator adds normals to the output mesh. For better performance and reduced memory requirements, use a shader that assumes the desired normal.</summary>
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("calculateNormals")] public bool addNormals = false;
|
||||
|
||||
/// <summary>If true, tangents are calculated every frame and added to the Mesh. Enable this when using a shader that uses lighting that requires tangents.</summary>
|
||||
public bool calculateTangents = false;
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
/// <summary>This enum controls the mode under which the sprite will interact with the masking system.</summary>
|
||||
/// <remarks>Interaction modes with <see cref="UnityEngine.SpriteMask"/> components are identical to Unity's <see cref="UnityEngine.SpriteRenderer"/>,
|
||||
/// see https://docs.unity3d.com/ScriptReference/SpriteMaskInteraction.html. </remarks>
|
||||
public SpriteMaskInteraction maskInteraction = SpriteMaskInteraction.None;
|
||||
|
||||
[System.Serializable]
|
||||
public class SpriteMaskInteractionMaterials {
|
||||
public bool AnyMaterialCreated {
|
||||
get {
|
||||
return materialsMaskDisabled.Length > 0 ||
|
||||
materialsInsideMask.Length > 0 ||
|
||||
materialsOutsideMask.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.None"/>.</summary>
|
||||
public Material[] materialsMaskDisabled = new Material[0];
|
||||
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
|
||||
public Material[] materialsInsideMask = new Material[0];
|
||||
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes to <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
|
||||
public Material[] materialsOutsideMask = new Material[0];
|
||||
}
|
||||
/// <summary>Material references for switching material sets at runtime when <see cref="SkeletonRenderer.maskInteraction"/> changes.</summary>
|
||||
public SpriteMaskInteractionMaterials maskMaterials = new SpriteMaskInteractionMaterials();
|
||||
|
||||
/// <summary>Shader property ID used for the Stencil comparison function.</summary>
|
||||
public static readonly int STENCIL_COMP_PARAM_ID = Shader.PropertyToID("_StencilComp");
|
||||
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.None"/>.</summary>
|
||||
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_NONE = UnityEngine.Rendering.CompareFunction.Always;
|
||||
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleInsideMask"/>.</summary>
|
||||
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE = UnityEngine.Rendering.CompareFunction.LessEqual;
|
||||
/// <summary>Shader property value used as Stencil comparison function for <see cref="SpriteMaskInteraction.VisibleOutsideMask"/>.</summary>
|
||||
public const UnityEngine.Rendering.CompareFunction STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE = UnityEngine.Rendering.CompareFunction.Greater;
|
||||
#if UNITY_EDITOR
|
||||
private static bool haveStencilParametersBeenFixed = false;
|
||||
#endif
|
||||
#endif // #if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
// These are API for anything that wants to take over rendering for a SkeletonRenderer.
|
||||
public bool disableRenderingOnOverride = true;
|
||||
public delegate void InstructionDelegate (SkeletonRendererInstruction instruction);
|
||||
event InstructionDelegate generateMeshOverride;
|
||||
|
||||
/// <summary>Allows separate code to take over rendering for this SkeletonRenderer component. The subscriber is passed a SkeletonRendererInstruction argument to determine how to render a skeleton.</summary>
|
||||
public event InstructionDelegate GenerateMeshOverride {
|
||||
add {
|
||||
generateMeshOverride += value;
|
||||
if (disableRenderingOnOverride && generateMeshOverride != null) {
|
||||
Initialize(false);
|
||||
if (meshRenderer)
|
||||
meshRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
remove {
|
||||
generateMeshOverride -= value;
|
||||
if (disableRenderingOnOverride && generateMeshOverride == null) {
|
||||
Initialize(false);
|
||||
if (meshRenderer)
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Occurs after the vertex data is populated every frame, before the vertices are pushed into the mesh.</summary>
|
||||
public event Spine.Unity.MeshGeneratorDelegate OnPostProcessVertices;
|
||||
#endif
|
||||
|
||||
#if SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
[System.NonSerialized] readonly Dictionary<Material, Material> customMaterialOverride = new Dictionary<Material, Material>();
|
||||
/// <summary>Use this Dictionary to override a Material with a different Material.</summary>
|
||||
public Dictionary<Material, Material> CustomMaterialOverride { get { return customMaterialOverride; } }
|
||||
#endif
|
||||
|
||||
[System.NonSerialized] readonly Dictionary<Slot, Material> customSlotMaterials = new Dictionary<Slot, Material>();
|
||||
/// <summary>Use this Dictionary to use a different Material to render specific Slots.</summary>
|
||||
public Dictionary<Slot, Material> CustomSlotMaterials { get { return customSlotMaterials; } }
|
||||
#endregion
|
||||
|
||||
#region Mesh Generator
|
||||
[System.NonSerialized] readonly SkeletonRendererInstruction currentInstructions = new SkeletonRendererInstruction();
|
||||
readonly MeshGenerator meshGenerator = new MeshGenerator();
|
||||
[System.NonSerialized] readonly MeshRendererBuffers rendererBuffers = new MeshRendererBuffers();
|
||||
#endregion
|
||||
|
||||
#region Cached component references
|
||||
MeshRenderer meshRenderer;
|
||||
MeshFilter meshFilter;
|
||||
#endregion
|
||||
|
||||
#region Skeleton
|
||||
[System.NonSerialized] public bool valid;
|
||||
[System.NonSerialized] public Skeleton skeleton;
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
Initialize(false);
|
||||
return skeleton;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public delegate void SkeletonRendererDelegate (SkeletonRenderer skeletonRenderer);
|
||||
|
||||
/// <summary>OnRebuild is raised after the Skeleton is successfully initialized.</summary>
|
||||
public event SkeletonRendererDelegate OnRebuild;
|
||||
|
||||
/// <summary>OnMeshAndMaterialsUpdated is called at the end of LateUpdate after the Mesh and
|
||||
/// all materials have been updated.</summary>
|
||||
public event SkeletonRendererDelegate OnMeshAndMaterialsUpdated;
|
||||
|
||||
public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
|
||||
|
||||
#region Runtime Instantiation
|
||||
public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
|
||||
return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset, quiet);
|
||||
}
|
||||
|
||||
/// <summary>Add and prepare a Spine component that derives from SkeletonRenderer to a GameObject at runtime.</summary>
|
||||
/// <typeparam name="T">T should be SkeletonRenderer or any of its derived classes.</typeparam>
|
||||
public static T AddSpineComponent<T> (GameObject gameObject, SkeletonDataAsset skeletonDataAsset, bool quiet = false) where T : SkeletonRenderer {
|
||||
var c = gameObject.AddComponent<T>();
|
||||
if (skeletonDataAsset != null) {
|
||||
c.skeletonDataAsset = skeletonDataAsset;
|
||||
c.Initialize(false, quiet);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>Applies MeshGenerator settings to the SkeletonRenderer and its internal MeshGenerator.</summary>
|
||||
public void SetMeshSettings (MeshGenerator.Settings settings) {
|
||||
this.calculateTangents = settings.calculateTangents;
|
||||
this.immutableTriangles = settings.immutableTriangles;
|
||||
this.pmaVertexColors = settings.pmaVertexColors;
|
||||
this.tintBlack = settings.tintBlack;
|
||||
this.useClipping = settings.useClipping;
|
||||
this.zSpacing = settings.zSpacing;
|
||||
|
||||
this.meshGenerator.settings = settings;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
public virtual void Awake () {
|
||||
Initialize(false);
|
||||
updateMode = updateWhenInvisible;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR && CONFIGURABLE_ENTER_PLAY_MODE
|
||||
public virtual void Start () {
|
||||
Initialize(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
void OnDisable () {
|
||||
if (clearStateOnDisable && valid)
|
||||
ClearState();
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
rendererBuffers.Dispose();
|
||||
valid = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the previously generated mesh and resets the skeleton's pose.</summary>
|
||||
public virtual void ClearState () {
|
||||
var meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter != null) meshFilter.sharedMesh = null;
|
||||
currentInstructions.Clear();
|
||||
if (skeleton != null) skeleton.SetToSetupPose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a minimum buffer size for the internal MeshGenerator to prevent excess allocations during animation.
|
||||
/// </summary>
|
||||
public void EnsureMeshGeneratorCapacity (int minimumVertexCount) {
|
||||
meshGenerator.EnsureVertexCapacity(minimumVertexCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this component. Attempts to load the SkeletonData and creates the internal Skeleton object and buffers.</summary>
|
||||
/// <param name="overwrite">If set to <c>true</c>, it will overwrite internal objects if they were already generated. Otherwise, the initialized component will ignore subsequent calls to initialize.</param>
|
||||
public virtual void Initialize (bool overwrite, bool quiet = false) {
|
||||
if (valid && !overwrite)
|
||||
return;
|
||||
|
||||
// Clear
|
||||
{
|
||||
// Note: do not reset meshFilter.sharedMesh or meshRenderer.sharedMaterial to null,
|
||||
// otherwise constant reloading will be triggered at prefabs.
|
||||
currentInstructions.Clear();
|
||||
rendererBuffers.Clear();
|
||||
meshGenerator.Begin();
|
||||
skeleton = null;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (skeletonDataAsset == null)
|
||||
return;
|
||||
|
||||
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
|
||||
if (skeletonData == null) return;
|
||||
valid = true;
|
||||
|
||||
meshFilter = GetComponent<MeshFilter>();
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
rendererBuffers.Initialize();
|
||||
|
||||
skeleton = new Skeleton(skeletonData) {
|
||||
ScaleX = initialFlipX ? -1 : 1,
|
||||
ScaleY = initialFlipY ? -1 : 1
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(initialSkinName) && !string.Equals(initialSkinName, "default", System.StringComparison.Ordinal))
|
||||
skeleton.SetSkin(initialSkinName);
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0; i < separatorSlotNames.Length; i++)
|
||||
separatorSlots.Add(skeleton.FindSlot(separatorSlotNames[i]));
|
||||
|
||||
LateUpdate(); // Generate mesh for the first frame it exists.
|
||||
|
||||
if (OnRebuild != null)
|
||||
OnRebuild(this);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
string errorMessage = null;
|
||||
if (!quiet && MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage))
|
||||
Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new UnityEngine.Mesh from the internal Skeleton.</summary>
|
||||
public virtual void LateUpdate () {
|
||||
if (!valid) return;
|
||||
|
||||
#if UNITY_EDITOR && NEW_PREFAB_SYSTEM
|
||||
// Don't store mesh or material at the prefab, otherwise it will permanently reload
|
||||
var prefabType = UnityEditor.PrefabUtility.GetPrefabAssetType(this);
|
||||
if (UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) &&
|
||||
(prefabType == UnityEditor.PrefabAssetType.Regular || prefabType == UnityEditor.PrefabAssetType.Variant)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (updateMode != UpdateMode.FullUpdate) return;
|
||||
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
bool doMeshOverride = generateMeshOverride != null;
|
||||
if ((!meshRenderer.enabled) && !doMeshOverride) return;
|
||||
#else
|
||||
const bool doMeshOverride = false;
|
||||
if (!meshRenderer.enabled) return;
|
||||
#endif
|
||||
var currentInstructions = this.currentInstructions;
|
||||
var workingSubmeshInstructions = currentInstructions.submeshInstructions;
|
||||
var currentSmartMesh = rendererBuffers.GetNextMesh(); // Double-buffer for performance.
|
||||
|
||||
bool updateTriangles;
|
||||
|
||||
if (this.singleSubmesh) {
|
||||
// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
|
||||
MeshGenerator.GenerateSingleSubmeshInstruction(currentInstructions, skeleton, skeletonDataAsset.atlasAssets[0].PrimaryMaterial);
|
||||
|
||||
// STEP 1.9. Post-process workingInstructions. ==================================================================================
|
||||
#if SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
|
||||
MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
|
||||
#endif
|
||||
|
||||
// STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
|
||||
meshGenerator.settings = new MeshGenerator.Settings {
|
||||
pmaVertexColors = this.pmaVertexColors,
|
||||
zSpacing = this.zSpacing,
|
||||
useClipping = this.useClipping,
|
||||
tintBlack = this.tintBlack,
|
||||
calculateTangents = this.calculateTangents,
|
||||
addNormals = this.addNormals
|
||||
};
|
||||
meshGenerator.Begin();
|
||||
updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
|
||||
if (currentInstructions.hasActiveClipping) {
|
||||
meshGenerator.AddSubmesh(workingSubmeshInstructions.Items[0], updateTriangles);
|
||||
} else {
|
||||
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
|
||||
}
|
||||
|
||||
} else {
|
||||
// STEP 1. Determine a SmartMesh.Instruction. Split up instructions into submeshes. =============================================
|
||||
MeshGenerator.GenerateSkeletonRendererInstruction(currentInstructions, skeleton, customSlotMaterials, separatorSlots, doMeshOverride, this.immutableTriangles);
|
||||
|
||||
// STEP 1.9. Post-process workingInstructions. ==================================================================================
|
||||
#if SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
if (customMaterialOverride.Count > 0) // isCustomMaterialOverridePopulated
|
||||
MeshGenerator.TryReplaceMaterials(workingSubmeshInstructions, customMaterialOverride);
|
||||
#endif
|
||||
|
||||
#if SPINE_OPTIONAL_RENDEROVERRIDE
|
||||
if (doMeshOverride) {
|
||||
this.generateMeshOverride(currentInstructions);
|
||||
if (disableRenderingOnOverride) return;
|
||||
}
|
||||
#endif
|
||||
|
||||
updateTriangles = SkeletonRendererInstruction.GeometryNotEqual(currentInstructions, currentSmartMesh.instructionUsed);
|
||||
|
||||
// STEP 2. Update vertex buffer based on verts from the attachments. ===========================================================
|
||||
meshGenerator.settings = new MeshGenerator.Settings {
|
||||
pmaVertexColors = this.pmaVertexColors,
|
||||
zSpacing = this.zSpacing,
|
||||
useClipping = this.useClipping,
|
||||
tintBlack = this.tintBlack,
|
||||
calculateTangents = this.calculateTangents,
|
||||
addNormals = this.addNormals
|
||||
};
|
||||
meshGenerator.Begin();
|
||||
if (currentInstructions.hasActiveClipping)
|
||||
meshGenerator.BuildMesh(currentInstructions, updateTriangles);
|
||||
else
|
||||
meshGenerator.BuildMeshWithArrays(currentInstructions, updateTriangles);
|
||||
}
|
||||
|
||||
if (OnPostProcessVertices != null) OnPostProcessVertices.Invoke(this.meshGenerator.Buffers);
|
||||
|
||||
// STEP 3. Move the mesh data into a UnityEngine.Mesh ===========================================================================
|
||||
var currentMesh = currentSmartMesh.mesh;
|
||||
meshGenerator.FillVertexData(currentMesh);
|
||||
|
||||
rendererBuffers.UpdateSharedMaterials(workingSubmeshInstructions);
|
||||
|
||||
bool materialsChanged = rendererBuffers.MaterialsChangedInLastUpdate();
|
||||
if (updateTriangles) { // Check if the triangles should also be updated.
|
||||
meshGenerator.FillTriangles(currentMesh);
|
||||
meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
|
||||
} else if (materialsChanged) {
|
||||
meshRenderer.sharedMaterials = rendererBuffers.GetUpdatedSharedMaterialsArray();
|
||||
}
|
||||
if (materialsChanged && (this.maskMaterials.AnyMaterialCreated)) {
|
||||
this.maskMaterials = new SpriteMaskInteractionMaterials();
|
||||
}
|
||||
|
||||
meshGenerator.FillLateVertexData(currentMesh);
|
||||
|
||||
// STEP 4. The UnityEngine.Mesh is ready. Set it as the MeshFilter's mesh. Store the instructions used for that mesh. ===========
|
||||
meshFilter.sharedMesh = currentMesh;
|
||||
currentSmartMesh.instructionUsed.Set(currentInstructions);
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
if (meshRenderer != null) {
|
||||
AssignSpriteMaskMaterials();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) {
|
||||
SetMaterialSettingsToFixDrawOrder();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (OnMeshAndMaterialsUpdated != null)
|
||||
OnMeshAndMaterialsUpdated(this);
|
||||
}
|
||||
|
||||
public void OnBecameVisible () {
|
||||
UpdateMode previousUpdateMode = updateMode;
|
||||
updateMode = UpdateMode.FullUpdate;
|
||||
if (previousUpdateMode != UpdateMode.FullUpdate)
|
||||
LateUpdate(); // OnBecameVisible is called after LateUpdate()
|
||||
}
|
||||
|
||||
public void OnBecameInvisible () {
|
||||
updateMode = updateWhenInvisible;
|
||||
}
|
||||
|
||||
public void FindAndApplySeparatorSlots (string startsWith, bool clearExistingSeparators = true, bool updateStringArray = false) {
|
||||
if (string.IsNullOrEmpty(startsWith)) return;
|
||||
|
||||
FindAndApplySeparatorSlots(
|
||||
(slotName) => slotName.StartsWith(startsWith),
|
||||
clearExistingSeparators,
|
||||
updateStringArray
|
||||
);
|
||||
}
|
||||
|
||||
public void FindAndApplySeparatorSlots (System.Func<string, bool> slotNamePredicate, bool clearExistingSeparators = true, bool updateStringArray = false) {
|
||||
if (slotNamePredicate == null) return;
|
||||
if (!valid) return;
|
||||
|
||||
if (clearExistingSeparators)
|
||||
separatorSlots.Clear();
|
||||
|
||||
var slots = skeleton.slots;
|
||||
foreach (var slot in slots) {
|
||||
if (slotNamePredicate.Invoke(slot.data.name))
|
||||
separatorSlots.Add(slot);
|
||||
}
|
||||
|
||||
if (updateStringArray) {
|
||||
var detectedSeparatorNames = new List<string>();
|
||||
foreach (var slot in skeleton.slots) {
|
||||
string slotName = slot.data.name;
|
||||
if (slotNamePredicate.Invoke(slotName))
|
||||
detectedSeparatorNames.Add(slotName);
|
||||
}
|
||||
if (!clearExistingSeparators) {
|
||||
string[] originalNames = this.separatorSlotNames;
|
||||
foreach (string originalName in originalNames)
|
||||
detectedSeparatorNames.Add(originalName);
|
||||
}
|
||||
|
||||
this.separatorSlotNames = detectedSeparatorNames.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void ReapplySeparatorSlotNames () {
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
separatorSlots.Clear();
|
||||
for (int i = 0, n = separatorSlotNames.Length; i < n; i++) {
|
||||
var slot = skeleton.FindSlot(separatorSlotNames[i]);
|
||||
if (slot != null) {
|
||||
separatorSlots.Add(slot);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
else if (!string.IsNullOrEmpty(separatorSlotNames[i]))
|
||||
{
|
||||
Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonDataAsset.skeletonJSON.name);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
private void AssignSpriteMaskMaterials()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying && !UnityEditor.EditorApplication.isUpdating) {
|
||||
EditorFixStencilCompParameters();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Application.isPlaying) {
|
||||
if (maskInteraction != SpriteMaskInteraction.None && maskMaterials.materialsMaskDisabled.Length == 0)
|
||||
maskMaterials.materialsMaskDisabled = meshRenderer.sharedMaterials;
|
||||
}
|
||||
|
||||
if (maskMaterials.materialsMaskDisabled.Length > 0 && maskMaterials.materialsMaskDisabled[0] != null &&
|
||||
maskInteraction == SpriteMaskInteraction.None) {
|
||||
this.meshRenderer.materials = maskMaterials.materialsMaskDisabled;
|
||||
}
|
||||
else if (maskInteraction == SpriteMaskInteraction.VisibleInsideMask) {
|
||||
if (maskMaterials.materialsInsideMask.Length == 0 || maskMaterials.materialsInsideMask[0] == null) {
|
||||
if (!InitSpriteMaskMaterialsInsideMask())
|
||||
return;
|
||||
}
|
||||
this.meshRenderer.materials = maskMaterials.materialsInsideMask;
|
||||
}
|
||||
else if (maskInteraction == SpriteMaskInteraction.VisibleOutsideMask) {
|
||||
if (maskMaterials.materialsOutsideMask.Length == 0 || maskMaterials.materialsOutsideMask[0] == null) {
|
||||
if (!InitSpriteMaskMaterialsOutsideMask())
|
||||
return;
|
||||
}
|
||||
this.meshRenderer.materials = maskMaterials.materialsOutsideMask;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InitSpriteMaskMaterialsInsideMask()
|
||||
{
|
||||
return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_INSIDE, ref maskMaterials.materialsInsideMask);
|
||||
}
|
||||
|
||||
private bool InitSpriteMaskMaterialsOutsideMask()
|
||||
{
|
||||
return InitSpriteMaskMaterialsForMaskType(STENCIL_COMP_MASKINTERACTION_VISIBLE_OUTSIDE, ref maskMaterials.materialsOutsideMask);
|
||||
}
|
||||
|
||||
private bool InitSpriteMaskMaterialsForMaskType(UnityEngine.Rendering.CompareFunction maskFunction, ref Material[] materialsToFill)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
var originalMaterials = maskMaterials.materialsMaskDisabled;
|
||||
materialsToFill = new Material[originalMaterials.Length];
|
||||
for (int i = 0; i < originalMaterials.Length; i++) {
|
||||
Material newMaterial = new Material(originalMaterials[i]);
|
||||
newMaterial.SetFloat(STENCIL_COMP_PARAM_ID, (int)maskFunction);
|
||||
materialsToFill[i] = newMaterial;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void EditorFixStencilCompParameters() {
|
||||
if (!haveStencilParametersBeenFixed && HasAnyStencilComp0Material()) {
|
||||
haveStencilParametersBeenFixed = true;
|
||||
FixAllProjectMaterialsStencilCompParameters();
|
||||
}
|
||||
}
|
||||
|
||||
private void FixAllProjectMaterialsStencilCompParameters() {
|
||||
string[] materialGUIDS = UnityEditor.AssetDatabase.FindAssets("t:material");
|
||||
foreach (var guid in materialGUIDS) {
|
||||
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path)) {
|
||||
var mat = UnityEditor.AssetDatabase.LoadAssetAtPath<Material>(path);
|
||||
if (mat.HasProperty(STENCIL_COMP_PARAM_ID) && mat.GetFloat(STENCIL_COMP_PARAM_ID) == 0) {
|
||||
mat.SetFloat(STENCIL_COMP_PARAM_ID, (int)STENCIL_COMP_MASKINTERACTION_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
UnityEditor.AssetDatabase.Refresh();
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
private bool HasAnyStencilComp0Material() {
|
||||
if (meshRenderer == null)
|
||||
return false;
|
||||
|
||||
foreach (var mat in meshRenderer.sharedMaterials) {
|
||||
if (mat != null && mat.HasProperty(STENCIL_COMP_PARAM_ID)) {
|
||||
float currentCompValue = mat.GetFloat(STENCIL_COMP_PARAM_ID);
|
||||
if (currentCompValue == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // UNITY_EDITOR
|
||||
|
||||
#endif //#if BUILT_IN_SPRITE_MASK_COMPONENT
|
||||
|
||||
#if PER_MATERIAL_PROPERTY_BLOCKS
|
||||
private MaterialPropertyBlock reusedPropertyBlock;
|
||||
public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh");
|
||||
|
||||
/// <summary>
|
||||
/// This method was introduced as a workaround for too aggressive submesh draw call batching,
|
||||
/// leading to incorrect draw order when 3+ materials are used at submeshes in alternating order.
|
||||
/// Otherwise, e.g. when using Lightweight Render Pipeline, deliberately separated draw calls
|
||||
/// "A1 B A2" are reordered to "A1A2 B", regardless of batching-related project settings.
|
||||
/// </summary>
|
||||
private void SetMaterialSettingsToFixDrawOrder() {
|
||||
if (reusedPropertyBlock == null) reusedPropertyBlock = new MaterialPropertyBlock();
|
||||
|
||||
bool hasPerRendererBlock = meshRenderer.HasPropertyBlock();
|
||||
if (hasPerRendererBlock) {
|
||||
meshRenderer.GetPropertyBlock(reusedPropertyBlock);
|
||||
}
|
||||
|
||||
for (int i = 0; i < meshRenderer.sharedMaterials.Length; ++i) {
|
||||
if (!meshRenderer.sharedMaterials[i])
|
||||
continue;
|
||||
|
||||
if (!hasPerRendererBlock) meshRenderer.GetPropertyBlock(reusedPropertyBlock, i);
|
||||
// Note: this parameter shall not exist at any shader, then Unity will create separate
|
||||
// material instances (not in terms of memory cost or leakage).
|
||||
reusedPropertyBlock.SetFloat(SUBMESH_DUMMY_PARAM_ID, i);
|
||||
meshRenderer.SetPropertyBlock(reusedPropertyBlock, i);
|
||||
|
||||
meshRenderer.sharedMaterials[i].enableInstancing = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e075b9a3e08e2f74fbd651c858ab16ed
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7236dbdc6a4e5a4989483dac97aee0b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,211 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonGraphicCustomMaterials")]
|
||||
public class SkeletonGraphicCustomMaterials : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
[SerializeField] protected List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
[SerializeField] protected List<AtlasTextureOverride> customTextureOverrides = new List<AtlasTextureOverride>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void Reset () {
|
||||
skeletonGraphic = GetComponent<SkeletonGraphic>();
|
||||
|
||||
// Populate material list
|
||||
if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
|
||||
var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
|
||||
|
||||
var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
var atlasMaterialOverride = new AtlasMaterialOverride {
|
||||
overrideEnabled = false,
|
||||
originalTexture = atlasMaterial.mainTexture
|
||||
};
|
||||
|
||||
initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
|
||||
}
|
||||
}
|
||||
customMaterialOverrides = initialAtlasMaterialOverrides;
|
||||
}
|
||||
|
||||
// Populate texture list
|
||||
if (skeletonGraphic != null && skeletonGraphic.skeletonDataAsset != null) {
|
||||
var atlasAssets = skeletonGraphic.skeletonDataAsset.atlasAssets;
|
||||
|
||||
var initialAtlasTextureOverrides = new List<AtlasTextureOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
var atlasTextureOverride = new AtlasTextureOverride {
|
||||
overrideEnabled = false,
|
||||
originalTexture = atlasMaterial.mainTexture
|
||||
};
|
||||
|
||||
initialAtlasTextureOverrides.Add(atlasTextureOverride);
|
||||
}
|
||||
}
|
||||
customTextureOverrides = initialAtlasTextureOverrides;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
void SetCustomMaterialOverrides () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customMaterialOverrides.Count; i++) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
|
||||
if (atlasMaterialOverride.overrideEnabled)
|
||||
skeletonGraphic.CustomMaterialOverride[atlasMaterialOverride.originalTexture] = atlasMaterialOverride.replacementMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomMaterialOverrides () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customMaterialOverrides.Count; i++) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
|
||||
Material currentMaterial;
|
||||
|
||||
if (!skeletonGraphic.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalTexture, out currentMaterial))
|
||||
continue;
|
||||
|
||||
// Do not revert the material if it was changed by something else
|
||||
if (currentMaterial != atlasMaterialOverride.replacementMaterial)
|
||||
continue;
|
||||
|
||||
skeletonGraphic.CustomMaterialOverride.Remove(atlasMaterialOverride.originalTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCustomTextureOverrides () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customTextureOverrides.Count; i++) {
|
||||
AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i];
|
||||
if (atlasTextureOverride.overrideEnabled)
|
||||
skeletonGraphic.CustomTextureOverride[atlasTextureOverride.originalTexture] = atlasTextureOverride.replacementTexture;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomTextureOverrides () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customTextureOverrides.Count; i++) {
|
||||
AtlasTextureOverride atlasTextureOverride = customTextureOverrides[i];
|
||||
Texture currentTexture;
|
||||
|
||||
if (!skeletonGraphic.CustomTextureOverride.TryGetValue(atlasTextureOverride.originalTexture, out currentTexture))
|
||||
continue;
|
||||
|
||||
// Do not revert the material if it was changed by something else
|
||||
if (currentTexture != atlasTextureOverride.replacementTexture)
|
||||
continue;
|
||||
|
||||
skeletonGraphic.CustomTextureOverride.Remove(atlasTextureOverride.originalTexture);
|
||||
}
|
||||
}
|
||||
|
||||
// OnEnable applies the overrides at runtime, and when the editor loads.
|
||||
void OnEnable () {
|
||||
if (skeletonGraphic == null)
|
||||
skeletonGraphic = GetComponent<SkeletonGraphic>();
|
||||
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
skeletonGraphic.Initialize(false);
|
||||
SetCustomMaterialOverrides();
|
||||
SetCustomTextureOverrides();
|
||||
}
|
||||
|
||||
// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
|
||||
void OnDisable () {
|
||||
if (skeletonGraphic == null) {
|
||||
Debug.LogError("skeletonGraphic == null");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveCustomMaterialOverrides();
|
||||
RemoveCustomTextureOverrides();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
|
||||
public bool overrideEnabled;
|
||||
public Texture originalTexture;
|
||||
public Material replacementMaterial;
|
||||
|
||||
public bool Equals (AtlasMaterialOverride other) {
|
||||
return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementMaterial == other.replacementMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AtlasTextureOverride : IEquatable<AtlasTextureOverride> {
|
||||
public bool overrideEnabled;
|
||||
public Texture originalTexture;
|
||||
public Texture replacementTexture;
|
||||
|
||||
public bool Equals (AtlasTextureOverride other) {
|
||||
return overrideEnabled == other.overrideEnabled && originalTexture == other.originalTexture && replacementTexture == other.replacementTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c8717e10b272bf42b05d363ac2679a6
|
||||
timeCreated: 1588789074
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,212 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
#define SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
|
||||
// Contributed by: Lost Polygon
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonRendererCustomMaterials")]
|
||||
public class SkeletonRendererCustomMaterials : MonoBehaviour {
|
||||
|
||||
#region Inspector
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
[SerializeField] protected List<SlotMaterialOverride> customSlotMaterials = new List<SlotMaterialOverride>();
|
||||
[SerializeField] protected List<AtlasMaterialOverride> customMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void Reset () {
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
|
||||
// Populate atlas list
|
||||
if (skeletonRenderer != null && skeletonRenderer.skeletonDataAsset != null) {
|
||||
var atlasAssets = skeletonRenderer.skeletonDataAsset.atlasAssets;
|
||||
|
||||
var initialAtlasMaterialOverrides = new List<AtlasMaterialOverride>();
|
||||
foreach (AtlasAssetBase atlasAsset in atlasAssets) {
|
||||
foreach (Material atlasMaterial in atlasAsset.Materials) {
|
||||
var atlasMaterialOverride = new AtlasMaterialOverride {
|
||||
overrideDisabled = true,
|
||||
originalMaterial = atlasMaterial
|
||||
};
|
||||
|
||||
initialAtlasMaterialOverrides.Add(atlasMaterialOverride);
|
||||
}
|
||||
}
|
||||
|
||||
customMaterialOverrides = initialAtlasMaterialOverrides;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
void SetCustomSlotMaterials () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customSlotMaterials.Count; i++) {
|
||||
SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
|
||||
if (slotMaterialOverride.overrideDisabled || string.IsNullOrEmpty(slotMaterialOverride.slotName))
|
||||
continue;
|
||||
|
||||
Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
|
||||
skeletonRenderer.CustomSlotMaterials[slotObject] = slotMaterialOverride.material;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveCustomSlotMaterials () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < customSlotMaterials.Count; i++) {
|
||||
SlotMaterialOverride slotMaterialOverride = customSlotMaterials[i];
|
||||
if (string.IsNullOrEmpty(slotMaterialOverride.slotName))
|
||||
continue;
|
||||
|
||||
Slot slotObject = skeletonRenderer.skeleton.FindSlot(slotMaterialOverride.slotName);
|
||||
|
||||
Material currentMaterial;
|
||||
if (!skeletonRenderer.CustomSlotMaterials.TryGetValue(slotObject, out currentMaterial))
|
||||
continue;
|
||||
|
||||
// Do not revert the material if it was changed by something else
|
||||
if (currentMaterial != slotMaterialOverride.material)
|
||||
continue;
|
||||
|
||||
skeletonRenderer.CustomSlotMaterials.Remove(slotObject);
|
||||
}
|
||||
}
|
||||
|
||||
void SetCustomMaterialOverrides () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
#if SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
for (int i = 0; i < customMaterialOverrides.Count; i++) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
|
||||
if (atlasMaterialOverride.overrideDisabled)
|
||||
continue;
|
||||
|
||||
skeletonRenderer.CustomMaterialOverride[atlasMaterialOverride.originalMaterial] = atlasMaterialOverride.replacementMaterial;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void RemoveCustomMaterialOverrides () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
#if SPINE_OPTIONAL_MATERIALOVERRIDE
|
||||
for (int i = 0; i < customMaterialOverrides.Count; i++) {
|
||||
AtlasMaterialOverride atlasMaterialOverride = customMaterialOverrides[i];
|
||||
Material currentMaterial;
|
||||
|
||||
if (!skeletonRenderer.CustomMaterialOverride.TryGetValue(atlasMaterialOverride.originalMaterial, out currentMaterial))
|
||||
continue;
|
||||
|
||||
// Do not revert the material if it was changed by something else
|
||||
if (currentMaterial != atlasMaterialOverride.replacementMaterial)
|
||||
continue;
|
||||
|
||||
skeletonRenderer.CustomMaterialOverride.Remove(atlasMaterialOverride.originalMaterial);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// OnEnable applies the overrides at runtime, and when the editor loads.
|
||||
void OnEnable () {
|
||||
if (skeletonRenderer == null)
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
skeletonRenderer.Initialize(false);
|
||||
SetCustomMaterialOverrides();
|
||||
SetCustomSlotMaterials();
|
||||
}
|
||||
|
||||
// OnDisable removes the overrides at runtime, and in the editor when the component is disabled or destroyed.
|
||||
void OnDisable () {
|
||||
if (skeletonRenderer == null) {
|
||||
Debug.LogError("skeletonRenderer == null");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveCustomMaterialOverrides();
|
||||
RemoveCustomSlotMaterials();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct SlotMaterialOverride : IEquatable<SlotMaterialOverride> {
|
||||
public bool overrideDisabled;
|
||||
|
||||
[SpineSlot]
|
||||
public string slotName;
|
||||
public Material material;
|
||||
|
||||
public bool Equals (SlotMaterialOverride other) {
|
||||
return overrideDisabled == other.overrideDisabled && slotName == other.slotName && material == other.material;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct AtlasMaterialOverride : IEquatable<AtlasMaterialOverride> {
|
||||
public bool overrideDisabled;
|
||||
public Material originalMaterial;
|
||||
public Material replacementMaterial;
|
||||
|
||||
public bool Equals (AtlasMaterialOverride other) {
|
||||
return overrideDisabled == other.overrideDisabled && originalMaterial == other.originalMaterial && replacementMaterial == other.replacementMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26947ae098a8447408d80c0c86e35b48
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6e0caaafe294de48af468a6a9321473
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,92 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Utility component to support flipping of 2D hinge chains (chains of HingeJoint2D objects) along
|
||||
/// with the parent skeleton by activating the respective mirrored versions of the hinge chain.
|
||||
/// Note: This component is automatically attached when calling "Create Hinge Chain 2D" at <see cref="SkeletonUtilityBone"/>,
|
||||
/// do not attempt to use this component for other purposes.
|
||||
/// </summary>
|
||||
public class ActivateBasedOnFlipDirection : MonoBehaviour {
|
||||
|
||||
public SkeletonRenderer skeletonRenderer;
|
||||
public SkeletonGraphic skeletonGraphic;
|
||||
public GameObject activeOnNormalX;
|
||||
public GameObject activeOnFlippedX;
|
||||
HingeJoint2D[] jointsNormalX;
|
||||
HingeJoint2D[] jointsFlippedX;
|
||||
ISkeletonComponent skeletonComponent;
|
||||
|
||||
bool wasFlippedXBefore = false;
|
||||
|
||||
private void Start () {
|
||||
jointsNormalX = activeOnNormalX.GetComponentsInChildren<HingeJoint2D>();
|
||||
jointsFlippedX = activeOnFlippedX.GetComponentsInChildren<HingeJoint2D>();
|
||||
skeletonComponent = skeletonRenderer != null ? (ISkeletonComponent)skeletonRenderer : (ISkeletonComponent)skeletonGraphic;
|
||||
}
|
||||
|
||||
private void FixedUpdate () {
|
||||
bool isFlippedX = (skeletonComponent.Skeleton.ScaleX < 0);
|
||||
if (isFlippedX != wasFlippedXBefore) {
|
||||
HandleFlip(isFlippedX);
|
||||
}
|
||||
wasFlippedXBefore = isFlippedX;
|
||||
}
|
||||
|
||||
void HandleFlip (bool isFlippedX) {
|
||||
GameObject gameObjectToActivate = isFlippedX ? activeOnFlippedX : activeOnNormalX;
|
||||
GameObject gameObjectToDeactivate = isFlippedX ? activeOnNormalX : activeOnFlippedX;
|
||||
|
||||
gameObjectToActivate.SetActive(true);
|
||||
gameObjectToDeactivate.SetActive(false);
|
||||
|
||||
ResetJointPositions(isFlippedX ? jointsFlippedX : jointsNormalX);
|
||||
ResetJointPositions(isFlippedX ? jointsNormalX : jointsFlippedX);
|
||||
CompensateMovementAfterFlipX(gameObjectToActivate.transform, gameObjectToDeactivate.transform);
|
||||
}
|
||||
|
||||
void ResetJointPositions (HingeJoint2D[] joints) {
|
||||
for (int i = 0; i < joints.Length; ++i) {
|
||||
var joint = joints[i];
|
||||
var parent = joint.connectedBody.transform;
|
||||
joint.transform.position = parent.TransformPoint(joint.connectedAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
void CompensateMovementAfterFlipX (Transform toActivate, Transform toDeactivate) {
|
||||
Transform targetLocation = toDeactivate.GetChild(0);
|
||||
Transform currentLocation = toActivate.GetChild(0);
|
||||
toActivate.position += targetLocation.position - currentLocation.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70ae96e4f2feb654681a2f16e4effeec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
|
||||
///
|
||||
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class FollowLocationRigidbody : MonoBehaviour {
|
||||
|
||||
public Transform reference;
|
||||
Rigidbody ownRigidbody;
|
||||
|
||||
private void Awake () {
|
||||
ownRigidbody = this.GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
void FixedUpdate () {
|
||||
ownRigidbody.rotation = reference.rotation;
|
||||
ownRigidbody.position = reference.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc20d5e917562341a5007777a9d0db2
|
||||
timeCreated: 1571763023
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
|
||||
///
|
||||
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Rigidbody2D))]
|
||||
public class FollowLocationRigidbody2D : MonoBehaviour {
|
||||
|
||||
public Transform reference;
|
||||
public bool followFlippedX;
|
||||
Rigidbody2D ownRigidbody;
|
||||
|
||||
private void Awake () {
|
||||
ownRigidbody = this.GetComponent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
void FixedUpdate () {
|
||||
if (followFlippedX) {
|
||||
ownRigidbody.rotation = ((-reference.rotation.eulerAngles.z + 270f) % 360f) - 90f;
|
||||
}
|
||||
else
|
||||
ownRigidbody.rotation = reference.rotation.eulerAngles.z;
|
||||
ownRigidbody.position = reference.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02aae87c39b869548a9051fbdb1975e6
|
||||
timeCreated: 1572012493
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,86 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
/// <summary>
|
||||
/// Utility component to support flipping of hinge chains (chains of HingeJoint objects) along with the parent skeleton.
|
||||
/// Note that flipping needs to be performed by 180 degree rotation at <see cref="SkeletonUtility"/>,
|
||||
/// by setting <see cref="SkeletonUtility.flipBy180DegreeRotation"/> to true, not via negative scale.
|
||||
///
|
||||
/// Note: This component is automatically attached when calling "Create Hinge Chain" at <see cref="SkeletonUtilityBone"/>,
|
||||
/// do not attempt to use this component for other purposes.
|
||||
/// </summary>
|
||||
public class FollowSkeletonUtilityRootRotation : MonoBehaviour {
|
||||
|
||||
const float FLIP_ANGLE_THRESHOLD = 100.0f;
|
||||
|
||||
public Transform reference;
|
||||
Vector3 prevLocalEulerAngles;
|
||||
|
||||
private void Start () {
|
||||
prevLocalEulerAngles = this.transform.localEulerAngles;
|
||||
}
|
||||
|
||||
void FixedUpdate () {
|
||||
this.transform.rotation = reference.rotation;
|
||||
|
||||
bool wasFlippedAroundY = Mathf.Abs(this.transform.localEulerAngles.y - prevLocalEulerAngles.y) > FLIP_ANGLE_THRESHOLD;
|
||||
bool wasFlippedAroundX = Mathf.Abs(this.transform.localEulerAngles.x - prevLocalEulerAngles.x) > FLIP_ANGLE_THRESHOLD;
|
||||
if (wasFlippedAroundY)
|
||||
CompensatePositionToYRotation();
|
||||
if (wasFlippedAroundX)
|
||||
CompensatePositionToXRotation();
|
||||
|
||||
prevLocalEulerAngles = this.transform.localEulerAngles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the position so that a child at the reference position remains in the same place,
|
||||
/// to counter any movement that occurred by rotation.
|
||||
/// </summary>
|
||||
void CompensatePositionToYRotation () {
|
||||
Vector3 newPosition = reference.position + (reference.position - this.transform.position);
|
||||
newPosition.y = this.transform.position.y;
|
||||
this.transform.position = newPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compensates the position so that a child at the reference position remains in the same place,
|
||||
/// to counter any movement that occurred by rotation.
|
||||
/// </summary>
|
||||
void CompensatePositionToXRotation () {
|
||||
Vector3 newPosition = reference.position + (reference.position - this.transform.position);
|
||||
newPosition.x = this.transform.position.x;
|
||||
this.transform.position = newPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 456a736ebb92ebf4b959fa9c4b704427
|
||||
timeCreated: 1571763206
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,469 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(ISkeletonAnimation))]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtility")]
|
||||
public sealed class SkeletonUtility : MonoBehaviour {
|
||||
|
||||
#region BoundingBoxAttachment
|
||||
public static PolygonCollider2D AddBoundingBoxGameObject (Skeleton skeleton, string skinName, string slotName, string attachmentName, Transform parent, bool isTrigger = true) {
|
||||
Skin skin = string.IsNullOrEmpty(skinName) ? skeleton.data.defaultSkin : skeleton.data.FindSkin(skinName);
|
||||
if (skin == null) {
|
||||
Debug.LogError("Skin " + skinName + " not found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
var attachment = skin.GetAttachment(skeleton.FindSlotIndex(slotName), attachmentName);
|
||||
if (attachment == null) {
|
||||
Debug.LogFormat("Attachment in slot '{0}' named '{1}' not found in skin '{2}'.", slotName, attachmentName, skin.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
var box = attachment as BoundingBoxAttachment;
|
||||
if (box != null) {
|
||||
var slot = skeleton.FindSlot(slotName);
|
||||
return AddBoundingBoxGameObject(box.Name, box, slot, parent, isTrigger);
|
||||
} else {
|
||||
Debug.LogFormat("Attachment '{0}' was not a Bounding Box.", attachmentName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static PolygonCollider2D AddBoundingBoxGameObject (string name, BoundingBoxAttachment box, Slot slot, Transform parent, bool isTrigger = true) {
|
||||
var go = new GameObject("[BoundingBox]" + (string.IsNullOrEmpty(name) ? box.Name : name));
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Spawn BoundingBox");
|
||||
# endif
|
||||
var got = go.transform;
|
||||
got.parent = parent;
|
||||
got.localPosition = Vector3.zero;
|
||||
got.localRotation = Quaternion.identity;
|
||||
got.localScale = Vector3.one;
|
||||
return AddBoundingBoxAsComponent(box, slot, go, isTrigger);
|
||||
}
|
||||
|
||||
public static PolygonCollider2D AddBoundingBoxAsComponent (BoundingBoxAttachment box, Slot slot, GameObject gameObject, bool isTrigger = true) {
|
||||
if (box == null) return null;
|
||||
var collider = gameObject.AddComponent<PolygonCollider2D>();
|
||||
collider.isTrigger = isTrigger;
|
||||
SetColliderPointsLocal(collider, slot, box);
|
||||
return collider;
|
||||
}
|
||||
|
||||
public static void SetColliderPointsLocal (PolygonCollider2D collider, Slot slot, BoundingBoxAttachment box, float scale = 1.0f) {
|
||||
if (box == null) return;
|
||||
if (box.IsWeighted()) Debug.LogWarning("UnityEngine.PolygonCollider2D does not support weighted or animated points. Collider points will not be animated and may have incorrect orientation. If you want to use it as a collider, please remove weights and animations from the bounding box in Spine editor.");
|
||||
var verts = box.GetLocalVertices(slot, null);
|
||||
if (scale != 1.0f) {
|
||||
for (int i = 0, n = verts.Length; i < n; ++i)
|
||||
verts[i] *= scale;
|
||||
}
|
||||
collider.SetPath(0, verts);
|
||||
}
|
||||
|
||||
public static Bounds GetBoundingBoxBounds (BoundingBoxAttachment boundingBox, float depth = 0) {
|
||||
float[] floats = boundingBox.Vertices;
|
||||
int floatCount = floats.Length;
|
||||
|
||||
Bounds bounds = new Bounds();
|
||||
|
||||
bounds.center = new Vector3(floats[0], floats[1], 0);
|
||||
for (int i = 2; i < floatCount; i += 2)
|
||||
bounds.Encapsulate(new Vector3(floats[i], floats[i + 1], 0));
|
||||
|
||||
Vector3 size = bounds.size;
|
||||
size.z = depth;
|
||||
bounds.size = size;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public static Rigidbody2D AddBoneRigidbody2D (GameObject gameObject, bool isKinematic = true, float gravityScale = 0f) {
|
||||
var rb = gameObject.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) {
|
||||
rb = gameObject.AddComponent<Rigidbody2D>();
|
||||
rb.isKinematic = isKinematic;
|
||||
rb.gravityScale = gravityScale;
|
||||
}
|
||||
return rb;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public delegate void SkeletonUtilityDelegate ();
|
||||
public event SkeletonUtilityDelegate OnReset;
|
||||
public Transform boneRoot;
|
||||
/// <summary>
|
||||
/// If true, <see cref="Skeleton.ScaleX"/> and <see cref="Skeleton.ScaleY"/> are followed
|
||||
/// by 180 degree rotation. If false, negative Transform scale is used.
|
||||
/// Note that using negative scale is consistent with previous behaviour (hence the default),
|
||||
/// however causes serious problems with rigidbodies and physics. Therefore, it is recommended to
|
||||
/// enable this parameter where possible. When creating hinge chains for a chain of skeleton bones
|
||||
/// via <see cref="SkeletonUtilityBone"/>, it is mandatory to have <c>flipBy180DegreeRotation</c> enabled.
|
||||
/// </summary>
|
||||
public bool flipBy180DegreeRotation = false;
|
||||
|
||||
void Update () {
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
if (skeleton != null && boneRoot != null) {
|
||||
|
||||
if (flipBy180DegreeRotation) {
|
||||
boneRoot.localScale = new Vector3(Mathf.Abs(skeleton.ScaleX), Mathf.Abs(skeleton.ScaleY), 1f);
|
||||
boneRoot.eulerAngles = new Vector3(skeleton.ScaleY > 0 ? 0 : 180,
|
||||
skeleton.ScaleX > 0 ? 0 : 180,
|
||||
0);
|
||||
}
|
||||
else {
|
||||
boneRoot.localScale = new Vector3(skeleton.ScaleX, skeleton.ScaleY, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
if (canvas != null) {
|
||||
positionScale = canvas.referencePixelsPerUnit;
|
||||
}
|
||||
}
|
||||
|
||||
[HideInInspector] public SkeletonRenderer skeletonRenderer;
|
||||
[HideInInspector] public SkeletonGraphic skeletonGraphic;
|
||||
private Canvas canvas;
|
||||
[System.NonSerialized] public ISkeletonAnimation skeletonAnimation;
|
||||
|
||||
private ISkeletonComponent skeletonComponent;
|
||||
[System.NonSerialized] public List<SkeletonUtilityBone> boneComponents = new List<SkeletonUtilityBone>();
|
||||
[System.NonSerialized] public List<SkeletonUtilityConstraint> constraintComponents = new List<SkeletonUtilityConstraint>();
|
||||
|
||||
|
||||
public ISkeletonComponent SkeletonComponent {
|
||||
get {
|
||||
if (skeletonComponent == null) {
|
||||
skeletonComponent = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonComponent>() :
|
||||
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonComponent>() :
|
||||
GetComponent<ISkeletonComponent>();
|
||||
}
|
||||
return skeletonComponent;
|
||||
}
|
||||
}
|
||||
public Skeleton Skeleton {
|
||||
get {
|
||||
if (SkeletonComponent == null)
|
||||
return null;
|
||||
return skeletonComponent.Skeleton;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid {
|
||||
get {
|
||||
return (skeletonRenderer != null && skeletonRenderer.valid) ||
|
||||
(skeletonGraphic != null && skeletonGraphic.IsValid);
|
||||
}
|
||||
}
|
||||
|
||||
public float PositionScale { get { return positionScale; } }
|
||||
|
||||
float positionScale = 1.0f;
|
||||
bool hasOverrideBones;
|
||||
bool hasConstraints;
|
||||
bool needToReprocessBones;
|
||||
|
||||
public void ResubscribeEvents () {
|
||||
OnDisable();
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (skeletonRenderer == null) {
|
||||
skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
}
|
||||
if (skeletonGraphic == null) {
|
||||
skeletonGraphic = GetComponent<SkeletonGraphic>();
|
||||
}
|
||||
if (skeletonAnimation == null) {
|
||||
skeletonAnimation = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonAnimation>() :
|
||||
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonAnimation>() :
|
||||
GetComponent<ISkeletonAnimation>();
|
||||
}
|
||||
if (skeletonComponent == null) {
|
||||
skeletonComponent = skeletonRenderer != null ? skeletonRenderer.GetComponent<ISkeletonComponent>() :
|
||||
skeletonGraphic != null ? skeletonGraphic.GetComponent<ISkeletonComponent>() :
|
||||
GetComponent<ISkeletonComponent>();
|
||||
}
|
||||
|
||||
if (skeletonRenderer != null) {
|
||||
skeletonRenderer.OnRebuild -= HandleRendererReset;
|
||||
skeletonRenderer.OnRebuild += HandleRendererReset;
|
||||
}
|
||||
else if (skeletonGraphic != null) {
|
||||
skeletonGraphic.OnRebuild -= HandleRendererReset;
|
||||
skeletonGraphic.OnRebuild += HandleRendererReset;
|
||||
canvas = skeletonGraphic.canvas;
|
||||
if (canvas == null)
|
||||
canvas = skeletonGraphic.GetComponentInParent<Canvas>();
|
||||
if (canvas == null)
|
||||
positionScale = 100.0f;
|
||||
}
|
||||
|
||||
if (skeletonAnimation != null) {
|
||||
skeletonAnimation.UpdateLocal -= UpdateLocal;
|
||||
skeletonAnimation.UpdateLocal += UpdateLocal;
|
||||
}
|
||||
|
||||
CollectBones();
|
||||
}
|
||||
|
||||
void Start () {
|
||||
//recollect because order of operations failure when switching between game mode and edit mode...
|
||||
CollectBones();
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (skeletonRenderer != null)
|
||||
skeletonRenderer.OnRebuild -= HandleRendererReset;
|
||||
if (skeletonGraphic != null)
|
||||
skeletonGraphic.OnRebuild -= HandleRendererReset;
|
||||
|
||||
if (skeletonAnimation != null) {
|
||||
skeletonAnimation.UpdateLocal -= UpdateLocal;
|
||||
skeletonAnimation.UpdateWorld -= UpdateWorld;
|
||||
skeletonAnimation.UpdateComplete -= UpdateComplete;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleRendererReset (SkeletonRenderer r) {
|
||||
if (OnReset != null) OnReset();
|
||||
CollectBones();
|
||||
}
|
||||
|
||||
void HandleRendererReset (SkeletonGraphic g) {
|
||||
if (OnReset != null) OnReset();
|
||||
CollectBones();
|
||||
}
|
||||
|
||||
public void RegisterBone (SkeletonUtilityBone bone) {
|
||||
if (boneComponents.Contains(bone)) {
|
||||
return;
|
||||
} else {
|
||||
boneComponents.Add(bone);
|
||||
needToReprocessBones = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterBone (SkeletonUtilityBone bone) {
|
||||
boneComponents.Remove(bone);
|
||||
}
|
||||
|
||||
public void RegisterConstraint (SkeletonUtilityConstraint constraint) {
|
||||
if (constraintComponents.Contains(constraint))
|
||||
return;
|
||||
else {
|
||||
constraintComponents.Add(constraint);
|
||||
needToReprocessBones = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterConstraint (SkeletonUtilityConstraint constraint) {
|
||||
constraintComponents.Remove(constraint);
|
||||
}
|
||||
|
||||
public void CollectBones () {
|
||||
var skeleton = skeletonComponent.Skeleton;
|
||||
if (skeleton == null) return;
|
||||
|
||||
if (boneRoot != null) {
|
||||
var constraintTargets = new List<System.Object>();
|
||||
var ikConstraints = skeleton.IkConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++)
|
||||
constraintTargets.Add(ikConstraints.Items[i].target);
|
||||
|
||||
var transformConstraints = skeleton.TransformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++)
|
||||
constraintTargets.Add(transformConstraints.Items[i].target);
|
||||
|
||||
var boneComponents = this.boneComponents;
|
||||
for (int i = 0, n = boneComponents.Count; i < n; i++) {
|
||||
var b = boneComponents[i];
|
||||
if (b.bone == null) {
|
||||
b.DoUpdate(SkeletonUtilityBone.UpdatePhase.Local);
|
||||
if (b.bone == null) continue;
|
||||
}
|
||||
hasOverrideBones |= (b.mode == SkeletonUtilityBone.Mode.Override);
|
||||
hasConstraints |= constraintTargets.Contains(b.bone);
|
||||
}
|
||||
|
||||
hasConstraints |= constraintComponents.Count > 0;
|
||||
|
||||
if (skeletonAnimation != null) {
|
||||
skeletonAnimation.UpdateWorld -= UpdateWorld;
|
||||
skeletonAnimation.UpdateComplete -= UpdateComplete;
|
||||
|
||||
if (hasOverrideBones || hasConstraints)
|
||||
skeletonAnimation.UpdateWorld += UpdateWorld;
|
||||
|
||||
if (hasConstraints)
|
||||
skeletonAnimation.UpdateComplete += UpdateComplete;
|
||||
}
|
||||
|
||||
needToReprocessBones = false;
|
||||
} else {
|
||||
boneComponents.Clear();
|
||||
constraintComponents.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateLocal (ISkeletonAnimation anim) {
|
||||
if (needToReprocessBones)
|
||||
CollectBones();
|
||||
|
||||
var boneComponents = this.boneComponents;
|
||||
if (boneComponents == null) return;
|
||||
for (int i = 0, n = boneComponents.Count; i < n; i++)
|
||||
boneComponents[i].transformLerpComplete = false;
|
||||
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Local);
|
||||
}
|
||||
|
||||
void UpdateWorld (ISkeletonAnimation anim) {
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.World);
|
||||
for (int i = 0, n = constraintComponents.Count; i < n; i++)
|
||||
constraintComponents[i].DoUpdate();
|
||||
}
|
||||
|
||||
void UpdateComplete (ISkeletonAnimation anim) {
|
||||
UpdateAllBones(SkeletonUtilityBone.UpdatePhase.Complete);
|
||||
}
|
||||
|
||||
void UpdateAllBones (SkeletonUtilityBone.UpdatePhase phase) {
|
||||
if (boneRoot == null)
|
||||
CollectBones();
|
||||
|
||||
var boneComponents = this.boneComponents;
|
||||
if (boneComponents == null) return;
|
||||
for (int i = 0, n = boneComponents.Count; i < n; i++)
|
||||
boneComponents[i].DoUpdate(phase);
|
||||
}
|
||||
|
||||
public Transform GetBoneRoot () {
|
||||
if (boneRoot != null)
|
||||
return boneRoot;
|
||||
|
||||
var boneRootObject = new GameObject("SkeletonUtility-SkeletonRoot");
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.Undo.RegisterCreatedObjectUndo(boneRootObject, "Spawn Bone");
|
||||
#endif
|
||||
if (skeletonGraphic != null)
|
||||
boneRootObject.AddComponent<RectTransform>();
|
||||
|
||||
boneRoot = boneRootObject.transform;
|
||||
boneRoot.SetParent(transform);
|
||||
boneRoot.localPosition = Vector3.zero;
|
||||
boneRoot.localRotation = Quaternion.identity;
|
||||
boneRoot.localScale = Vector3.one;
|
||||
|
||||
return boneRoot;
|
||||
}
|
||||
|
||||
public GameObject SpawnRoot (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
|
||||
GetBoneRoot();
|
||||
Skeleton skeleton = this.skeletonComponent.Skeleton;
|
||||
|
||||
GameObject go = SpawnBone(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
|
||||
CollectBones();
|
||||
return go;
|
||||
}
|
||||
|
||||
public GameObject SpawnHierarchy (SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
|
||||
GetBoneRoot();
|
||||
Skeleton skeleton = this.skeletonComponent.Skeleton;
|
||||
GameObject go = SpawnBoneRecursively(skeleton.RootBone, boneRoot, mode, pos, rot, sca);
|
||||
CollectBones();
|
||||
return go;
|
||||
}
|
||||
|
||||
public GameObject SpawnBoneRecursively (Bone bone, Transform parent, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
|
||||
GameObject go = SpawnBone(bone, parent, mode, pos, rot, sca);
|
||||
|
||||
ExposedList<Bone> childrenBones = bone.Children;
|
||||
for (int i = 0, n = childrenBones.Count; i < n; i++) {
|
||||
Bone child = childrenBones.Items[i];
|
||||
SpawnBoneRecursively(child, go.transform, mode, pos, rot, sca);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
public GameObject SpawnBone (Bone bone, Transform parent, SkeletonUtilityBone.Mode mode, bool pos, bool rot, bool sca) {
|
||||
GameObject go = new GameObject(bone.Data.Name);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Spawn Bone");
|
||||
#endif
|
||||
if (skeletonGraphic != null)
|
||||
go.AddComponent<RectTransform>();
|
||||
|
||||
var goTransform = go.transform;
|
||||
goTransform.SetParent(parent);
|
||||
|
||||
SkeletonUtilityBone b = go.AddComponent<SkeletonUtilityBone>();
|
||||
b.hierarchy = this;
|
||||
b.position = pos;
|
||||
b.rotation = rot;
|
||||
b.scale = sca;
|
||||
b.mode = mode;
|
||||
b.zPosition = true;
|
||||
b.Reset();
|
||||
b.bone = bone;
|
||||
b.boneName = bone.Data.Name;
|
||||
b.valid = true;
|
||||
|
||||
if (mode == SkeletonUtilityBone.Mode.Override) {
|
||||
if (rot) goTransform.localRotation = Quaternion.Euler(0, 0, b.bone.AppliedRotation);
|
||||
if (pos) goTransform.localPosition = new Vector3(b.bone.X * positionScale, b.bone.Y * positionScale, 0);
|
||||
goTransform.localScale = new Vector3(b.bone.scaleX, b.bone.scaleY, 0);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f726fb798ad621458c431cb9966d91d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,243 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Sets a GameObject's transform to match a bone on a Spine skeleton.</summary>
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[AddComponentMenu("Spine/SkeletonUtilityBone")]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtilityBone")]
|
||||
public class SkeletonUtilityBone : MonoBehaviour {
|
||||
public enum Mode {
|
||||
Follow,
|
||||
Override
|
||||
}
|
||||
|
||||
public enum UpdatePhase {
|
||||
Local,
|
||||
World,
|
||||
Complete
|
||||
}
|
||||
|
||||
#region Inspector
|
||||
/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
|
||||
public string boneName;
|
||||
public Transform parentReference;
|
||||
public Mode mode;
|
||||
public bool position, rotation, scale, zPosition = true;
|
||||
[Range(0f, 1f)]
|
||||
public float overrideAlpha = 1;
|
||||
#endregion
|
||||
|
||||
public SkeletonUtility hierarchy;
|
||||
[System.NonSerialized] public Bone bone;
|
||||
[System.NonSerialized] public bool transformLerpComplete;
|
||||
[System.NonSerialized] public bool valid;
|
||||
Transform cachedTransform;
|
||||
Transform skeletonTransform;
|
||||
bool incompatibleTransformMode;
|
||||
public bool IncompatibleTransformMode { get { return incompatibleTransformMode; } }
|
||||
|
||||
public void Reset () {
|
||||
bone = null;
|
||||
cachedTransform = transform;
|
||||
valid = hierarchy != null && hierarchy.IsValid;
|
||||
if (!valid)
|
||||
return;
|
||||
skeletonTransform = hierarchy.transform;
|
||||
hierarchy.OnReset -= HandleOnReset;
|
||||
hierarchy.OnReset += HandleOnReset;
|
||||
DoUpdate(UpdatePhase.Local);
|
||||
}
|
||||
|
||||
void OnEnable () {
|
||||
if (hierarchy == null) hierarchy = transform.GetComponentInParent<SkeletonUtility>();
|
||||
if (hierarchy == null) return;
|
||||
|
||||
hierarchy.RegisterBone(this);
|
||||
hierarchy.OnReset += HandleOnReset;
|
||||
}
|
||||
|
||||
void HandleOnReset () {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void OnDisable () {
|
||||
if (hierarchy != null) {
|
||||
hierarchy.OnReset -= HandleOnReset;
|
||||
hierarchy.UnregisterBone(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void DoUpdate (UpdatePhase phase) {
|
||||
if (!valid) {
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
var skeleton = hierarchy.Skeleton;
|
||||
|
||||
if (bone == null) {
|
||||
if (string.IsNullOrEmpty(boneName)) return;
|
||||
bone = skeleton.FindBone(boneName);
|
||||
if (bone == null) {
|
||||
Debug.LogError("Bone not found: " + boneName, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!bone.Active) return;
|
||||
|
||||
float positionScale = hierarchy.PositionScale;
|
||||
|
||||
var thisTransform = cachedTransform;
|
||||
float skeletonFlipRotation = Mathf.Sign(skeleton.ScaleX * skeleton.ScaleY);
|
||||
if (mode == Mode.Follow) {
|
||||
switch (phase) {
|
||||
case UpdatePhase.Local:
|
||||
if (position)
|
||||
thisTransform.localPosition = new Vector3(bone.x * positionScale, bone.y * positionScale, 0);
|
||||
|
||||
if (rotation) {
|
||||
if (bone.data.transformMode.InheritsRotation()) {
|
||||
thisTransform.localRotation = Quaternion.Euler(0, 0, bone.rotation);
|
||||
} else {
|
||||
Vector3 euler = skeletonTransform.rotation.eulerAngles;
|
||||
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
|
||||
}
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
thisTransform.localScale = new Vector3(bone.scaleX, bone.scaleY, 1f);
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
}
|
||||
break;
|
||||
case UpdatePhase.World:
|
||||
case UpdatePhase.Complete:
|
||||
// Use Applied transform values (ax, ay, AppliedRotation, ascale) if world values were modified by constraints.
|
||||
if (!bone.appliedValid) {
|
||||
bone.UpdateAppliedTransform();
|
||||
}
|
||||
|
||||
if (position)
|
||||
thisTransform.localPosition = new Vector3(bone.ax * positionScale, bone.ay * positionScale, 0);
|
||||
|
||||
if (rotation) {
|
||||
if (bone.data.transformMode.InheritsRotation()) {
|
||||
thisTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
|
||||
} else {
|
||||
Vector3 euler = skeletonTransform.rotation.eulerAngles;
|
||||
thisTransform.rotation = Quaternion.Euler(euler.x, euler.y, euler.z + (bone.WorldRotationX * skeletonFlipRotation));
|
||||
}
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
thisTransform.localScale = new Vector3(bone.ascaleX, bone.ascaleY, 1f);
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (mode == Mode.Override) {
|
||||
if (transformLerpComplete)
|
||||
return;
|
||||
|
||||
if (parentReference == null) {
|
||||
if (position) {
|
||||
Vector3 clp = thisTransform.localPosition / positionScale;
|
||||
bone.x = Mathf.Lerp(bone.x, clp.x, overrideAlpha);
|
||||
bone.y = Mathf.Lerp(bone.y, clp.y, overrideAlpha);
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
float angle = Mathf.LerpAngle(bone.Rotation, thisTransform.localRotation.eulerAngles.z, overrideAlpha);
|
||||
bone.Rotation = angle;
|
||||
bone.AppliedRotation = angle;
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
Vector3 cls = thisTransform.localScale;
|
||||
bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
|
||||
bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (transformLerpComplete)
|
||||
return;
|
||||
|
||||
if (position) {
|
||||
Vector3 pos = parentReference.InverseTransformPoint(thisTransform.position) / positionScale;
|
||||
bone.x = Mathf.Lerp(bone.x, pos.x, overrideAlpha);
|
||||
bone.y = Mathf.Lerp(bone.y, pos.y, overrideAlpha);
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
float angle = Mathf.LerpAngle(bone.Rotation, Quaternion.LookRotation(Vector3.forward, parentReference.InverseTransformDirection(thisTransform.up)).eulerAngles.z, overrideAlpha);
|
||||
bone.Rotation = angle;
|
||||
bone.AppliedRotation = angle;
|
||||
}
|
||||
|
||||
if (scale) {
|
||||
Vector3 cls = thisTransform.localScale;
|
||||
bone.scaleX = Mathf.Lerp(bone.scaleX, cls.x, overrideAlpha);
|
||||
bone.scaleY = Mathf.Lerp(bone.scaleY, cls.y, overrideAlpha);
|
||||
}
|
||||
|
||||
incompatibleTransformMode = BoneTransformModeIncompatible(bone);
|
||||
}
|
||||
|
||||
transformLerpComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool BoneTransformModeIncompatible (Bone bone) {
|
||||
return !bone.data.transformMode.InheritsScale();
|
||||
}
|
||||
|
||||
public void AddBoundingBox (string skinName, string slotName, string attachmentName) {
|
||||
SkeletonUtility.AddBoneRigidbody2D(transform.gameObject);
|
||||
SkeletonUtility.AddBoundingBoxGameObject(bone.skeleton, skinName, slotName, attachmentName, transform);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnDrawGizmos () {
|
||||
if (IncompatibleTransformMode)
|
||||
Gizmos.DrawIcon(transform.position + new Vector3(0, 0.128f, 0), "icon-warning");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b238dfcde8209044b97d23f62bcaadf6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
#if UNITY_2018_3 || UNITY_2019 || UNITY_2018_3_OR_NEWER
|
||||
#define NEW_PREFAB_SYSTEM
|
||||
#endif
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Spine.Unity {
|
||||
|
||||
#if NEW_PREFAB_SYSTEM
|
||||
[ExecuteAlways]
|
||||
#else
|
||||
[ExecuteInEditMode]
|
||||
#endif
|
||||
[RequireComponent(typeof(SkeletonUtilityBone))]
|
||||
[HelpURL("http://esotericsoftware.com/spine-unity#SkeletonUtilityConstraint")]
|
||||
public abstract class SkeletonUtilityConstraint : MonoBehaviour {
|
||||
|
||||
protected SkeletonUtilityBone bone;
|
||||
protected SkeletonUtility hierarchy;
|
||||
|
||||
protected virtual void OnEnable () {
|
||||
bone = GetComponent<SkeletonUtilityBone>();
|
||||
hierarchy = transform.GetComponentInParent<SkeletonUtility>();
|
||||
hierarchy.RegisterConstraint(this);
|
||||
}
|
||||
|
||||
protected virtual void OnDisable () {
|
||||
hierarchy.UnregisterConstraint(this);
|
||||
}
|
||||
|
||||
public abstract void DoUpdate ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 522dbfcc6c916df4396f14f35048d185
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Spine/Runtime/spine-unity/Deprecated.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Deprecated.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04817e31b917de6489f349dd332d7468
|
||||
folderAsset: yes
|
||||
timeCreated: 1563295668
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfdd78a071ca1a04bb64c6cc41e14aa0
|
||||
folderAsset: yes
|
||||
timeCreated: 1496447038
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,230 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace Spine.Unity.Deprecated {
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated. The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. See the
|
||||
/// <see href="http://esotericsoftware.com/spine-unity-skeletondatamodifierassets#BlendModeMaterials">SkeletonDataModifierAssets BlendModeMaterials documentation page</see> and
|
||||
/// <see href="http://esotericsoftware.com/forum/Slot-blending-not-work-11281">this forum thread</see> for further information.
|
||||
/// This class will be removed in the spine-unity 3.9 runtime.
|
||||
/// </summary>
|
||||
[Obsolete("The spine-unity 3.7 runtime introduced SkeletonDataModifierAssets BlendModeMaterials which replaced SlotBlendModes. Will be removed in spine-unity 3.9.", false)]
|
||||
[DisallowMultipleComponent]
|
||||
public class SlotBlendModes : MonoBehaviour {
|
||||
|
||||
#region Internal Material Dictionary
|
||||
public struct MaterialTexturePair {
|
||||
public Texture2D texture2D;
|
||||
public Material material;
|
||||
}
|
||||
|
||||
internal class MaterialWithRefcount {
|
||||
public Material materialClone;
|
||||
public int refcount = 1;
|
||||
|
||||
public MaterialWithRefcount(Material mat) {
|
||||
this.materialClone = mat;
|
||||
}
|
||||
}
|
||||
static Dictionary<MaterialTexturePair, MaterialWithRefcount> materialTable;
|
||||
internal static Dictionary<MaterialTexturePair, MaterialWithRefcount> MaterialTable {
|
||||
get {
|
||||
if (materialTable == null) materialTable = new Dictionary<MaterialTexturePair, MaterialWithRefcount>();
|
||||
return materialTable;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct SlotMaterialTextureTuple {
|
||||
public Slot slot;
|
||||
public Texture2D texture2D;
|
||||
public Material material;
|
||||
|
||||
public SlotMaterialTextureTuple(Slot slot, Material material, Texture2D texture) {
|
||||
this.slot = slot;
|
||||
this.material = material;
|
||||
this.texture2D = texture;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Material GetOrAddMaterialFor(Material materialSource, Texture2D texture) {
|
||||
if (materialSource == null || texture == null) return null;
|
||||
|
||||
var mt = SlotBlendModes.MaterialTable;
|
||||
MaterialWithRefcount matWithRefcount;
|
||||
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
|
||||
if (!mt.TryGetValue(key, out matWithRefcount)) {
|
||||
matWithRefcount = new MaterialWithRefcount(new Material(materialSource));
|
||||
var m = matWithRefcount.materialClone;
|
||||
m.name = "(Clone)" + texture.name + "-" + materialSource.name;
|
||||
m.mainTexture = texture;
|
||||
mt[key] = matWithRefcount;
|
||||
}
|
||||
else {
|
||||
matWithRefcount.refcount++;
|
||||
}
|
||||
return matWithRefcount.materialClone;
|
||||
}
|
||||
|
||||
internal static MaterialWithRefcount GetExistingMaterialFor(Material materialSource, Texture2D texture)
|
||||
{
|
||||
if (materialSource == null || texture == null) return null;
|
||||
|
||||
var mt = SlotBlendModes.MaterialTable;
|
||||
MaterialWithRefcount matWithRefcount;
|
||||
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
|
||||
if (!mt.TryGetValue(key, out matWithRefcount)) {
|
||||
return null;
|
||||
}
|
||||
return matWithRefcount;
|
||||
}
|
||||
|
||||
internal static void RemoveMaterialFromTable(Material materialSource, Texture2D texture) {
|
||||
var mt = SlotBlendModes.MaterialTable;
|
||||
var key = new MaterialTexturePair { material = materialSource, texture2D = texture };
|
||||
mt.Remove(key);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Inspector
|
||||
public Material multiplyMaterialSource;
|
||||
public Material screenMaterialSource;
|
||||
|
||||
Texture2D texture;
|
||||
#endregion
|
||||
|
||||
SlotMaterialTextureTuple[] slotsWithCustomMaterial = new SlotMaterialTextureTuple[0];
|
||||
|
||||
public bool Applied { get; private set; }
|
||||
|
||||
void Start() {
|
||||
if (!Applied) Apply();
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
if (Applied) Remove();
|
||||
}
|
||||
|
||||
public void Apply() {
|
||||
GetTexture();
|
||||
if (texture == null) return;
|
||||
|
||||
var skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null) return;
|
||||
|
||||
var slotMaterials = skeletonRenderer.CustomSlotMaterials;
|
||||
|
||||
int numSlotsWithCustomMaterial = 0;
|
||||
foreach (var s in skeletonRenderer.Skeleton.Slots) {
|
||||
switch (s.data.blendMode) {
|
||||
case BlendMode.Multiply:
|
||||
if (multiplyMaterialSource != null) {
|
||||
slotMaterials[s] = GetOrAddMaterialFor(multiplyMaterialSource, texture);
|
||||
++numSlotsWithCustomMaterial;
|
||||
}
|
||||
break;
|
||||
case BlendMode.Screen:
|
||||
if (screenMaterialSource != null) {
|
||||
slotMaterials[s] = GetOrAddMaterialFor(screenMaterialSource, texture);
|
||||
++numSlotsWithCustomMaterial;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
slotsWithCustomMaterial = new SlotMaterialTextureTuple[numSlotsWithCustomMaterial];
|
||||
int storedSlotIndex = 0;
|
||||
foreach (var s in skeletonRenderer.Skeleton.Slots) {
|
||||
switch (s.data.blendMode) {
|
||||
case BlendMode.Multiply:
|
||||
if (multiplyMaterialSource != null) {
|
||||
slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, multiplyMaterialSource, texture);
|
||||
}
|
||||
break;
|
||||
case BlendMode.Screen:
|
||||
if (screenMaterialSource != null) {
|
||||
slotsWithCustomMaterial[storedSlotIndex++] = new SlotMaterialTextureTuple(s, screenMaterialSource, texture);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Applied = true;
|
||||
skeletonRenderer.LateUpdate();
|
||||
}
|
||||
|
||||
public void Remove() {
|
||||
GetTexture();
|
||||
if (texture == null) return;
|
||||
|
||||
var skeletonRenderer = GetComponent<SkeletonRenderer>();
|
||||
if (skeletonRenderer == null) return;
|
||||
|
||||
var slotMaterials = skeletonRenderer.CustomSlotMaterials;
|
||||
|
||||
foreach (var slotWithCustomMat in slotsWithCustomMaterial) {
|
||||
|
||||
Slot s = slotWithCustomMat.slot;
|
||||
Material storedMaterialSource = slotWithCustomMat.material;
|
||||
Texture2D storedTexture = slotWithCustomMat.texture2D;
|
||||
|
||||
var matWithRefcount = GetExistingMaterialFor(storedMaterialSource, storedTexture);
|
||||
if (--matWithRefcount.refcount == 0) {
|
||||
RemoveMaterialFromTable(storedMaterialSource, storedTexture);
|
||||
}
|
||||
// we don't want to remove slotMaterials[s] if it has been changed in the meantime.
|
||||
Material m;
|
||||
if (slotMaterials.TryGetValue(s, out m)) {
|
||||
var existingMat = matWithRefcount == null ? null : matWithRefcount.materialClone;
|
||||
if (Material.ReferenceEquals(m, existingMat)) {
|
||||
slotMaterials.Remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
slotsWithCustomMaterial = null;
|
||||
|
||||
Applied = false;
|
||||
if (skeletonRenderer.valid) skeletonRenderer.LateUpdate();
|
||||
}
|
||||
|
||||
public void GetTexture() {
|
||||
if (texture == null) {
|
||||
var sr = GetComponent<SkeletonRenderer>(); if (sr == null) return;
|
||||
var sda = sr.skeletonDataAsset; if (sda == null) return;
|
||||
var aa = sda.atlasAssets[0]; if (aa == null) return;
|
||||
var am = aa.PrimaryMaterial; if (am == null) return;
|
||||
texture = am.mainTexture as Texture2D;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f8243645ba2e74aa3564bd956eed89
|
||||
timeCreated: 1496794038
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- multiplyMaterialSource: {fileID: 2100000, guid: 53bf0ab317d032d418cf1252d68f51df,
|
||||
type: 2}
|
||||
- screenMaterialSource: {fileID: 2100000, guid: 73f0f46d3177c614baf0fa48d646a9be,
|
||||
type: 2}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
81
Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs
Normal file
81
Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
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 delegate void UpdateBonesDelegate (ISkeletonAnimation animated);
|
||||
|
||||
/// <summary>A Spine-Unity Component that animates a Skeleton but not necessarily with a Spine.AnimationState.</summary>
|
||||
public interface ISkeletonAnimation {
|
||||
event UpdateBonesDelegate UpdateLocal;
|
||||
event UpdateBonesDelegate UpdateWorld;
|
||||
event UpdateBonesDelegate UpdateComplete;
|
||||
Skeleton Skeleton { get; }
|
||||
}
|
||||
|
||||
/// <summary>Holds a reference to a SkeletonDataAsset.</summary>
|
||||
public interface IHasSkeletonDataAsset {
|
||||
/// <summary>Gets the SkeletonDataAsset of the Spine Component.</summary>
|
||||
SkeletonDataAsset SkeletonDataAsset { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that manages a Spine.Skeleton instance, instantiated from a SkeletonDataAsset.</summary>
|
||||
public interface ISkeletonComponent {
|
||||
/// <summary>Gets the SkeletonDataAsset of the Spine Component.</summary>
|
||||
//[System.Obsolete]
|
||||
SkeletonDataAsset SkeletonDataAsset { get; }
|
||||
|
||||
/// <summary>Gets the Spine.Skeleton instance of the Spine Component. This is equivalent to SkeletonRenderer's .skeleton.</summary>
|
||||
Skeleton Skeleton { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that uses a Spine.AnimationState to animate its skeleton.</summary>
|
||||
public interface IAnimationStateComponent {
|
||||
/// <summary>Gets the Spine.AnimationState of the animated Spine Component. This is equivalent to SkeletonAnimation.state.</summary>
|
||||
AnimationState AnimationState { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that holds a reference to a SkeletonRenderer.</summary>
|
||||
public interface IHasSkeletonRenderer {
|
||||
SkeletonRenderer SkeletonRenderer { get; }
|
||||
}
|
||||
|
||||
/// <summary>A Spine-Unity Component that holds a reference to an ISkeletonComponent.</summary>
|
||||
public interface IHasSkeletonComponent {
|
||||
ISkeletonComponent SkeletonComponent { get; }
|
||||
}
|
||||
}
|
||||
10
Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs.meta
Normal file
10
Assets/Spine/Runtime/spine-unity/ISkeletonAnimation.cs.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7b480b941568134891f411137bfbf55
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Spine/Runtime/spine-unity/Materials.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Materials.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43897010c7e77c54897cb98c1ddf84f1
|
||||
folderAsset: yes
|
||||
timeCreated: 1455128695
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,106 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: SkeletonGraphicDefault
|
||||
m_Shader: {fileID: 4800000, guid: fa95b0fb6983c0f40a152e6f9aa82bfb, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords:
|
||||
- _STRAIGHT_ALPHA_INPUT
|
||||
m_InvalidKeywords:
|
||||
- _USE8NEIGHBOURHOOD_ON
|
||||
m_LightmapFlags: 5
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
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_Ints: []
|
||||
m_Floats:
|
||||
- PixelSnap: 0
|
||||
- _BumpScale: 1
|
||||
- _CanvasGroupCompatible: 0
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _EnableExternalAlpha: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _OutlineMipLevel: 0
|
||||
- _OutlineReferenceTexWidth: 1024
|
||||
- _OutlineSmoothness: 1
|
||||
- _OutlineWidth: 3
|
||||
- _Parallax: 0.02
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _StraightAlphaInput: 1
|
||||
- _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: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b66cf7a186d13054989b33a5c90044e4
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: SkeletonGraphicDefaultOutline
|
||||
m_Shader: {fileID: 4800000, guid: 8f5d14d2a7fedb84998c50eb96c8b748, type: 3}
|
||||
m_ShaderKeywords: _USE8NEIGHBOURHOOD_ON
|
||||
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
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _EnableExternalAlpha: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _OutlineMipLevel: 0
|
||||
- _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}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4ee0f8f4be17434aa3df5774a03b366
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: SkeletonGraphicTintBlack
|
||||
m_Shader: {fileID: 4800000, guid: f64c7bc238bb2c246b8ca1912b2b6b9c, type: 3}
|
||||
m_ShaderKeywords:
|
||||
m_LightmapFlags: 5
|
||||
m_EnableInstancingVariants: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _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:
|
||||
- _BumpScale: 1
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _UseUIAlphaClip: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Black: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfcea0e11aa80bb4b8d05790b905fc31
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: SkeletonGraphicTintBlackOutline
|
||||
m_Shader: {fileID: 4800000, guid: d55d64dd09c46af40a319933a62fa1b2, type: 3}
|
||||
m_ShaderKeywords: _USE8NEIGHBOURHOOD_ON
|
||||
m_LightmapFlags: 5
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _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:
|
||||
- _BumpScale: 1
|
||||
- _ColorMask: 15
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _Glossiness: 0.5
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _OutlineMipLevel: 0
|
||||
- _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:
|
||||
- _Black: {r: 0, g: 0, b: 0, a: 0}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _OutlineColor: {r: 1, g: 1, b: 0, a: 1}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94fe565c79b0aeb418cd05e4f1f8343c
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Spine/Runtime/spine-unity/Mesh Generation.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Mesh Generation.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5d065c4fe677ad4495a852580ec32fa
|
||||
folderAsset: yes
|
||||
timeCreated: 1455493477
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
namespace Spine.Unity {
|
||||
public class DoubleBuffered<T> where T : new() {
|
||||
readonly T a = new T();
|
||||
readonly T b = new T();
|
||||
bool usingA;
|
||||
|
||||
public T GetCurrent () {
|
||||
return usingA ? a : b;
|
||||
}
|
||||
|
||||
public T GetNext () {
|
||||
usingA = !usingA;
|
||||
return usingA ? a : b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08b76da7751523448a87e528c48a5399
|
||||
timeCreated: 1457396939
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1387
Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs
Normal file
1387
Assets/Spine/Runtime/spine-unity/Mesh Generation/MeshGenerator.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 311447d6e56727c4dad7611d5fa5afbf
|
||||
timeCreated: 1563322425
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Not for optimization. Do not disable.
|
||||
#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
|
||||
//#define SPINE_DEBUG
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>A double-buffered Mesh, and a shared material array, bundled for use by Spine components that need to push a Mesh and materials to a Unity MeshRenderer and MeshFilter.</summary>
|
||||
public class MeshRendererBuffers : IDisposable {
|
||||
DoubleBuffered<SmartMesh> doubleBufferedMesh;
|
||||
internal readonly ExposedList<Material> submeshMaterials = new ExposedList<Material>();
|
||||
internal Material[] sharedMaterials = new Material[0];
|
||||
|
||||
public void Initialize () {
|
||||
if (doubleBufferedMesh != null) {
|
||||
doubleBufferedMesh.GetNext().Clear();
|
||||
doubleBufferedMesh.GetNext().Clear();
|
||||
submeshMaterials.Clear();
|
||||
} else {
|
||||
doubleBufferedMesh = new DoubleBuffered<SmartMesh>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns a sharedMaterials array for use on a MeshRenderer.</summary>
|
||||
/// <returns></returns>
|
||||
public Material[] GetUpdatedSharedMaterialsArray () {
|
||||
if (submeshMaterials.Count == sharedMaterials.Length)
|
||||
submeshMaterials.CopyTo(sharedMaterials);
|
||||
else
|
||||
sharedMaterials = submeshMaterials.ToArray();
|
||||
|
||||
return sharedMaterials;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the materials were modified since the buffers were last updated.</summary>
|
||||
public bool MaterialsChangedInLastUpdate () {
|
||||
int newSubmeshMaterials = submeshMaterials.Count;
|
||||
var sharedMaterials = this.sharedMaterials;
|
||||
if (newSubmeshMaterials != sharedMaterials.Length) return true;
|
||||
|
||||
var submeshMaterialsItems = submeshMaterials.Items;
|
||||
for (int i = 0; i < newSubmeshMaterials; i++)
|
||||
if (!Material.ReferenceEquals(submeshMaterialsItems[i], sharedMaterials[i])) return true; //if (submeshMaterialsItems[i].GetInstanceID() != sharedMaterials[i].GetInstanceID()) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Updates the internal shared materials array with the given instruction list.</summary>
|
||||
public void UpdateSharedMaterials (ExposedList<SubmeshInstruction> instructions) {
|
||||
int newSize = instructions.Count;
|
||||
{ //submeshMaterials.Resize(instructions.Count);
|
||||
if (newSize > submeshMaterials.Items.Length)
|
||||
Array.Resize(ref submeshMaterials.Items, newSize);
|
||||
submeshMaterials.Count = newSize;
|
||||
}
|
||||
|
||||
var submeshMaterialsItems = submeshMaterials.Items;
|
||||
var instructionsItems = instructions.Items;
|
||||
for (int i = 0; i < newSize; i++)
|
||||
submeshMaterialsItems[i] = instructionsItems[i].material;
|
||||
}
|
||||
|
||||
public SmartMesh GetNextMesh () {
|
||||
return doubleBufferedMesh.GetNext();
|
||||
}
|
||||
|
||||
public void Clear () {
|
||||
sharedMaterials = new Material[0];
|
||||
submeshMaterials.Clear();
|
||||
}
|
||||
|
||||
public void Dispose () {
|
||||
if (doubleBufferedMesh == null) return;
|
||||
doubleBufferedMesh.GetNext().Dispose();
|
||||
doubleBufferedMesh.GetNext().Dispose();
|
||||
doubleBufferedMesh = null;
|
||||
}
|
||||
|
||||
///<summary>This is a Mesh that also stores the instructions SkeletonRenderer generated for it.</summary>
|
||||
public class SmartMesh : IDisposable {
|
||||
public Mesh mesh = SpineMesh.NewSkeletonMesh();
|
||||
public SkeletonRendererInstruction instructionUsed = new SkeletonRendererInstruction();
|
||||
|
||||
public void Clear () {
|
||||
mesh.Clear();
|
||||
instructionUsed.Clear();
|
||||
}
|
||||
|
||||
public void Dispose () {
|
||||
if (mesh != null) {
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor && !Application.isPlaying)
|
||||
UnityEngine.Object.DestroyImmediate(mesh);
|
||||
else
|
||||
UnityEngine.Object.Destroy(mesh);
|
||||
#else
|
||||
UnityEngine.Object.Destroy(mesh);
|
||||
#endif
|
||||
}
|
||||
mesh = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1ab80744ac17724dbc0d15fdb6f4727
|
||||
timeCreated: 1563322425
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Not for optimization. Do not disable.
|
||||
#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
|
||||
//#define SPINE_DEBUG
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
/// <summary>Instructions used by a SkeletonRenderer to render a mesh.</summary>
|
||||
public class SkeletonRendererInstruction {
|
||||
public readonly ExposedList<SubmeshInstruction> submeshInstructions = new ExposedList<SubmeshInstruction>();
|
||||
|
||||
public bool immutableTriangles;
|
||||
#if SPINE_TRIANGLECHECK
|
||||
public bool hasActiveClipping;
|
||||
public int rawVertexCount = -1;
|
||||
public readonly ExposedList<Attachment> attachments = new ExposedList<Attachment>();
|
||||
#endif
|
||||
|
||||
public void Clear () {
|
||||
#if SPINE_TRIANGLECHECK
|
||||
this.attachments.Clear(false);
|
||||
rawVertexCount = -1;
|
||||
hasActiveClipping = false;
|
||||
#endif
|
||||
this.submeshInstructions.Clear(false);
|
||||
}
|
||||
|
||||
public void Dispose () {
|
||||
attachments.Clear(true);
|
||||
}
|
||||
|
||||
public void SetWithSubset (ExposedList<SubmeshInstruction> instructions, int startSubmesh, int endSubmesh) {
|
||||
#if SPINE_TRIANGLECHECK
|
||||
int runningVertexCount = 0;
|
||||
#endif
|
||||
|
||||
var submeshes = this.submeshInstructions;
|
||||
submeshes.Clear(false);
|
||||
int submeshCount = endSubmesh - startSubmesh;
|
||||
submeshes.Resize(submeshCount);
|
||||
var submeshesItems = submeshes.Items;
|
||||
var instructionsItems = instructions.Items;
|
||||
for (int i = 0; i < submeshCount; i++) {
|
||||
var instruction = instructionsItems[startSubmesh + i];
|
||||
submeshesItems[i] = instruction;
|
||||
#if SPINE_TRIANGLECHECK
|
||||
this.hasActiveClipping |= instruction.hasClipping;
|
||||
submeshesItems[i].rawFirstVertexIndex = runningVertexCount; // Ensure current instructions have correct cached values.
|
||||
runningVertexCount += instruction.rawVertexCount; // vertexCount will also be used for the rest of this method.
|
||||
#endif
|
||||
}
|
||||
#if SPINE_TRIANGLECHECK
|
||||
this.rawVertexCount = runningVertexCount;
|
||||
|
||||
// assumption: instructions are contiguous. start and end are valid within instructions.
|
||||
|
||||
int startSlot = instructionsItems[startSubmesh].startSlot;
|
||||
int endSlot = instructionsItems[endSubmesh - 1].endSlot;
|
||||
attachments.Clear(false);
|
||||
int attachmentCount = endSlot - startSlot;
|
||||
attachments.Resize(attachmentCount);
|
||||
var attachmentsItems = attachments.Items;
|
||||
|
||||
var drawOrderItems = instructionsItems[0].skeleton.drawOrder.Items;
|
||||
for (int i = 0; i < attachmentCount; i++) {
|
||||
Slot slot = drawOrderItems[startSlot + i];
|
||||
if (!slot.bone.active) continue;
|
||||
attachmentsItems[i] = slot.attachment;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Set (SkeletonRendererInstruction other) {
|
||||
this.immutableTriangles = other.immutableTriangles;
|
||||
|
||||
#if SPINE_TRIANGLECHECK
|
||||
this.hasActiveClipping = other.hasActiveClipping;
|
||||
this.rawVertexCount = other.rawVertexCount;
|
||||
this.attachments.Clear(false);
|
||||
this.attachments.EnsureCapacity(other.attachments.Capacity);
|
||||
this.attachments.Count = other.attachments.Count;
|
||||
other.attachments.CopyTo(this.attachments.Items);
|
||||
#endif
|
||||
|
||||
this.submeshInstructions.Clear(false);
|
||||
this.submeshInstructions.EnsureCapacity(other.submeshInstructions.Capacity);
|
||||
this.submeshInstructions.Count = other.submeshInstructions.Count;
|
||||
other.submeshInstructions.CopyTo(this.submeshInstructions.Items);
|
||||
}
|
||||
|
||||
public static bool GeometryNotEqual (SkeletonRendererInstruction a, SkeletonRendererInstruction b) {
|
||||
#if SPINE_TRIANGLECHECK
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if (a.hasActiveClipping || b.hasActiveClipping) return true; // Triangles are unpredictable when clipping is active.
|
||||
|
||||
// Everything below assumes the raw vertex and triangle counts were used. (ie, no clipping was done)
|
||||
if (a.rawVertexCount != b.rawVertexCount) return true;
|
||||
|
||||
if (a.immutableTriangles != b.immutableTriangles) return true;
|
||||
|
||||
int attachmentCountB = b.attachments.Count;
|
||||
if (a.attachments.Count != attachmentCountB) return true; // Bounds check for the looped storedAttachments count below.
|
||||
|
||||
// Submesh count changed
|
||||
int submeshCountA = a.submeshInstructions.Count;
|
||||
int submeshCountB = b.submeshInstructions.Count;
|
||||
if (submeshCountA != submeshCountB) return true;
|
||||
|
||||
// Submesh Instruction mismatch
|
||||
var submeshInstructionsItemsA = a.submeshInstructions.Items;
|
||||
var submeshInstructionsItemsB = b.submeshInstructions.Items;
|
||||
|
||||
var attachmentsA = a.attachments.Items;
|
||||
var attachmentsB = b.attachments.Items;
|
||||
for (int i = 0; i < attachmentCountB; i++)
|
||||
if (!System.Object.ReferenceEquals(attachmentsA[i], attachmentsB[i])) return true;
|
||||
|
||||
for (int i = 0; i < submeshCountB; i++) {
|
||||
var submeshA = submeshInstructionsItemsA[i];
|
||||
var submeshB = submeshInstructionsItemsB[i];
|
||||
|
||||
if (!(
|
||||
submeshA.rawVertexCount == submeshB.rawVertexCount &&
|
||||
submeshA.startSlot == submeshB.startSlot &&
|
||||
submeshA.endSlot == submeshB.endSlot
|
||||
&& submeshA.rawTriangleCount == submeshB.rawTriangleCount &&
|
||||
submeshA.rawFirstVertexIndex == submeshB.rawFirstVertexIndex
|
||||
))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
#else
|
||||
// In normal immutable triangle use, immutableTriangles will be initially false, forcing the smartmesh to update the first time but never again after that, unless there was an immutableTriangles flag mismatch..
|
||||
if (a.immutableTriangles || b.immutableTriangles)
|
||||
return (a.immutableTriangles != b.immutableTriangles);
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d07866ade25bd0b44a7bb1d59bacf4cb
|
||||
timeCreated: 1563322425
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated January 1, 2020. Replaces all prior versions.
|
||||
*
|
||||
* Copyright (c) 2013-2020, Esoteric Software LLC
|
||||
*
|
||||
* Integration of the Spine Runtimes into software or otherwise creating
|
||||
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||
* http://esotericsoftware.com/spine-editor-license
|
||||
*
|
||||
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||
* "Products"), provided that each user of the Products must obtain their own
|
||||
* Spine Editor license and redistribution of the Products in any form must
|
||||
* include this license and copyright notice.
|
||||
*
|
||||
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*****************************************************************************/
|
||||
|
||||
// Not for optimization. Do not disable.
|
||||
#define SPINE_TRIANGLECHECK // Avoid calling SetTriangles at the cost of checking for mesh differences (vertex counts, memberwise attachment list compare) every frame.
|
||||
//#define SPINE_DEBUG
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spine.Unity {
|
||||
public static class SpineMesh {
|
||||
internal const HideFlags MeshHideflags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
|
||||
|
||||
/// <summary>Factory method for creating a new mesh for use in Spine components. This can be called in field initializers.</summary>
|
||||
public static Mesh NewSkeletonMesh () {
|
||||
var m = new Mesh();
|
||||
m.MarkDynamic();
|
||||
m.name = "Skeleton Mesh";
|
||||
m.hideFlags = SpineMesh.MeshHideflags;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Instructions for how to generate a mesh or submesh: "Render this skeleton's slots: start slot, up to but not including endSlot, using this material."</summary>
|
||||
public struct SubmeshInstruction {
|
||||
public Skeleton skeleton;
|
||||
public int startSlot;
|
||||
public int endSlot;
|
||||
public Material material;
|
||||
|
||||
public bool forceSeparate;
|
||||
public int preActiveClippingSlotSource;
|
||||
|
||||
#if SPINE_TRIANGLECHECK
|
||||
// Cached values because they are determined in the process of generating instructions,
|
||||
// but could otherwise be pulled from accessing attachments, checking materials and counting tris and verts.
|
||||
public int rawTriangleCount;
|
||||
public int rawVertexCount;
|
||||
public int rawFirstVertexIndex;
|
||||
public bool hasClipping;
|
||||
#endif
|
||||
|
||||
/// <summary>The number of slots in this SubmeshInstruction's range. Not necessarily the number of attachments.</summary>
|
||||
public int SlotCount { get { return endSlot - startSlot; } }
|
||||
|
||||
public override string ToString () {
|
||||
return
|
||||
string.Format("[SubmeshInstruction: slots {0} to {1}. (Material){2}. preActiveClippingSlotSource:{3}]",
|
||||
startSlot,
|
||||
endSlot - 1,
|
||||
material == null ? "<none>" : material.name,
|
||||
preActiveClippingSlotSource
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f834c8746034db645a52a9506ff1de89
|
||||
timeCreated: 1455416715
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
9
Assets/Spine/Runtime/spine-unity/Modules.meta
Normal file
9
Assets/Spine/Runtime/spine-unity/Modules.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7935399edad9a14bb5708cf59c94b67
|
||||
folderAsset: yes
|
||||
timeCreated: 1455489260
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
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