e
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a60dd41ef13d98647b9f963089feb7b0
|
||||
folderAsset: yes
|
||||
timeCreated: 1456265151
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,635 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
public class Skeleton {
|
||||
internal SkeletonData data;
|
||||
internal ExposedList<Bone> bones;
|
||||
internal ExposedList<Slot> slots;
|
||||
internal ExposedList<Slot> drawOrder;
|
||||
internal ExposedList<IkConstraint> ikConstraints;
|
||||
internal ExposedList<TransformConstraint> transformConstraints;
|
||||
internal ExposedList<PathConstraint> pathConstraints;
|
||||
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
|
||||
internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
|
||||
internal Skin skin;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float time;
|
||||
private float scaleX = 1, scaleY = 1;
|
||||
internal float x, y;
|
||||
|
||||
public SkeletonData Data { get { return data; } }
|
||||
public ExposedList<Bone> Bones { get { return bones; } }
|
||||
public ExposedList<IUpdatable> UpdateCacheList { get { return updateCache; } }
|
||||
public ExposedList<Slot> Slots { get { return slots; } }
|
||||
public ExposedList<Slot> DrawOrder { get { return drawOrder; } }
|
||||
public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
|
||||
public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
|
||||
public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
|
||||
public Skin Skin { get { return skin; } set { SetSkin(value); } }
|
||||
public float R { get { return r; } set { r = value; } }
|
||||
public float G { get { return g; } set { g = value; } }
|
||||
public float B { get { return b; } set { b = value; } }
|
||||
public float A { get { return a; } set { a = value; } }
|
||||
public float Time { get { return time; } set { time = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY * (Bone.yDown ? -1 : 1); } set { scaleY = value; } }
|
||||
|
||||
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
|
||||
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
|
||||
|
||||
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
|
||||
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
|
||||
|
||||
public Bone RootBone {
|
||||
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
||||
}
|
||||
|
||||
public Skeleton (SkeletonData data) {
|
||||
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
|
||||
this.data = data;
|
||||
|
||||
bones = new ExposedList<Bone>(data.bones.Count);
|
||||
foreach (BoneData boneData in data.bones) {
|
||||
Bone bone;
|
||||
if (boneData.parent == null) {
|
||||
bone = new Bone(boneData, this, null);
|
||||
} else {
|
||||
Bone parent = bones.Items[boneData.parent.index];
|
||||
bone = new Bone(boneData, this, parent);
|
||||
parent.children.Add(bone);
|
||||
}
|
||||
bones.Add(bone);
|
||||
}
|
||||
|
||||
slots = new ExposedList<Slot>(data.slots.Count);
|
||||
drawOrder = new ExposedList<Slot>(data.slots.Count);
|
||||
foreach (SlotData slotData in data.slots) {
|
||||
Bone bone = bones.Items[slotData.boneData.index];
|
||||
Slot slot = new Slot(slotData, bone);
|
||||
slots.Add(slot);
|
||||
drawOrder.Add(slot);
|
||||
}
|
||||
|
||||
ikConstraints = new ExposedList<IkConstraint>(data.ikConstraints.Count);
|
||||
foreach (IkConstraintData ikConstraintData in data.ikConstraints)
|
||||
ikConstraints.Add(new IkConstraint(ikConstraintData, this));
|
||||
|
||||
transformConstraints = new ExposedList<TransformConstraint>(data.transformConstraints.Count);
|
||||
foreach (TransformConstraintData transformConstraintData in data.transformConstraints)
|
||||
transformConstraints.Add(new TransformConstraint(transformConstraintData, this));
|
||||
|
||||
pathConstraints = new ExposedList<PathConstraint> (data.pathConstraints.Count);
|
||||
foreach (PathConstraintData pathConstraintData in data.pathConstraints)
|
||||
pathConstraints.Add(new PathConstraint(pathConstraintData, this));
|
||||
|
||||
UpdateCache();
|
||||
UpdateWorldTransform();
|
||||
}
|
||||
|
||||
/// <summary>Caches information about bones and constraints. Must be called if the <see cref="Skin"/> is modified or if bones, constraints, or
|
||||
/// constraints, or weighted path attachments are added or removed.</summary>
|
||||
public void UpdateCache () {
|
||||
var updateCache = this.updateCache;
|
||||
updateCache.Clear();
|
||||
this.updateCacheReset.Clear();
|
||||
|
||||
int boneCount = this.bones.Items.Length;
|
||||
var bones = this.bones;
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone bone = bones.Items[i];
|
||||
bone.sorted = bone.data.skinRequired;
|
||||
bone.active = !bone.sorted;
|
||||
}
|
||||
if (skin != null) {
|
||||
Object[] skinBones = skin.bones.Items;
|
||||
for (int i = 0, n = skin.bones.Count; i < n; i++) {
|
||||
Bone bone = (Bone)bones.Items[((BoneData)skinBones[i]).index];
|
||||
do {
|
||||
bone.sorted = false;
|
||||
bone.active = true;
|
||||
bone = bone.parent;
|
||||
} while (bone != null);
|
||||
}
|
||||
}
|
||||
|
||||
int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count;
|
||||
var ikConstraints = this.ikConstraints;
|
||||
var transformConstraints = this.transformConstraints;
|
||||
var pathConstraints = this.pathConstraints;
|
||||
int constraintCount = ikCount + transformCount + pathCount;
|
||||
//outer:
|
||||
for (int i = 0; i < constraintCount; i++) {
|
||||
for (int ii = 0; ii < ikCount; ii++) {
|
||||
IkConstraint constraint = ikConstraints.Items[ii];
|
||||
if (constraint.data.order == i) {
|
||||
SortIkConstraint(constraint);
|
||||
goto continue_outer; //continue outer;
|
||||
}
|
||||
}
|
||||
for (int ii = 0; ii < transformCount; ii++) {
|
||||
TransformConstraint constraint = transformConstraints.Items[ii];
|
||||
if (constraint.data.order == i) {
|
||||
SortTransformConstraint(constraint);
|
||||
goto continue_outer; //continue outer;
|
||||
}
|
||||
}
|
||||
for (int ii = 0; ii < pathCount; ii++) {
|
||||
PathConstraint constraint = pathConstraints.Items[ii];
|
||||
if (constraint.data.order == i) {
|
||||
SortPathConstraint(constraint);
|
||||
goto continue_outer; //continue outer;
|
||||
}
|
||||
}
|
||||
continue_outer: {}
|
||||
}
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(bones.Items[i]);
|
||||
}
|
||||
|
||||
private void SortIkConstraint (IkConstraint constraint) {
|
||||
constraint.active = constraint.target.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
Bone target = constraint.target;
|
||||
SortBone(target);
|
||||
|
||||
var constrained = constraint.bones;
|
||||
Bone parent = constrained.Items[0];
|
||||
SortBone(parent);
|
||||
|
||||
if (constrained.Count > 1) {
|
||||
Bone child = constrained.Items[constrained.Count - 1];
|
||||
if (!updateCache.Contains(child))
|
||||
updateCacheReset.Add(child);
|
||||
}
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
SortReset(parent.children);
|
||||
constrained.Items[constrained.Count - 1].sorted = true;
|
||||
}
|
||||
|
||||
private void SortPathConstraint (PathConstraint constraint) {
|
||||
constraint.active = constraint.target.bone.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
Slot slot = constraint.target;
|
||||
int slotIndex = slot.data.index;
|
||||
Bone slotBone = slot.bone;
|
||||
if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
|
||||
if (data.defaultSkin != null && data.defaultSkin != skin)
|
||||
SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
|
||||
|
||||
Attachment attachment = slot.attachment;
|
||||
if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
|
||||
|
||||
var constrained = constraint.bones;
|
||||
int boneCount = constrained.Count;
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(constrained.Items[i]);
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortReset(constrained.Items[i].children);
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
constrained.Items[i].sorted = true;
|
||||
}
|
||||
|
||||
private void SortTransformConstraint (TransformConstraint constraint) {
|
||||
constraint.active = constraint.target.active
|
||||
&& (!constraint.data.skinRequired || (skin != null && skin.constraints.Contains(constraint.data)));
|
||||
if (!constraint.active) return;
|
||||
|
||||
SortBone(constraint.target);
|
||||
|
||||
var constrained = constraint.bones;
|
||||
int boneCount = constrained.Count;
|
||||
if (constraint.data.local) {
|
||||
for (int i = 0; i < boneCount; i++) {
|
||||
Bone child = constrained.Items[i];
|
||||
SortBone(child.parent);
|
||||
if (!updateCache.Contains(child)) updateCacheReset.Add(child);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortBone(constrained.Items[i]);
|
||||
}
|
||||
|
||||
updateCache.Add(constraint);
|
||||
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
SortReset(constrained.Items[i].children);
|
||||
for (int i = 0; i < boneCount; i++)
|
||||
constrained.Items[i].sorted = true;
|
||||
}
|
||||
|
||||
private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
|
||||
foreach (var entryObj in skin.Attachments.Keys) {
|
||||
var entry = (Skin.SkinEntry)entryObj;
|
||||
if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone);
|
||||
}
|
||||
}
|
||||
|
||||
private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
|
||||
if (!(attachment is PathAttachment)) return;
|
||||
int[] pathBones = ((PathAttachment)attachment).bones;
|
||||
if (pathBones == null)
|
||||
SortBone(slotBone);
|
||||
else {
|
||||
var bones = this.bones;
|
||||
for (int i = 0, n = pathBones.Length; i < n;) {
|
||||
int nn = pathBones[i++];
|
||||
nn += i;
|
||||
while (i < nn)
|
||||
SortBone(bones.Items[pathBones[i++]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SortBone (Bone bone) {
|
||||
if (bone.sorted) return;
|
||||
Bone parent = bone.parent;
|
||||
if (parent != null) SortBone(parent);
|
||||
bone.sorted = true;
|
||||
updateCache.Add(bone);
|
||||
}
|
||||
|
||||
private static void SortReset (ExposedList<Bone> bones) {
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bonesItems[i];
|
||||
if (!bone.active) continue;
|
||||
if (bone.sorted) SortReset(bone.children);
|
||||
bone.sorted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Updates the world transform for each bone and applies constraints.</summary>
|
||||
public void UpdateWorldTransform () {
|
||||
var updateCacheReset = this.updateCacheReset;
|
||||
var updateCacheResetItems = updateCacheReset.Items;
|
||||
for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
|
||||
Bone bone = updateCacheResetItems[i];
|
||||
bone.ax = bone.x;
|
||||
bone.ay = bone.y;
|
||||
bone.arotation = bone.rotation;
|
||||
bone.ascaleX = bone.scaleX;
|
||||
bone.ascaleY = bone.scaleY;
|
||||
bone.ashearX = bone.shearX;
|
||||
bone.ashearY = bone.shearY;
|
||||
bone.appliedValid = true;
|
||||
}
|
||||
var updateItems = this.updateCache.Items;
|
||||
for (int i = 0, n = updateCache.Count; i < n; i++)
|
||||
updateItems[i].Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
|
||||
/// all constraints.
|
||||
/// </summary>
|
||||
public void UpdateWorldTransform (Bone parent) {
|
||||
// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
|
||||
// before the constraint, 2) the constraint only needs to access the applied local transform, and 3) the constraint calls
|
||||
// updateWorldTransform.
|
||||
var updateCacheReset = this.updateCacheReset;
|
||||
var updateCacheResetItems = updateCacheReset.Items;
|
||||
for (int i = 0, n = updateCacheReset.Count; i < n; i++) {
|
||||
Bone bone = updateCacheResetItems[i];
|
||||
bone.ax = bone.x;
|
||||
bone.ay = bone.y;
|
||||
bone.arotation = bone.rotation;
|
||||
bone.ascaleX = bone.scaleX;
|
||||
bone.ascaleY = bone.scaleY;
|
||||
bone.ashearX = bone.shearX;
|
||||
bone.ashearY = bone.shearY;
|
||||
bone.appliedValid = true;
|
||||
}
|
||||
|
||||
// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
|
||||
Bone rootBone = this.RootBone;
|
||||
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||
rootBone.worldX = pa * x + pb * y + parent.worldX;
|
||||
rootBone.worldY = pc * x + pd * y + parent.worldY;
|
||||
|
||||
float rotationY = rootBone.rotation + 90 + rootBone.shearY;
|
||||
float la = MathUtils.CosDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
||||
float lb = MathUtils.CosDeg(rotationY) * rootBone.scaleY;
|
||||
float lc = MathUtils.SinDeg(rootBone.rotation + rootBone.shearX) * rootBone.scaleX;
|
||||
float ld = MathUtils.SinDeg(rotationY) * rootBone.scaleY;
|
||||
rootBone.a = (pa * la + pb * lc) * scaleX;
|
||||
rootBone.b = (pa * lb + pb * ld) * scaleX;
|
||||
rootBone.c = (pc * la + pd * lc) * scaleY;
|
||||
rootBone.d = (pc * lb + pd * ld) * scaleY;
|
||||
|
||||
// Update everything except root bone.
|
||||
var updateCache = this.updateCache;
|
||||
var updateCacheItems = updateCache.Items;
|
||||
for (int i = 0, n = updateCache.Count; i < n; i++) {
|
||||
var updatable = updateCacheItems[i];
|
||||
if (updatable != rootBone)
|
||||
updatable.Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets the bones, constraints, and slots to their setup pose values.</summary>
|
||||
public void SetToSetupPose () {
|
||||
SetBonesToSetupPose();
|
||||
SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
/// <summary>Sets the bones and constraints to their setup pose values.</summary>
|
||||
public void SetBonesToSetupPose () {
|
||||
var bonesItems = this.bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
bonesItems[i].SetToSetupPose();
|
||||
|
||||
var ikConstraintsItems = this.ikConstraints.Items;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint constraint = ikConstraintsItems[i];
|
||||
constraint.mix = constraint.data.mix;
|
||||
constraint.softness = constraint.data.softness;
|
||||
constraint.bendDirection = constraint.data.bendDirection;
|
||||
constraint.compress = constraint.data.compress;
|
||||
constraint.stretch = constraint.data.stretch;
|
||||
}
|
||||
|
||||
var transformConstraintsItems = this.transformConstraints.Items;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint constraint = transformConstraintsItems[i];
|
||||
TransformConstraintData constraintData = constraint.data;
|
||||
constraint.rotateMix = constraintData.rotateMix;
|
||||
constraint.translateMix = constraintData.translateMix;
|
||||
constraint.scaleMix = constraintData.scaleMix;
|
||||
constraint.shearMix = constraintData.shearMix;
|
||||
}
|
||||
|
||||
var pathConstraintItems = this.pathConstraints.Items;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraintItems[i];
|
||||
PathConstraintData constraintData = constraint.data;
|
||||
constraint.position = constraintData.position;
|
||||
constraint.spacing = constraintData.spacing;
|
||||
constraint.rotateMix = constraintData.rotateMix;
|
||||
constraint.translateMix = constraintData.translateMix;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSlotsToSetupPose () {
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
drawOrder.Clear();
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
drawOrder.Add(slotsItems[i]);
|
||||
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
slotsItems[i].SetToSetupPose();
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Bone FindBone (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
Bone bone = bonesItems[i];
|
||||
if (bone.data.name == boneName) return bone;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>-1 if the bone was not found.</returns>
|
||||
public int FindBoneIndex (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
if (bonesItems[i].data.name == boneName) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Slot FindSlot (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
Slot slot = slotsItems[i];
|
||||
if (slot.data.name == slotName) return slot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>-1 if the bone was not found.</returns>
|
||||
public int FindSlotIndex (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
var slots = this.slots;
|
||||
var slotsItems = slots.Items;
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
if (slotsItems[i].data.name.Equals(slotName)) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>Sets a skin by name (see SetSkin).</summary>
|
||||
public void SetSkin (string skinName) {
|
||||
Skin foundSkin = data.FindSkin(skinName);
|
||||
if (foundSkin == null) throw new ArgumentException("Skin not found: " + skinName, "skinName");
|
||||
SetSkin(foundSkin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Sets the skin used to look up attachments before looking in the <see cref="SkeletonData.DefaultSkin"/>. If the
|
||||
/// skin is changed, <see cref="UpdateCache()"/> is called.
|
||||
/// </para>
|
||||
/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached.
|
||||
/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.
|
||||
/// </para>
|
||||
/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling
|
||||
/// <see cref="Skeleton.SetSlotsToSetupPose()"/>.
|
||||
/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the
|
||||
/// skeleton is rendered to allow any attachment keys in the current animation(s) to hide or show attachments from the new skin.</para>
|
||||
/// </summary>
|
||||
/// <param name="newSkin">May be null.</param>
|
||||
public void SetSkin (Skin newSkin) {
|
||||
if (newSkin == skin) return;
|
||||
if (newSkin != null) {
|
||||
if (skin != null)
|
||||
newSkin.AttachAll(this, skin);
|
||||
else {
|
||||
ExposedList<Slot> slots = this.slots;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
Slot slot = slots.Items[i];
|
||||
string name = slot.data.attachmentName;
|
||||
if (name != null) {
|
||||
Attachment attachment = newSkin.GetAttachment(i, name);
|
||||
if (attachment != null) slot.Attachment = attachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skin = newSkin;
|
||||
UpdateCache();
|
||||
}
|
||||
|
||||
/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary>
|
||||
/// <returns>May be null.</returns>
|
||||
public Attachment GetAttachment (string slotName, string attachmentName) {
|
||||
return GetAttachment(data.FindSlotIndex(slotName), attachmentName);
|
||||
}
|
||||
|
||||
/// <summary>Finds an attachment by looking in the skin and skeletonData.defaultSkin using the slot index and attachment name.First the skin is checked and if the attachment was not found, the default skin is checked.</summary>
|
||||
/// <returns>May be null.</returns>
|
||||
public Attachment GetAttachment (int slotIndex, string attachmentName) {
|
||||
if (attachmentName == null) throw new ArgumentNullException("attachmentName", "attachmentName cannot be null.");
|
||||
if (skin != null) {
|
||||
Attachment attachment = skin.GetAttachment(slotIndex, attachmentName);
|
||||
if (attachment != null) return attachment;
|
||||
}
|
||||
return data.defaultSkin != null ? data.defaultSkin.GetAttachment(slotIndex, attachmentName) : null;
|
||||
}
|
||||
|
||||
/// <summary>A convenience method to set an attachment by finding the slot with FindSlot, finding the attachment with GetAttachment, then setting the slot's slot.Attachment.</summary>
|
||||
/// <param name="attachmentName">May be null to clear the slot's attachment.</param>
|
||||
public void SetAttachment (string slotName, string attachmentName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
ExposedList<Slot> slots = this.slots;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
Slot slot = slots.Items[i];
|
||||
if (slot.data.name == slotName) {
|
||||
Attachment attachment = null;
|
||||
if (attachmentName != null) {
|
||||
attachment = GetAttachment(i, attachmentName);
|
||||
if (attachment == null) throw new Exception("Attachment not found: " + attachmentName + ", for slot: " + slotName);
|
||||
}
|
||||
slot.Attachment = attachment;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Exception("Slot not found: " + slotName);
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public IkConstraint FindIkConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<IkConstraint> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraint ikConstraint = ikConstraints.Items[i];
|
||||
if (ikConstraint.data.name == constraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public TransformConstraint FindTransformConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraint transformConstraint = transformConstraints.Items[i];
|
||||
if (transformConstraint.data.Name == constraintName) return transformConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public PathConstraint FindPathConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraint constraint = pathConstraints.Items[i];
|
||||
if (constraint.data.Name.Equals(constraintName)) return constraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Update (float delta) {
|
||||
time += delta;
|
||||
}
|
||||
|
||||
/// <summary>Returns the axis aligned bounding box (AABB) of the region and mesh attachments for the current pose.</summary>
|
||||
/// <param name="x">The horizontal distance between the skeleton origin and the left side of the AABB.</param>
|
||||
/// <param name="y">The vertical distance between the skeleton origin and the bottom side of the AABB.</param>
|
||||
/// <param name="width">The width of the AABB</param>
|
||||
/// <param name="height">The height of the AABB.</param>
|
||||
/// <param name="vertexBuffer">Reference to hold a float[]. May be a null reference. This method will assign it a new float[] with the appropriate size as needed.</param>
|
||||
public void GetBounds (out float x, out float y, out float width, out float height, ref float[] vertexBuffer) {
|
||||
float[] temp = vertexBuffer;
|
||||
temp = temp ?? new float[8];
|
||||
var drawOrderItems = this.drawOrder.Items;
|
||||
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
|
||||
for (int i = 0, n = drawOrderItems.Length; i < n; i++) {
|
||||
Slot slot = drawOrderItems[i];
|
||||
if (!slot.bone.active) continue;
|
||||
int verticesLength = 0;
|
||||
float[] vertices = null;
|
||||
Attachment attachment = slot.attachment;
|
||||
var regionAttachment = attachment as RegionAttachment;
|
||||
if (regionAttachment != null) {
|
||||
verticesLength = 8;
|
||||
vertices = temp;
|
||||
if (vertices.Length < 8) vertices = temp = new float[8];
|
||||
regionAttachment.ComputeWorldVertices(slot.bone, temp, 0);
|
||||
} else {
|
||||
var meshAttachment = attachment as MeshAttachment;
|
||||
if (meshAttachment != null) {
|
||||
MeshAttachment mesh = meshAttachment;
|
||||
verticesLength = mesh.WorldVerticesLength;
|
||||
vertices = temp;
|
||||
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
|
||||
mesh.ComputeWorldVertices(slot, 0, verticesLength, temp, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices != null) {
|
||||
for (int ii = 0; ii < verticesLength; ii += 2) {
|
||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||
minX = Math.Min(minX, vx);
|
||||
minY = Math.Min(minY, vy);
|
||||
maxX = Math.Max(maxX, vx);
|
||||
maxY = Math.Max(maxY, vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
x = minX;
|
||||
y = minY;
|
||||
width = maxX - minX;
|
||||
height = maxY - minY;
|
||||
vertexBuffer = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12ac3c1c7546be24fb9625d3c850619d
|
||||
timeCreated: 1456265153
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,997 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
|
||||
#define IS_UNITY
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if WINDOWS_STOREAPP
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
#endif
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonBinary {
|
||||
public const int BONE_ROTATE = 0;
|
||||
public const int BONE_TRANSLATE = 1;
|
||||
public const int BONE_SCALE = 2;
|
||||
public const int BONE_SHEAR = 3;
|
||||
|
||||
public const int SLOT_ATTACHMENT = 0;
|
||||
public const int SLOT_COLOR = 1;
|
||||
public const int SLOT_TWO_COLOR = 2;
|
||||
|
||||
public const int PATH_POSITION = 0;
|
||||
public const int PATH_SPACING = 1;
|
||||
public const int PATH_MIX = 2;
|
||||
|
||||
public const int CURVE_LINEAR = 0;
|
||||
public const int CURVE_STEPPED = 1;
|
||||
public const int CURVE_BEZIER = 2;
|
||||
|
||||
public float Scale { get; set; }
|
||||
|
||||
private AttachmentLoader attachmentLoader;
|
||||
private List<SkeletonJson.LinkedMesh> linkedMeshes = new List<SkeletonJson.LinkedMesh>();
|
||||
|
||||
public SkeletonBinary (params Atlas[] atlasArray)
|
||||
: this(new AtlasAttachmentLoader(atlasArray)) {
|
||||
}
|
||||
|
||||
public SkeletonBinary (AttachmentLoader attachmentLoader) {
|
||||
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader");
|
||||
this.attachmentLoader = attachmentLoader;
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !ISUNITY && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
using (var input = new BufferedStream(await folder.GetFileAsync(path).AsTask().ConfigureAwait(false))) {
|
||||
SkeletonData skeletonData = ReadSkeletonData(input);
|
||||
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
|
||||
return skeletonData;
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonData ReadSkeletonData (String path) {
|
||||
return this.ReadFile(path).Result;
|
||||
}
|
||||
#else
|
||||
public SkeletonData ReadSkeletonData (String path) {
|
||||
#if WINDOWS_PHONE
|
||||
using (var input = new BufferedStream(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
|
||||
#else
|
||||
using (var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
||||
#endif
|
||||
SkeletonData skeletonData = ReadSkeletonData(input);
|
||||
skeletonData.name = Path.GetFileNameWithoutExtension(path);
|
||||
return skeletonData;
|
||||
}
|
||||
}
|
||||
#endif // WINDOWS_STOREAPP
|
||||
|
||||
public static readonly TransformMode[] TransformModeValues = {
|
||||
TransformMode.Normal,
|
||||
TransformMode.OnlyTranslation,
|
||||
TransformMode.NoRotationOrReflection,
|
||||
TransformMode.NoScale,
|
||||
TransformMode.NoScaleOrReflection
|
||||
};
|
||||
|
||||
/// <summary>Returns the version string of binary skeleton data.</summary>
|
||||
public static string GetVersionString (Stream file) {
|
||||
if (file == null) throw new ArgumentNullException("file");
|
||||
|
||||
SkeletonInput input = new SkeletonInput(file);
|
||||
return input.GetVersionString();
|
||||
}
|
||||
|
||||
public SkeletonData ReadSkeletonData (Stream file) {
|
||||
if (file == null) throw new ArgumentNullException("file");
|
||||
float scale = Scale;
|
||||
|
||||
var skeletonData = new SkeletonData();
|
||||
SkeletonInput input = new SkeletonInput(file);
|
||||
|
||||
skeletonData.hash = input.ReadString();
|
||||
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
|
||||
skeletonData.version = input.ReadString();
|
||||
if (skeletonData.version.Length == 0) skeletonData.version = null;
|
||||
if ("3.8.75" == skeletonData.version)
|
||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
skeletonData.x = input.ReadFloat();
|
||||
skeletonData.y = input.ReadFloat();
|
||||
skeletonData.width = input.ReadFloat();
|
||||
skeletonData.height = input.ReadFloat();
|
||||
|
||||
bool nonessential = input.ReadBoolean();
|
||||
|
||||
if (nonessential) {
|
||||
skeletonData.fps = input.ReadFloat();
|
||||
|
||||
skeletonData.imagesPath = input.ReadString();
|
||||
if (string.IsNullOrEmpty(skeletonData.imagesPath)) skeletonData.imagesPath = null;
|
||||
|
||||
skeletonData.audioPath = input.ReadString();
|
||||
if (string.IsNullOrEmpty(skeletonData.audioPath)) skeletonData.audioPath = null;
|
||||
}
|
||||
|
||||
int n;
|
||||
Object[] o;
|
||||
|
||||
// Strings.
|
||||
input.strings = new ExposedList<string>(n = input.ReadInt(true));
|
||||
o = input.strings.Resize(n).Items;
|
||||
for (int i = 0; i < n; i++)
|
||||
o[i] = input.ReadString();
|
||||
|
||||
// Bones.
|
||||
o = skeletonData.bones.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0; i < n; i++) {
|
||||
String name = input.ReadString();
|
||||
BoneData parent = i == 0 ? null : skeletonData.bones.Items[input.ReadInt(true)];
|
||||
BoneData data = new BoneData(i, name, parent);
|
||||
data.rotation = input.ReadFloat();
|
||||
data.x = input.ReadFloat() * scale;
|
||||
data.y = input.ReadFloat() * scale;
|
||||
data.scaleX = input.ReadFloat();
|
||||
data.scaleY = input.ReadFloat();
|
||||
data.shearX = input.ReadFloat();
|
||||
data.shearY = input.ReadFloat();
|
||||
data.length = input.ReadFloat() * scale;
|
||||
data.transformMode = TransformModeValues[input.ReadInt(true)];
|
||||
data.skinRequired = input.ReadBoolean();
|
||||
if (nonessential) input.ReadInt(); // Skip bone color.
|
||||
o[i] = data;
|
||||
}
|
||||
|
||||
// Slots.
|
||||
o = skeletonData.slots.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0; i < n; i++) {
|
||||
String slotName = input.ReadString();
|
||||
BoneData boneData = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
SlotData slotData = new SlotData(i, slotName, boneData);
|
||||
int color = input.ReadInt();
|
||||
slotData.r = ((color & 0xff000000) >> 24) / 255f;
|
||||
slotData.g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
slotData.b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
slotData.a = ((color & 0x000000ff)) / 255f;
|
||||
|
||||
int darkColor = input.ReadInt(); // 0x00rrggbb
|
||||
if (darkColor != -1) {
|
||||
slotData.hasSecondColor = true;
|
||||
slotData.r2 = ((darkColor & 0x00ff0000) >> 16) / 255f;
|
||||
slotData.g2 = ((darkColor & 0x0000ff00) >> 8) / 255f;
|
||||
slotData.b2 = ((darkColor & 0x000000ff)) / 255f;
|
||||
}
|
||||
|
||||
slotData.attachmentName = input.ReadStringRef();
|
||||
slotData.blendMode = (BlendMode)input.ReadInt(true);
|
||||
o[i] = slotData;
|
||||
}
|
||||
|
||||
// IK constraints.
|
||||
o = skeletonData.ikConstraints.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0, nn; i < n; i++) {
|
||||
IkConstraintData data = new IkConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
data.skinRequired = input.ReadBoolean();
|
||||
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
|
||||
for (int ii = 0; ii < nn; ii++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.mix = input.ReadFloat();
|
||||
data.softness = input.ReadFloat() * scale;
|
||||
data.bendDirection = input.ReadSByte();
|
||||
data.compress = input.ReadBoolean();
|
||||
data.stretch = input.ReadBoolean();
|
||||
data.uniform = input.ReadBoolean();
|
||||
o[i] = data;
|
||||
}
|
||||
|
||||
// Transform constraints.
|
||||
o = skeletonData.transformConstraints.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0, nn; i < n; i++) {
|
||||
TransformConstraintData data = new TransformConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
data.skinRequired = input.ReadBoolean();
|
||||
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
|
||||
for (int ii = 0; ii < nn; ii++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.local = input.ReadBoolean();
|
||||
data.relative = input.ReadBoolean();
|
||||
data.offsetRotation = input.ReadFloat();
|
||||
data.offsetX = input.ReadFloat() * scale;
|
||||
data.offsetY = input.ReadFloat() * scale;
|
||||
data.offsetScaleX = input.ReadFloat();
|
||||
data.offsetScaleY = input.ReadFloat();
|
||||
data.offsetShearY = input.ReadFloat();
|
||||
data.rotateMix = input.ReadFloat();
|
||||
data.translateMix = input.ReadFloat();
|
||||
data.scaleMix = input.ReadFloat();
|
||||
data.shearMix = input.ReadFloat();
|
||||
o[i] = data;
|
||||
}
|
||||
|
||||
// Path constraints
|
||||
o = skeletonData.pathConstraints.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0, nn; i < n; i++) {
|
||||
PathConstraintData data = new PathConstraintData(input.ReadString());
|
||||
data.order = input.ReadInt(true);
|
||||
data.skinRequired = input.ReadBoolean();
|
||||
Object[] bones = data.bones.Resize(nn = input.ReadInt(true)).Items;
|
||||
for (int ii = 0; ii < nn; ii++)
|
||||
bones[ii] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
data.target = skeletonData.slots.Items[input.ReadInt(true)];
|
||||
data.positionMode = (PositionMode)Enum.GetValues(typeof(PositionMode)).GetValue(input.ReadInt(true));
|
||||
data.spacingMode = (SpacingMode)Enum.GetValues(typeof(SpacingMode)).GetValue(input.ReadInt(true));
|
||||
data.rotateMode = (RotateMode)Enum.GetValues(typeof(RotateMode)).GetValue(input.ReadInt(true));
|
||||
data.offsetRotation = input.ReadFloat();
|
||||
data.position = input.ReadFloat();
|
||||
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
|
||||
data.spacing = input.ReadFloat();
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
|
||||
data.rotateMix = input.ReadFloat();
|
||||
data.translateMix = input.ReadFloat();
|
||||
o[i] = data;
|
||||
}
|
||||
|
||||
// Default skin.
|
||||
Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
|
||||
if (defaultSkin != null) {
|
||||
skeletonData.defaultSkin = defaultSkin;
|
||||
skeletonData.skins.Add(defaultSkin);
|
||||
}
|
||||
|
||||
// Skins.
|
||||
{
|
||||
int i = skeletonData.skins.Count;
|
||||
o = skeletonData.skins.Resize(n = i + input.ReadInt(true)).Items;
|
||||
for (; i < n; i++)
|
||||
o[i] = ReadSkin(input, skeletonData, false, nonessential);
|
||||
}
|
||||
|
||||
// Linked meshes.
|
||||
n = linkedMeshes.Count;
|
||||
for (int i = 0; i < n; i++) {
|
||||
SkeletonJson.LinkedMesh linkedMesh = linkedMeshes[i];
|
||||
Skin skin = linkedMesh.skin == null ? skeletonData.DefaultSkin : skeletonData.FindSkin(linkedMesh.skin);
|
||||
if (skin == null) throw new Exception("Skin not found: " + linkedMesh.skin);
|
||||
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
|
||||
linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh;
|
||||
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
|
||||
linkedMesh.mesh.UpdateUVs();
|
||||
}
|
||||
linkedMeshes.Clear();
|
||||
|
||||
// Events.
|
||||
o = skeletonData.events.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0; i < n; i++) {
|
||||
EventData data = new EventData(input.ReadStringRef());
|
||||
data.Int = input.ReadInt(false);
|
||||
data.Float = input.ReadFloat();
|
||||
data.String = input.ReadString();
|
||||
data.AudioPath = input.ReadString();
|
||||
if (data.AudioPath != null) {
|
||||
data.Volume = input.ReadFloat();
|
||||
data.Balance = input.ReadFloat();
|
||||
}
|
||||
o[i] = data;
|
||||
}
|
||||
|
||||
// Animations.
|
||||
o = skeletonData.animations.Resize(n = input.ReadInt(true)).Items;
|
||||
for (int i = 0; i < n; i++)
|
||||
o[i] = ReadAnimation(input.ReadString(), input, skeletonData);
|
||||
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
private Skin ReadSkin (SkeletonInput input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) {
|
||||
|
||||
Skin skin;
|
||||
int slotCount;
|
||||
|
||||
if (defaultSkin) {
|
||||
slotCount = input.ReadInt(true);
|
||||
if (slotCount == 0) return null;
|
||||
skin = new Skin("default");
|
||||
} else {
|
||||
skin = new Skin(input.ReadStringRef());
|
||||
Object[] bones = skin.bones.Resize(input.ReadInt(true)).Items;
|
||||
for (int i = 0, n = skin.bones.Count; i < n; i++)
|
||||
bones[i] = skeletonData.bones.Items[input.ReadInt(true)];
|
||||
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++)
|
||||
skin.constraints.Add(skeletonData.ikConstraints.Items[input.ReadInt(true)]);
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++)
|
||||
skin.constraints.Add(skeletonData.transformConstraints.Items[input.ReadInt(true)]);
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++)
|
||||
skin.constraints.Add(skeletonData.pathConstraints.Items[input.ReadInt(true)]);
|
||||
skin.constraints.TrimExcess();
|
||||
slotCount = input.ReadInt(true);
|
||||
}
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
String name = input.ReadStringRef();
|
||||
Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
|
||||
if (attachment != null) skin.SetAttachment(slotIndex, name, attachment);
|
||||
}
|
||||
}
|
||||
return skin;
|
||||
}
|
||||
|
||||
private Attachment ReadAttachment (SkeletonInput input, SkeletonData skeletonData, Skin skin, int slotIndex,
|
||||
String attachmentName, bool nonessential) {
|
||||
|
||||
float scale = Scale;
|
||||
|
||||
String name = input.ReadStringRef();
|
||||
if (name == null) name = attachmentName;
|
||||
|
||||
AttachmentType type = (AttachmentType)input.ReadByte();
|
||||
switch (type) {
|
||||
case AttachmentType.Region: {
|
||||
String path = input.ReadStringRef();
|
||||
float rotation = input.ReadFloat();
|
||||
float x = input.ReadFloat();
|
||||
float y = input.ReadFloat();
|
||||
float scaleX = input.ReadFloat();
|
||||
float scaleY = input.ReadFloat();
|
||||
float width = input.ReadFloat();
|
||||
float height = input.ReadFloat();
|
||||
int color = input.ReadInt();
|
||||
|
||||
if (path == null) path = name;
|
||||
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
|
||||
if (region == null) return null;
|
||||
region.Path = path;
|
||||
region.x = x * scale;
|
||||
region.y = y * scale;
|
||||
region.scaleX = scaleX;
|
||||
region.scaleY = scaleY;
|
||||
region.rotation = rotation;
|
||||
region.width = width * scale;
|
||||
region.height = height * scale;
|
||||
region.r = ((color & 0xff000000) >> 24) / 255f;
|
||||
region.g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
region.b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
region.a = ((color & 0x000000ff)) / 255f;
|
||||
region.UpdateOffset();
|
||||
return region;
|
||||
}
|
||||
case AttachmentType.Boundingbox: {
|
||||
int vertexCount = input.ReadInt(true);
|
||||
Vertices vertices = ReadVertices(input, vertexCount);
|
||||
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0; // Avoid unused local warning.
|
||||
|
||||
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
|
||||
if (box == null) return null;
|
||||
box.worldVerticesLength = vertexCount << 1;
|
||||
box.vertices = vertices.vertices;
|
||||
box.bones = vertices.bones;
|
||||
// skipped porting: if (nonessential) Color.rgba8888ToColor(box.getColor(), color);
|
||||
return box;
|
||||
}
|
||||
case AttachmentType.Mesh: {
|
||||
String path = input.ReadStringRef();
|
||||
int color = input.ReadInt();
|
||||
int vertexCount = input.ReadInt(true);
|
||||
float[] uvs = ReadFloatArray(input, vertexCount << 1, 1);
|
||||
int[] triangles = ReadShortArray(input);
|
||||
Vertices vertices = ReadVertices(input, vertexCount);
|
||||
int hullLength = input.ReadInt(true);
|
||||
int[] edges = null;
|
||||
float width = 0, height = 0;
|
||||
if (nonessential) {
|
||||
edges = ReadShortArray(input);
|
||||
width = input.ReadFloat();
|
||||
height = input.ReadFloat();
|
||||
}
|
||||
|
||||
if (path == null) path = name;
|
||||
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
|
||||
if (mesh == null) return null;
|
||||
mesh.Path = path;
|
||||
mesh.r = ((color & 0xff000000) >> 24) / 255f;
|
||||
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
mesh.a = ((color & 0x000000ff)) / 255f;
|
||||
mesh.bones = vertices.bones;
|
||||
mesh.vertices = vertices.vertices;
|
||||
mesh.WorldVerticesLength = vertexCount << 1;
|
||||
mesh.triangles = triangles;
|
||||
mesh.regionUVs = uvs;
|
||||
mesh.UpdateUVs();
|
||||
mesh.HullLength = hullLength << 1;
|
||||
if (nonessential) {
|
||||
mesh.Edges = edges;
|
||||
mesh.Width = width * scale;
|
||||
mesh.Height = height * scale;
|
||||
}
|
||||
return mesh;
|
||||
}
|
||||
case AttachmentType.Linkedmesh: {
|
||||
String path = input.ReadStringRef();
|
||||
int color = input.ReadInt();
|
||||
String skinName = input.ReadStringRef();
|
||||
String parent = input.ReadStringRef();
|
||||
bool inheritDeform = input.ReadBoolean();
|
||||
float width = 0, height = 0;
|
||||
if (nonessential) {
|
||||
width = input.ReadFloat();
|
||||
height = input.ReadFloat();
|
||||
}
|
||||
|
||||
if (path == null) path = name;
|
||||
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
|
||||
if (mesh == null) return null;
|
||||
mesh.Path = path;
|
||||
mesh.r = ((color & 0xff000000) >> 24) / 255f;
|
||||
mesh.g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
mesh.b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
mesh.a = ((color & 0x000000ff)) / 255f;
|
||||
if (nonessential) {
|
||||
mesh.Width = width * scale;
|
||||
mesh.Height = height * scale;
|
||||
}
|
||||
linkedMeshes.Add(new SkeletonJson.LinkedMesh(mesh, skinName, slotIndex, parent, inheritDeform));
|
||||
return mesh;
|
||||
}
|
||||
case AttachmentType.Path: {
|
||||
bool closed = input.ReadBoolean();
|
||||
bool constantSpeed = input.ReadBoolean();
|
||||
int vertexCount = input.ReadInt(true);
|
||||
Vertices vertices = ReadVertices(input, vertexCount);
|
||||
float[] lengths = new float[vertexCount / 3];
|
||||
for (int i = 0, n = lengths.Length; i < n; i++)
|
||||
lengths[i] = input.ReadFloat() * scale;
|
||||
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
|
||||
|
||||
PathAttachment path = attachmentLoader.NewPathAttachment(skin, name);
|
||||
if (path == null) return null;
|
||||
path.closed = closed;
|
||||
path.constantSpeed = constantSpeed;
|
||||
path.worldVerticesLength = vertexCount << 1;
|
||||
path.vertices = vertices.vertices;
|
||||
path.bones = vertices.bones;
|
||||
path.lengths = lengths;
|
||||
// skipped porting: if (nonessential) Color.rgba8888ToColor(path.getColor(), color);
|
||||
return path;
|
||||
}
|
||||
case AttachmentType.Point: {
|
||||
float rotation = input.ReadFloat();
|
||||
float x = input.ReadFloat();
|
||||
float y = input.ReadFloat();
|
||||
if (nonessential) input.ReadInt(); //int color = nonessential ? input.ReadInt() : 0;
|
||||
|
||||
PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
|
||||
if (point == null) return null;
|
||||
point.x = x * scale;
|
||||
point.y = y * scale;
|
||||
point.rotation = rotation;
|
||||
// skipped porting: if (nonessential) point.color = color;
|
||||
return point;
|
||||
}
|
||||
case AttachmentType.Clipping: {
|
||||
int endSlotIndex = input.ReadInt(true);
|
||||
int vertexCount = input.ReadInt(true);
|
||||
Vertices vertices = ReadVertices(input, vertexCount);
|
||||
if (nonessential) input.ReadInt();
|
||||
|
||||
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
|
||||
if (clip == null) return null;
|
||||
clip.EndSlot = skeletonData.slots.Items[endSlotIndex];
|
||||
clip.worldVerticesLength = vertexCount << 1;
|
||||
clip.vertices = vertices.vertices;
|
||||
clip.bones = vertices.bones;
|
||||
// skipped porting: if (nonessential) Color.rgba8888ToColor(clip.getColor(), color);
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Vertices ReadVertices (SkeletonInput input, int vertexCount) {
|
||||
float scale = Scale;
|
||||
int verticesLength = vertexCount << 1;
|
||||
Vertices vertices = new Vertices();
|
||||
if(!input.ReadBoolean()) {
|
||||
vertices.vertices = ReadFloatArray(input, verticesLength, scale);
|
||||
return vertices;
|
||||
}
|
||||
var weights = new ExposedList<float>(verticesLength * 3 * 3);
|
||||
var bonesArray = new ExposedList<int>(verticesLength * 3);
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
int boneCount = input.ReadInt(true);
|
||||
bonesArray.Add(boneCount);
|
||||
for (int ii = 0; ii < boneCount; ii++) {
|
||||
bonesArray.Add(input.ReadInt(true));
|
||||
weights.Add(input.ReadFloat() * scale);
|
||||
weights.Add(input.ReadFloat() * scale);
|
||||
weights.Add(input.ReadFloat());
|
||||
}
|
||||
}
|
||||
|
||||
vertices.vertices = weights.ToArray();
|
||||
vertices.bones = bonesArray.ToArray();
|
||||
return vertices;
|
||||
}
|
||||
|
||||
private float[] ReadFloatArray (SkeletonInput input, int n, float scale) {
|
||||
float[] array = new float[n];
|
||||
if (scale == 1) {
|
||||
for (int i = 0; i < n; i++)
|
||||
array[i] = input.ReadFloat();
|
||||
} else {
|
||||
for (int i = 0; i < n; i++)
|
||||
array[i] = input.ReadFloat() * scale;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private int[] ReadShortArray (SkeletonInput input) {
|
||||
int n = input.ReadInt(true);
|
||||
int[] array = new int[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
array[i] = (input.ReadByte() << 8) | input.ReadByte();
|
||||
return array;
|
||||
}
|
||||
|
||||
private Animation ReadAnimation (String name, SkeletonInput input, SkeletonData skeletonData) {
|
||||
var timelines = new ExposedList<Timeline>(32);
|
||||
float scale = Scale;
|
||||
float duration = 0;
|
||||
|
||||
// Slot timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
switch (timelineType) {
|
||||
case SLOT_ATTACHMENT: {
|
||||
AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadStringRef());
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
|
||||
break;
|
||||
}
|
||||
case SLOT_COLOR: {
|
||||
ColorTimeline timeline = new ColorTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
float time = input.ReadFloat();
|
||||
int color = input.ReadInt();
|
||||
float r = ((color & 0xff000000) >> 24) / 255f;
|
||||
float g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
float b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
float a = ((color & 0x000000ff)) / 255f;
|
||||
timeline.SetFrame(frameIndex, time, r, g, b, a);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * ColorTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
case SLOT_TWO_COLOR: {
|
||||
TwoColorTimeline timeline = new TwoColorTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
float time = input.ReadFloat();
|
||||
int color = input.ReadInt();
|
||||
float r = ((color & 0xff000000) >> 24) / 255f;
|
||||
float g = ((color & 0x00ff0000) >> 16) / 255f;
|
||||
float b = ((color & 0x0000ff00) >> 8) / 255f;
|
||||
float a = ((color & 0x000000ff)) / 255f;
|
||||
int color2 = input.ReadInt(); // 0x00rrggbb
|
||||
float r2 = ((color2 & 0x00ff0000) >> 16) / 255f;
|
||||
float g2 = ((color2 & 0x0000ff00) >> 8) / 255f;
|
||||
float b2 = ((color2 & 0x000000ff)) / 255f;
|
||||
|
||||
timeline.SetFrame(frameIndex, time, r, g, b, a, r2, g2, b2);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TwoColorTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bone timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int boneIndex = input.ReadInt(true);
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
switch (timelineType) {
|
||||
case BONE_ROTATE: {
|
||||
RotateTimeline timeline = new RotateTimeline(frameCount);
|
||||
timeline.boneIndex = boneIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * RotateTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
case BONE_TRANSLATE:
|
||||
case BONE_SCALE:
|
||||
case BONE_SHEAR: {
|
||||
TranslateTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineType == BONE_SCALE)
|
||||
timeline = new ScaleTimeline(frameCount);
|
||||
else if (timelineType == BONE_SHEAR)
|
||||
timeline = new ShearTimeline(frameCount);
|
||||
else {
|
||||
timeline = new TranslateTimeline(frameCount);
|
||||
timelineScale = scale;
|
||||
}
|
||||
timeline.boneIndex = boneIndex;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale,
|
||||
input.ReadFloat() * timelineScale);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TranslateTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IK constraint timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int index = input.ReadInt(true);
|
||||
int frameCount = input.ReadInt(true);
|
||||
IkConstraintTimeline timeline = new IkConstraintTimeline(frameCount) {
|
||||
ikConstraintIndex = index
|
||||
};
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat() * scale, input.ReadSByte(), input.ReadBoolean(),
|
||||
input.ReadBoolean());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * IkConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int index = input.ReadInt(true);
|
||||
int frameCount = input.ReadInt(true);
|
||||
TransformConstraintTimeline timeline = new TransformConstraintTimeline(frameCount);
|
||||
timeline.transformConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat(),
|
||||
input.ReadFloat());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * TransformConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
|
||||
// Path constraint timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
int index = input.ReadInt(true);
|
||||
PathConstraintData data = skeletonData.pathConstraints.Items[index];
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int timelineType = input.ReadSByte();
|
||||
int frameCount = input.ReadInt(true);
|
||||
switch(timelineType) {
|
||||
case PATH_POSITION:
|
||||
case PATH_SPACING: {
|
||||
PathConstraintPositionTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineType == PATH_SPACING) {
|
||||
timeline = new PathConstraintSpacingTimeline(frameCount);
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
|
||||
} else {
|
||||
timeline = new PathConstraintPositionTimeline(frameCount);
|
||||
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
|
||||
}
|
||||
timeline.pathConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat() * timelineScale);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
case PATH_MIX: {
|
||||
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(frameCount);
|
||||
timeline.pathConstraintIndex = index;
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
timeline.SetFrame(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(frameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deform timelines.
|
||||
for (int i = 0, n = input.ReadInt(true); i < n; i++) {
|
||||
Skin skin = skeletonData.skins.Items[input.ReadInt(true)];
|
||||
for (int ii = 0, nn = input.ReadInt(true); ii < nn; ii++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
for (int iii = 0, nnn = input.ReadInt(true); iii < nnn; iii++) {
|
||||
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, input.ReadStringRef());
|
||||
bool weighted = attachment.bones != null;
|
||||
float[] vertices = attachment.vertices;
|
||||
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
|
||||
|
||||
int frameCount = input.ReadInt(true);
|
||||
DeformTimeline timeline = new DeformTimeline(frameCount);
|
||||
timeline.slotIndex = slotIndex;
|
||||
timeline.attachment = attachment;
|
||||
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
|
||||
float time = input.ReadFloat();
|
||||
float[] deform;
|
||||
int end = input.ReadInt(true);
|
||||
if (end == 0)
|
||||
deform = weighted ? new float[deformLength] : vertices;
|
||||
else {
|
||||
deform = new float[deformLength];
|
||||
int start = input.ReadInt(true);
|
||||
end += start;
|
||||
if (scale == 1) {
|
||||
for (int v = start; v < end; v++)
|
||||
deform[v] = input.ReadFloat();
|
||||
} else {
|
||||
for (int v = start; v < end; v++)
|
||||
deform[v] = input.ReadFloat() * scale;
|
||||
}
|
||||
if (!weighted) {
|
||||
for (int v = 0, vn = deform.Length; v < vn; v++)
|
||||
deform[v] += vertices[v];
|
||||
}
|
||||
}
|
||||
|
||||
timeline.SetFrame(frameIndex, time, deform);
|
||||
if (frameIndex < frameCount - 1) ReadCurve(input, frameIndex, timeline);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[frameCount - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw order timeline.
|
||||
int drawOrderCount = input.ReadInt(true);
|
||||
if (drawOrderCount > 0) {
|
||||
DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
|
||||
int slotCount = skeletonData.slots.Count;
|
||||
for (int i = 0; i < drawOrderCount; i++) {
|
||||
float time = input.ReadFloat();
|
||||
int offsetCount = input.ReadInt(true);
|
||||
int[] drawOrder = new int[slotCount];
|
||||
for (int ii = slotCount - 1; ii >= 0; ii--)
|
||||
drawOrder[ii] = -1;
|
||||
int[] unchanged = new int[slotCount - offsetCount];
|
||||
int originalIndex = 0, unchangedIndex = 0;
|
||||
for (int ii = 0; ii < offsetCount; ii++) {
|
||||
int slotIndex = input.ReadInt(true);
|
||||
// Collect unchanged items.
|
||||
while (originalIndex != slotIndex)
|
||||
unchanged[unchangedIndex++] = originalIndex++;
|
||||
// Set changed items.
|
||||
drawOrder[originalIndex + input.ReadInt(true)] = originalIndex++;
|
||||
}
|
||||
// Collect remaining unchanged items.
|
||||
while (originalIndex < slotCount)
|
||||
unchanged[unchangedIndex++] = originalIndex++;
|
||||
// Fill in unchanged items.
|
||||
for (int ii = slotCount - 1; ii >= 0; ii--)
|
||||
if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
|
||||
timeline.SetFrame(i, time, drawOrder);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[drawOrderCount - 1]);
|
||||
}
|
||||
|
||||
// Event timeline.
|
||||
int eventCount = input.ReadInt(true);
|
||||
if (eventCount > 0) {
|
||||
EventTimeline timeline = new EventTimeline(eventCount);
|
||||
for (int i = 0; i < eventCount; i++) {
|
||||
float time = input.ReadFloat();
|
||||
EventData eventData = skeletonData.events.Items[input.ReadInt(true)];
|
||||
Event e = new Event(time, eventData) {
|
||||
Int = input.ReadInt(false),
|
||||
Float = input.ReadFloat(),
|
||||
String = input.ReadBoolean() ? input.ReadString() : eventData.String
|
||||
};
|
||||
if (e.data.AudioPath != null) {
|
||||
e.volume = input.ReadFloat();
|
||||
e.balance = input.ReadFloat();
|
||||
}
|
||||
timeline.SetFrame(i, e);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[eventCount - 1]);
|
||||
}
|
||||
|
||||
timelines.TrimExcess();
|
||||
return new Animation(name, timelines, duration);
|
||||
}
|
||||
|
||||
private void ReadCurve (SkeletonInput input, int frameIndex, CurveTimeline timeline) {
|
||||
switch (input.ReadByte()) {
|
||||
case CURVE_STEPPED:
|
||||
timeline.SetStepped(frameIndex);
|
||||
break;
|
||||
case CURVE_BEZIER:
|
||||
timeline.SetCurve(frameIndex, input.ReadFloat(), input.ReadFloat(), input.ReadFloat(), input.ReadFloat());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Vertices
|
||||
{
|
||||
public int[] bones;
|
||||
public float[] vertices;
|
||||
}
|
||||
|
||||
internal class SkeletonInput {
|
||||
private byte[] chars = new byte[32];
|
||||
private byte[] bytesBigEndian = new byte[4];
|
||||
internal ExposedList<String> strings;
|
||||
Stream input;
|
||||
|
||||
public SkeletonInput (Stream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public byte ReadByte () {
|
||||
return (byte)input.ReadByte();
|
||||
}
|
||||
|
||||
public sbyte ReadSByte () {
|
||||
int value = input.ReadByte();
|
||||
if (value == -1) throw new EndOfStreamException();
|
||||
return (sbyte)value;
|
||||
}
|
||||
|
||||
public bool ReadBoolean () {
|
||||
return input.ReadByte() != 0;
|
||||
}
|
||||
|
||||
public float ReadFloat () {
|
||||
input.Read(bytesBigEndian, 0, 4);
|
||||
chars[3] = bytesBigEndian[0];
|
||||
chars[2] = bytesBigEndian[1];
|
||||
chars[1] = bytesBigEndian[2];
|
||||
chars[0] = bytesBigEndian[3];
|
||||
return BitConverter.ToSingle(chars, 0);
|
||||
}
|
||||
|
||||
public int ReadInt () {
|
||||
input.Read(bytesBigEndian, 0, 4);
|
||||
return (bytesBigEndian[0] << 24)
|
||||
+ (bytesBigEndian[1] << 16)
|
||||
+ (bytesBigEndian[2] << 8)
|
||||
+ bytesBigEndian[3];
|
||||
}
|
||||
|
||||
public int ReadInt (bool optimizePositive) {
|
||||
int b = input.ReadByte();
|
||||
int result = b & 0x7F;
|
||||
if ((b & 0x80) != 0) {
|
||||
b = input.ReadByte();
|
||||
result |= (b & 0x7F) << 7;
|
||||
if ((b & 0x80) != 0) {
|
||||
b = input.ReadByte();
|
||||
result |= (b & 0x7F) << 14;
|
||||
if ((b & 0x80) != 0) {
|
||||
b = input.ReadByte();
|
||||
result |= (b & 0x7F) << 21;
|
||||
if ((b & 0x80) != 0) result |= (input.ReadByte() & 0x7F) << 28;
|
||||
}
|
||||
}
|
||||
}
|
||||
return optimizePositive ? result : ((result >> 1) ^ -(result & 1));
|
||||
}
|
||||
|
||||
public string ReadString () {
|
||||
int byteCount = ReadInt(true);
|
||||
switch (byteCount) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return "";
|
||||
}
|
||||
byteCount--;
|
||||
byte[] buffer = this.chars;
|
||||
if (buffer.Length < byteCount) buffer = new byte[byteCount];
|
||||
ReadFully(buffer, 0, byteCount);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
|
||||
}
|
||||
|
||||
///<return>May be null.</return>
|
||||
public String ReadStringRef () {
|
||||
int index = ReadInt(true);
|
||||
return index == 0 ? null : strings.Items[index - 1];
|
||||
}
|
||||
|
||||
public void ReadFully (byte[] buffer, int offset, int length) {
|
||||
while (length > 0) {
|
||||
int count = input.Read(buffer, offset, length);
|
||||
if (count <= 0) throw new EndOfStreamException();
|
||||
offset += count;
|
||||
length -= count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the version string of binary skeleton data.</summary>
|
||||
public string GetVersionString () {
|
||||
try {
|
||||
// Hash.
|
||||
int byteCount = ReadInt(true);
|
||||
if (byteCount > 1) input.Position += byteCount - 1;
|
||||
|
||||
// Version.
|
||||
byteCount = ReadInt(true);
|
||||
if (byteCount > 1) {
|
||||
byteCount--;
|
||||
var buffer = new byte[byteCount];
|
||||
ReadFully(buffer, 0, byteCount);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.", "input");
|
||||
} catch (Exception e) {
|
||||
throw new ArgumentException("Stream does not contain a valid binary Skeleton Data.\n" + e, "input");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40d8a8f15082f3844a5c9c8c3ef2047f
|
||||
timeCreated: 1456265154
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,234 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
|
||||
/// <summary>
|
||||
/// Collects each BoundingBoxAttachment that is visible and computes the world vertices for its polygon.
|
||||
/// The polygon vertices are provided along with convenience methods for doing hit detection.
|
||||
/// </summary>
|
||||
public class SkeletonBounds {
|
||||
private ExposedList<Polygon> polygonPool = new ExposedList<Polygon>();
|
||||
private float minX, minY, maxX, maxY;
|
||||
|
||||
public ExposedList<BoundingBoxAttachment> BoundingBoxes { get; private set; }
|
||||
public ExposedList<Polygon> Polygons { get; private set; }
|
||||
public float MinX { get { return minX; } set { minX = value; } }
|
||||
public float MinY { get { return minY; } set { minY = value; } }
|
||||
public float MaxX { get { return maxX; } set { maxX = value; } }
|
||||
public float MaxY { get { return maxY; } set { maxY = value; } }
|
||||
public float Width { get { return maxX - minX; } }
|
||||
public float Height { get { return maxY - minY; } }
|
||||
|
||||
public SkeletonBounds () {
|
||||
BoundingBoxes = new ExposedList<BoundingBoxAttachment>();
|
||||
Polygons = new ExposedList<Polygon>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears any previous polygons, finds all visible bounding box attachments,
|
||||
/// and computes the world vertices for each bounding box's polygon.</summary>
|
||||
/// <param name="skeleton">The skeleton.</param>
|
||||
/// <param name="updateAabb">
|
||||
/// If true, the axis aligned bounding box containing all the polygons is computed.
|
||||
/// If false, the SkeletonBounds AABB methods will always return true.
|
||||
/// </param>
|
||||
public void Update (Skeleton skeleton, bool updateAabb) {
|
||||
ExposedList<BoundingBoxAttachment> boundingBoxes = BoundingBoxes;
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
ExposedList<Slot> slots = skeleton.slots;
|
||||
int slotCount = slots.Count;
|
||||
|
||||
boundingBoxes.Clear();
|
||||
for (int i = 0, n = polygons.Count; i < n; i++)
|
||||
polygonPool.Add(polygons.Items[i]);
|
||||
polygons.Clear();
|
||||
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
Slot slot = slots.Items[i];
|
||||
if (!slot.bone.active) continue;
|
||||
BoundingBoxAttachment boundingBox = slot.attachment as BoundingBoxAttachment;
|
||||
if (boundingBox == null) continue;
|
||||
boundingBoxes.Add(boundingBox);
|
||||
|
||||
Polygon polygon = null;
|
||||
int poolCount = polygonPool.Count;
|
||||
if (poolCount > 0) {
|
||||
polygon = polygonPool.Items[poolCount - 1];
|
||||
polygonPool.RemoveAt(poolCount - 1);
|
||||
} else
|
||||
polygon = new Polygon();
|
||||
polygons.Add(polygon);
|
||||
|
||||
int count = boundingBox.worldVerticesLength;
|
||||
polygon.Count = count;
|
||||
if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
|
||||
boundingBox.ComputeWorldVertices(slot, polygon.Vertices);
|
||||
}
|
||||
|
||||
if (updateAabb) {
|
||||
AabbCompute();
|
||||
} else {
|
||||
minX = int.MinValue;
|
||||
minY = int.MinValue;
|
||||
maxX = int.MaxValue;
|
||||
maxY = int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void AabbCompute () {
|
||||
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
for (int i = 0, n = polygons.Count; i < n; i++) {
|
||||
Polygon polygon = polygons.Items[i];
|
||||
float[] vertices = polygon.Vertices;
|
||||
for (int ii = 0, nn = polygon.Count; ii < nn; ii += 2) {
|
||||
float x = vertices[ii];
|
||||
float y = vertices[ii + 1];
|
||||
minX = Math.Min(minX, x);
|
||||
minY = Math.Min(minY, y);
|
||||
maxX = Math.Max(maxX, x);
|
||||
maxY = Math.Max(maxY, y);
|
||||
}
|
||||
}
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Returns true if the axis aligned bounding box contains the point.</summary>
|
||||
public bool AabbContainsPoint (float x, float y) {
|
||||
return x >= minX && x <= maxX && y >= minY && y <= maxY;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the axis aligned bounding box intersects the line segment.</summary>
|
||||
public bool AabbIntersectsSegment (float x1, float y1, float x2, float y2) {
|
||||
float minX = this.minX;
|
||||
float minY = this.minY;
|
||||
float maxX = this.maxX;
|
||||
float maxY = this.maxY;
|
||||
if ((x1 <= minX && x2 <= minX) || (y1 <= minY && y2 <= minY) || (x1 >= maxX && x2 >= maxX) || (y1 >= maxY && y2 >= maxY))
|
||||
return false;
|
||||
float m = (y2 - y1) / (x2 - x1);
|
||||
float y = m * (minX - x1) + y1;
|
||||
if (y > minY && y < maxY) return true;
|
||||
y = m * (maxX - x1) + y1;
|
||||
if (y > minY && y < maxY) return true;
|
||||
float x = (minY - y1) / m + x1;
|
||||
if (x > minX && x < maxX) return true;
|
||||
x = (maxY - y1) / m + x1;
|
||||
if (x > minX && x < maxX) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the axis aligned bounding box intersects the axis aligned bounding box of the specified bounds.</summary>
|
||||
public bool AabbIntersectsSkeleton (SkeletonBounds bounds) {
|
||||
return minX < bounds.maxX && maxX > bounds.minX && minY < bounds.maxY && maxY > bounds.minY;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the polygon contains the point.</summary>
|
||||
public bool ContainsPoint (Polygon polygon, float x, float y) {
|
||||
float[] vertices = polygon.Vertices;
|
||||
int nn = polygon.Count;
|
||||
|
||||
int prevIndex = nn - 2;
|
||||
bool inside = false;
|
||||
for (int ii = 0; ii < nn; ii += 2) {
|
||||
float vertexY = vertices[ii + 1];
|
||||
float prevY = vertices[prevIndex + 1];
|
||||
if ((vertexY < y && prevY >= y) || (prevY < y && vertexY >= y)) {
|
||||
float vertexX = vertices[ii];
|
||||
if (vertexX + (y - vertexY) / (prevY - vertexY) * (vertices[prevIndex] - vertexX) < x) inside = !inside;
|
||||
}
|
||||
prevIndex = ii;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
/// <summary>Returns the first bounding box attachment that contains the point, or null. When doing many checks, it is usually more
|
||||
/// efficient to only call this method if {@link #aabbContainsPoint(float, float)} returns true.</summary>
|
||||
public BoundingBoxAttachment ContainsPoint (float x, float y) {
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
for (int i = 0, n = polygons.Count; i < n; i++)
|
||||
if (ContainsPoint(polygons.Items[i], x, y)) return BoundingBoxes.Items[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Returns the first bounding box attachment that contains the line segment, or null. When doing many checks, it is usually
|
||||
/// more efficient to only call this method if {@link #aabbIntersectsSegment(float, float, float, float)} returns true.</summary>
|
||||
public BoundingBoxAttachment IntersectsSegment (float x1, float y1, float x2, float y2) {
|
||||
ExposedList<Polygon> polygons = Polygons;
|
||||
for (int i = 0, n = polygons.Count; i < n; i++)
|
||||
if (IntersectsSegment(polygons.Items[i], x1, y1, x2, y2)) return BoundingBoxes.Items[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Returns true if the polygon contains the line segment.</summary>
|
||||
public bool IntersectsSegment (Polygon polygon, float x1, float y1, float x2, float y2) {
|
||||
float[] vertices = polygon.Vertices;
|
||||
int nn = polygon.Count;
|
||||
|
||||
float width12 = x1 - x2, height12 = y1 - y2;
|
||||
float det1 = x1 * y2 - y1 * x2;
|
||||
float x3 = vertices[nn - 2], y3 = vertices[nn - 1];
|
||||
for (int ii = 0; ii < nn; ii += 2) {
|
||||
float x4 = vertices[ii], y4 = vertices[ii + 1];
|
||||
float det2 = x3 * y4 - y3 * x4;
|
||||
float width34 = x3 - x4, height34 = y3 - y4;
|
||||
float det3 = width12 * height34 - height12 * width34;
|
||||
float x = (det1 * width34 - width12 * det2) / det3;
|
||||
if (((x >= x3 && x <= x4) || (x >= x4 && x <= x3)) && ((x >= x1 && x <= x2) || (x >= x2 && x <= x1))) {
|
||||
float y = (det1 * height34 - height12 * det2) / det3;
|
||||
if (((y >= y3 && y <= y4) || (y >= y4 && y <= y3)) && ((y >= y1 && y <= y2) || (y >= y2 && y <= y1))) return true;
|
||||
}
|
||||
x3 = x4;
|
||||
y3 = y4;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Polygon GetPolygon (BoundingBoxAttachment attachment) {
|
||||
int index = BoundingBoxes.IndexOf(attachment);
|
||||
return index == -1 ? null : Polygons.Items[index];
|
||||
}
|
||||
}
|
||||
|
||||
public class Polygon {
|
||||
public float[] Vertices { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
||||
public Polygon () {
|
||||
Vertices = new float[16];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 087b328a58c93b149bb977eee3a17258
|
||||
timeCreated: 1456265153
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,296 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonClipping {
|
||||
internal readonly Triangulator triangulator = new Triangulator();
|
||||
internal readonly ExposedList<float> clippingPolygon = new ExposedList<float>();
|
||||
internal readonly ExposedList<float> clipOutput = new ExposedList<float>(128);
|
||||
internal readonly ExposedList<float> clippedVertices = new ExposedList<float>(128);
|
||||
internal readonly ExposedList<int> clippedTriangles = new ExposedList<int>(128);
|
||||
internal readonly ExposedList<float> clippedUVs = new ExposedList<float>(128);
|
||||
internal readonly ExposedList<float> scratch = new ExposedList<float>();
|
||||
|
||||
internal ClippingAttachment clipAttachment;
|
||||
internal ExposedList<ExposedList<float>> clippingPolygons;
|
||||
|
||||
public ExposedList<float> ClippedVertices { get { return clippedVertices; } }
|
||||
public ExposedList<int> ClippedTriangles { get { return clippedTriangles; } }
|
||||
public ExposedList<float> ClippedUVs { get { return clippedUVs; } }
|
||||
|
||||
public bool IsClipping { get { return clipAttachment != null; } }
|
||||
|
||||
public int ClipStart (Slot slot, ClippingAttachment clip) {
|
||||
if (clipAttachment != null) return 0;
|
||||
clipAttachment = clip;
|
||||
|
||||
int n = clip.worldVerticesLength;
|
||||
float[] vertices = clippingPolygon.Resize(n).Items;
|
||||
clip.ComputeWorldVertices(slot, 0, n, vertices, 0, 2);
|
||||
MakeClockwise(clippingPolygon);
|
||||
clippingPolygons = triangulator.Decompose(clippingPolygon, triangulator.Triangulate(clippingPolygon));
|
||||
foreach (var polygon in clippingPolygons) {
|
||||
MakeClockwise(polygon);
|
||||
polygon.Add(polygon.Items[0]);
|
||||
polygon.Add(polygon.Items[1]);
|
||||
}
|
||||
return clippingPolygons.Count;
|
||||
}
|
||||
|
||||
public void ClipEnd (Slot slot) {
|
||||
if (clipAttachment != null && clipAttachment.endSlot == slot.data) ClipEnd();
|
||||
}
|
||||
|
||||
public void ClipEnd () {
|
||||
if (clipAttachment == null) return;
|
||||
clipAttachment = null;
|
||||
clippingPolygons = null;
|
||||
clippedVertices.Clear();
|
||||
clippedTriangles.Clear();
|
||||
clippingPolygon.Clear();
|
||||
}
|
||||
|
||||
public void ClipTriangles (float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs) {
|
||||
ExposedList<float> clipOutput = this.clipOutput, clippedVertices = this.clippedVertices;
|
||||
var clippedTriangles = this.clippedTriangles;
|
||||
var polygons = clippingPolygons.Items;
|
||||
int polygonsCount = clippingPolygons.Count;
|
||||
|
||||
int index = 0;
|
||||
clippedVertices.Clear();
|
||||
clippedUVs.Clear();
|
||||
clippedTriangles.Clear();
|
||||
//outer:
|
||||
for (int i = 0; i < trianglesLength; i += 3) {
|
||||
int vertexOffset = triangles[i] << 1;
|
||||
float x1 = vertices[vertexOffset], y1 = vertices[vertexOffset + 1];
|
||||
float u1 = uvs[vertexOffset], v1 = uvs[vertexOffset + 1];
|
||||
|
||||
vertexOffset = triangles[i + 1] << 1;
|
||||
float x2 = vertices[vertexOffset], y2 = vertices[vertexOffset + 1];
|
||||
float u2 = uvs[vertexOffset], v2 = uvs[vertexOffset + 1];
|
||||
|
||||
vertexOffset = triangles[i + 2] << 1;
|
||||
float x3 = vertices[vertexOffset], y3 = vertices[vertexOffset + 1];
|
||||
float u3 = uvs[vertexOffset], v3 = uvs[vertexOffset + 1];
|
||||
|
||||
for (int p = 0; p < polygonsCount; p++) {
|
||||
int s = clippedVertices.Count;
|
||||
if (Clip(x1, y1, x2, y2, x3, y3, polygons[p], clipOutput)) {
|
||||
int clipOutputLength = clipOutput.Count;
|
||||
if (clipOutputLength == 0) continue;
|
||||
float d0 = y2 - y3, d1 = x3 - x2, d2 = x1 - x3, d4 = y3 - y1;
|
||||
float d = 1 / (d0 * d2 + d1 * (y1 - y3));
|
||||
|
||||
int clipOutputCount = clipOutputLength >> 1;
|
||||
float[] clipOutputItems = clipOutput.Items;
|
||||
float[] clippedVerticesItems = clippedVertices.Resize(s + clipOutputCount * 2).Items;
|
||||
float[] clippedUVsItems = clippedUVs.Resize(s + clipOutputCount * 2).Items;
|
||||
for (int ii = 0; ii < clipOutputLength; ii += 2) {
|
||||
float x = clipOutputItems[ii], y = clipOutputItems[ii + 1];
|
||||
clippedVerticesItems[s] = x;
|
||||
clippedVerticesItems[s + 1] = y;
|
||||
float c0 = x - x3, c1 = y - y3;
|
||||
float a = (d0 * c0 + d1 * c1) * d;
|
||||
float b = (d4 * c0 + d2 * c1) * d;
|
||||
float c = 1 - a - b;
|
||||
clippedUVsItems[s] = u1 * a + u2 * b + u3 * c;
|
||||
clippedUVsItems[s + 1] = v1 * a + v2 * b + v3 * c;
|
||||
s += 2;
|
||||
}
|
||||
|
||||
s = clippedTriangles.Count;
|
||||
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3 * (clipOutputCount - 2)).Items;
|
||||
clipOutputCount--;
|
||||
for (int ii = 1; ii < clipOutputCount; ii++) {
|
||||
clippedTrianglesItems[s] = index;
|
||||
clippedTrianglesItems[s + 1] = index + ii;
|
||||
clippedTrianglesItems[s + 2] = index + ii + 1;
|
||||
s += 3;
|
||||
}
|
||||
index += clipOutputCount + 1;
|
||||
}
|
||||
else {
|
||||
float[] clippedVerticesItems = clippedVertices.Resize(s + 3 * 2).Items;
|
||||
float[] clippedUVsItems = clippedUVs.Resize(s + 3 * 2).Items;
|
||||
clippedVerticesItems[s] = x1;
|
||||
clippedVerticesItems[s + 1] = y1;
|
||||
clippedVerticesItems[s + 2] = x2;
|
||||
clippedVerticesItems[s + 3] = y2;
|
||||
clippedVerticesItems[s + 4] = x3;
|
||||
clippedVerticesItems[s + 5] = y3;
|
||||
|
||||
clippedUVsItems[s] = u1;
|
||||
clippedUVsItems[s + 1] = v1;
|
||||
clippedUVsItems[s + 2] = u2;
|
||||
clippedUVsItems[s + 3] = v2;
|
||||
clippedUVsItems[s + 4] = u3;
|
||||
clippedUVsItems[s + 5] = v3;
|
||||
|
||||
s = clippedTriangles.Count;
|
||||
int[] clippedTrianglesItems = clippedTriangles.Resize(s + 3).Items;
|
||||
clippedTrianglesItems[s] = index;
|
||||
clippedTrianglesItems[s + 1] = index + 1;
|
||||
clippedTrianglesItems[s + 2] = index + 2;
|
||||
index += 3;
|
||||
break; //continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Clips the input triangle against the convex, clockwise clipping area. If the triangle lies entirely within the clipping
|
||||
* area, false is returned. The clipping area must duplicate the first vertex at the end of the vertices list. */
|
||||
internal bool Clip (float x1, float y1, float x2, float y2, float x3, float y3, ExposedList<float> clippingArea, ExposedList<float> output) {
|
||||
var originalOutput = output;
|
||||
var clipped = false;
|
||||
|
||||
// Avoid copy at the end.
|
||||
ExposedList<float> input = null;
|
||||
if (clippingArea.Count % 4 >= 2) {
|
||||
input = output;
|
||||
output = scratch;
|
||||
} else {
|
||||
input = scratch;
|
||||
}
|
||||
|
||||
input.Clear();
|
||||
input.Add(x1);
|
||||
input.Add(y1);
|
||||
input.Add(x2);
|
||||
input.Add(y2);
|
||||
input.Add(x3);
|
||||
input.Add(y3);
|
||||
input.Add(x1);
|
||||
input.Add(y1);
|
||||
output.Clear();
|
||||
|
||||
float[] clippingVertices = clippingArea.Items;
|
||||
int clippingVerticesLast = clippingArea.Count - 4;
|
||||
for (int i = 0; ; i += 2) {
|
||||
float edgeX = clippingVertices[i], edgeY = clippingVertices[i + 1];
|
||||
float edgeX2 = clippingVertices[i + 2], edgeY2 = clippingVertices[i + 3];
|
||||
float deltaX = edgeX - edgeX2, deltaY = edgeY - edgeY2;
|
||||
|
||||
float[] inputVertices = input.Items;
|
||||
int inputVerticesLength = input.Count - 2, outputStart = output.Count;
|
||||
for (int ii = 0; ii < inputVerticesLength; ii += 2) {
|
||||
float inputX = inputVertices[ii], inputY = inputVertices[ii + 1];
|
||||
float inputX2 = inputVertices[ii + 2], inputY2 = inputVertices[ii + 3];
|
||||
bool side2 = deltaX * (inputY2 - edgeY2) - deltaY * (inputX2 - edgeX2) > 0;
|
||||
if (deltaX * (inputY - edgeY2) - deltaY * (inputX - edgeX2) > 0) {
|
||||
if (side2) { // v1 inside, v2 inside
|
||||
output.Add(inputX2);
|
||||
output.Add(inputY2);
|
||||
continue;
|
||||
}
|
||||
// v1 inside, v2 outside
|
||||
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
|
||||
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
|
||||
if (Math.Abs(s) > 0.000001f) {
|
||||
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
|
||||
output.Add(edgeX + (edgeX2 - edgeX) * ua);
|
||||
output.Add(edgeY + (edgeY2 - edgeY) * ua);
|
||||
} else {
|
||||
output.Add(edgeX);
|
||||
output.Add(edgeY);
|
||||
}
|
||||
}
|
||||
else if (side2) { // v1 outside, v2 inside
|
||||
float c0 = inputY2 - inputY, c2 = inputX2 - inputX;
|
||||
float s = c0 * (edgeX2 - edgeX) - c2 * (edgeY2 - edgeY);
|
||||
if (Math.Abs(s) > 0.000001f) {
|
||||
float ua = (c2 * (edgeY - inputY) - c0 * (edgeX - inputX)) / s;
|
||||
output.Add(edgeX + (edgeX2 - edgeX) * ua);
|
||||
output.Add(edgeY + (edgeY2 - edgeY) * ua);
|
||||
} else {
|
||||
output.Add(edgeX);
|
||||
output.Add(edgeY);
|
||||
}
|
||||
output.Add(inputX2);
|
||||
output.Add(inputY2);
|
||||
}
|
||||
clipped = true;
|
||||
}
|
||||
|
||||
if (outputStart == output.Count) { // All edges outside.
|
||||
originalOutput.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
output.Add(output.Items[0]);
|
||||
output.Add(output.Items[1]);
|
||||
|
||||
if (i == clippingVerticesLast) break;
|
||||
var temp = output;
|
||||
output = input;
|
||||
output.Clear();
|
||||
input = temp;
|
||||
}
|
||||
|
||||
if (originalOutput != output) {
|
||||
originalOutput.Clear();
|
||||
for (int i = 0, n = output.Count - 2; i < n; i++) {
|
||||
originalOutput.Add(output.Items[i]);
|
||||
}
|
||||
} else {
|
||||
originalOutput.Resize(originalOutput.Count - 2);
|
||||
}
|
||||
|
||||
return clipped;
|
||||
}
|
||||
|
||||
public static void MakeClockwise (ExposedList<float> polygon) {
|
||||
float[] vertices = polygon.Items;
|
||||
int verticeslength = polygon.Count;
|
||||
|
||||
float area = vertices[verticeslength - 2] * vertices[1] - vertices[0] * vertices[verticeslength - 1], p1x, p1y, p2x, p2y;
|
||||
for (int i = 0, n = verticeslength - 3; i < n; i += 2) {
|
||||
p1x = vertices[i];
|
||||
p1y = vertices[i + 1];
|
||||
p2x = vertices[i + 2];
|
||||
p2y = vertices[i + 3];
|
||||
area += p1x * p2y - p2x * p1y;
|
||||
}
|
||||
if (area < 0) return;
|
||||
|
||||
for (int i = 0, lastX = verticeslength - 2, n = verticeslength >> 1; i < n; i += 2) {
|
||||
float x = vertices[i], y = vertices[i + 1];
|
||||
int other = lastX - i;
|
||||
vertices[i] = vertices[other];
|
||||
vertices[i + 1] = vertices[other + 1];
|
||||
vertices[other] = x;
|
||||
vertices[other + 1] = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7db809d277afd0e4a8e8c6b703002ee0
|
||||
timeCreated: 1492744746
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,230 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
|
||||
/// <summary>Stores the setup pose and all of the stateless data for a skeleton.</summary>
|
||||
public class SkeletonData {
|
||||
internal string name;
|
||||
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
|
||||
internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
|
||||
internal ExposedList<Skin> skins = new ExposedList<Skin>();
|
||||
internal Skin defaultSkin;
|
||||
internal ExposedList<EventData> events = new ExposedList<EventData>();
|
||||
internal ExposedList<Animation> animations = new ExposedList<Animation>();
|
||||
internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
|
||||
internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
|
||||
internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
|
||||
internal float x , y, width, height;
|
||||
internal string version, hash;
|
||||
|
||||
// Nonessential.
|
||||
internal float fps;
|
||||
internal string imagesPath, audioPath;
|
||||
|
||||
public string Name { get { return name; } set { name = value; } }
|
||||
|
||||
/// <summary>The skeleton's bones, sorted parent first. The root bone is always the first bone.</summary>
|
||||
public ExposedList<BoneData> Bones { get { return bones; } }
|
||||
|
||||
public ExposedList<SlotData> Slots { get { return slots; } }
|
||||
|
||||
/// <summary>All skins, including the default skin.</summary>
|
||||
public ExposedList<Skin> Skins { get { return skins; } set { skins = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// The skeleton's default skin.
|
||||
/// By default this skin contains all attachments that were not in a skin in Spine.
|
||||
/// </summary>
|
||||
/// <return>May be null.</return>
|
||||
public Skin DefaultSkin { get { return defaultSkin; } set { defaultSkin = value; } }
|
||||
|
||||
public ExposedList<EventData> Events { get { return events; } set { events = value; } }
|
||||
public ExposedList<Animation> Animations { get { return animations; } set { animations = value; } }
|
||||
public ExposedList<IkConstraintData> IkConstraints { get { return ikConstraints; } set { ikConstraints = value; } }
|
||||
public ExposedList<TransformConstraintData> TransformConstraints { get { return transformConstraints; } set { transformConstraints = value; } }
|
||||
public ExposedList<PathConstraintData> PathConstraints { get { return pathConstraints; } set { pathConstraints = value; } }
|
||||
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public float Width { get { return width; } set { width = value; } }
|
||||
public float Height { get { return height; } set { height = value; } }
|
||||
/// <summary>The Spine version used to export this data, or null.</summary>
|
||||
public string Version { get { return version; } set { version = value; } }
|
||||
public string Hash { get { return hash; } set { hash = value; } }
|
||||
|
||||
/// <summary>The path to the images directory as defined in Spine. Available only when nonessential data was exported. May be null</summary>
|
||||
public string ImagesPath { get { return imagesPath; } set { imagesPath = value; } }
|
||||
|
||||
/// <summary>The path to the audio directory defined in Spine. Available only when nonessential data was exported. May be null.</summary>
|
||||
public string AudioPath { get { return audioPath; } set { audioPath = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// The dopesheet FPS in Spine. Available only when nonessential data was exported.</summary>
|
||||
public float Fps { get { return fps; } set { fps = value; } }
|
||||
|
||||
// --- Bones.
|
||||
|
||||
/// <summary>
|
||||
/// Finds a bone by comparing each bone's name.
|
||||
/// It is more efficient to cache the results of this method than to call it multiple times.</summary>
|
||||
/// <returns>May be null.</returns>
|
||||
public BoneData FindBone (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++) {
|
||||
BoneData bone = bonesItems[i];
|
||||
if (bone.name == boneName) return bone;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>-1 if the bone was not found.</returns>
|
||||
public int FindBoneIndex (string boneName) {
|
||||
if (boneName == null) throw new ArgumentNullException("boneName", "boneName cannot be null.");
|
||||
var bones = this.bones;
|
||||
var bonesItems = bones.Items;
|
||||
for (int i = 0, n = bones.Count; i < n; i++)
|
||||
if (bonesItems[i].name == boneName) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// --- Slots.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public SlotData FindSlot (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
ExposedList<SlotData> slots = this.slots;
|
||||
for (int i = 0, n = slots.Count; i < n; i++) {
|
||||
SlotData slot = slots.Items[i];
|
||||
if (slot.name == slotName) return slot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>-1 if the slot was not found.</returns>
|
||||
public int FindSlotIndex (string slotName) {
|
||||
if (slotName == null) throw new ArgumentNullException("slotName", "slotName cannot be null.");
|
||||
ExposedList<SlotData> slots = this.slots;
|
||||
for (int i = 0, n = slots.Count; i < n; i++)
|
||||
if (slots.Items[i].name == slotName) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// --- Skins.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Skin FindSkin (string skinName) {
|
||||
if (skinName == null) throw new ArgumentNullException("skinName", "skinName cannot be null.");
|
||||
foreach (Skin skin in skins)
|
||||
if (skin.name == skinName) return skin;
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Events.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public EventData FindEvent (string eventDataName) {
|
||||
if (eventDataName == null) throw new ArgumentNullException("eventDataName", "eventDataName cannot be null.");
|
||||
foreach (EventData eventData in events)
|
||||
if (eventData.name == eventDataName) return eventData;
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Animations.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public Animation FindAnimation (string animationName) {
|
||||
if (animationName == null) throw new ArgumentNullException("animationName", "animationName cannot be null.");
|
||||
ExposedList<Animation> animations = this.animations;
|
||||
for (int i = 0, n = animations.Count; i < n; i++) {
|
||||
Animation animation = animations.Items[i];
|
||||
if (animation.name == animationName) return animation;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- IK constraints.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public IkConstraintData FindIkConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<IkConstraintData> ikConstraints = this.ikConstraints;
|
||||
for (int i = 0, n = ikConstraints.Count; i < n; i++) {
|
||||
IkConstraintData ikConstraint = ikConstraints.Items[i];
|
||||
if (ikConstraint.name == constraintName) return ikConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Transform constraints.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public TransformConstraintData FindTransformConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<TransformConstraintData> transformConstraints = this.transformConstraints;
|
||||
for (int i = 0, n = transformConstraints.Count; i < n; i++) {
|
||||
TransformConstraintData transformConstraint = transformConstraints.Items[i];
|
||||
if (transformConstraint.name == constraintName) return transformConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Path constraints.
|
||||
|
||||
/// <returns>May be null.</returns>
|
||||
public PathConstraintData FindPathConstraint (string constraintName) {
|
||||
if (constraintName == null) throw new ArgumentNullException("constraintName", "constraintName cannot be null.");
|
||||
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++) {
|
||||
PathConstraintData constraint = pathConstraints.Items[i];
|
||||
if (constraint.name.Equals(constraintName)) return constraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>-1 if the path constraint was not found.</returns>
|
||||
public int FindPathConstraintIndex (string pathConstraintName) {
|
||||
if (pathConstraintName == null) throw new ArgumentNullException("pathConstraintName", "pathConstraintName cannot be null.");
|
||||
ExposedList<PathConstraintData> pathConstraints = this.pathConstraints;
|
||||
for (int i = 0, n = pathConstraints.Count; i < n; i++)
|
||||
if (pathConstraints.Items[i].name.Equals(pathConstraintName)) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
override public string ToString () {
|
||||
return name ?? base.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b813f63abbb6d94a80a5c050590a0be
|
||||
timeCreated: 1456265153
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,918 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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_5 || UNITY_5_3_OR_NEWER || UNITY_WSA || UNITY_WP8 || UNITY_WP8_1)
|
||||
#define IS_UNITY
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if WINDOWS_STOREAPP
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
#endif
|
||||
|
||||
namespace Spine {
|
||||
public class SkeletonJson {
|
||||
public float Scale { get; set; }
|
||||
|
||||
private AttachmentLoader attachmentLoader;
|
||||
private List<LinkedMesh> linkedMeshes = new List<LinkedMesh>();
|
||||
|
||||
public SkeletonJson (params Atlas[] atlasArray)
|
||||
: this(new AtlasAttachmentLoader(atlasArray)) {
|
||||
}
|
||||
|
||||
public SkeletonJson (AttachmentLoader attachmentLoader) {
|
||||
if (attachmentLoader == null) throw new ArgumentNullException("attachmentLoader", "attachmentLoader cannot be null.");
|
||||
this.attachmentLoader = attachmentLoader;
|
||||
Scale = 1;
|
||||
}
|
||||
|
||||
#if !IS_UNITY && WINDOWS_STOREAPP
|
||||
private async Task<SkeletonData> ReadFile(string path) {
|
||||
var folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
|
||||
var file = await folder.GetFileAsync(path).AsTask().ConfigureAwait(false);
|
||||
using (var reader = new StreamReader(await file.OpenStreamForReadAsync().ConfigureAwait(false))) {
|
||||
SkeletonData skeletonData = ReadSkeletonData(reader);
|
||||
skeletonData.Name = Path.GetFileNameWithoutExtension(path);
|
||||
return skeletonData;
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonData ReadSkeletonData (string path) {
|
||||
return this.ReadFile(path).Result;
|
||||
}
|
||||
#else
|
||||
public SkeletonData ReadSkeletonData (string path) {
|
||||
#if WINDOWS_PHONE
|
||||
using (var reader = new StreamReader(Microsoft.Xna.Framework.TitleContainer.OpenStream(path))) {
|
||||
#else
|
||||
using (var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))) {
|
||||
#endif
|
||||
SkeletonData skeletonData = ReadSkeletonData(reader);
|
||||
skeletonData.name = Path.GetFileNameWithoutExtension(path);
|
||||
return skeletonData;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public SkeletonData ReadSkeletonData (TextReader reader) {
|
||||
if (reader == null) throw new ArgumentNullException("reader", "reader cannot be null.");
|
||||
|
||||
float scale = this.Scale;
|
||||
var skeletonData = new SkeletonData();
|
||||
|
||||
var root = Json.Deserialize(reader) as Dictionary<string, Object>;
|
||||
if (root == null) throw new Exception("Invalid JSON.");
|
||||
|
||||
// Skeleton.
|
||||
if (root.ContainsKey("skeleton")) {
|
||||
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
|
||||
skeletonData.hash = (string)skeletonMap["hash"];
|
||||
skeletonData.version = (string)skeletonMap["spine"];
|
||||
if ("3.8.75" == skeletonData.version)
|
||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
skeletonData.x = GetFloat(skeletonMap, "x", 0);
|
||||
skeletonData.y = GetFloat(skeletonMap, "y", 0);
|
||||
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
||||
skeletonData.height = GetFloat(skeletonMap, "height", 0);
|
||||
skeletonData.fps = GetFloat(skeletonMap, "fps", 30);
|
||||
skeletonData.imagesPath = GetString(skeletonMap, "images", null);
|
||||
skeletonData.audioPath = GetString(skeletonMap, "audio", null);
|
||||
}
|
||||
|
||||
// Bones.
|
||||
if (root.ContainsKey("bones")) {
|
||||
foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
|
||||
BoneData parent = null;
|
||||
if (boneMap.ContainsKey("parent")) {
|
||||
parent = skeletonData.FindBone((string)boneMap["parent"]);
|
||||
if (parent == null)
|
||||
throw new Exception("Parent bone not found: " + boneMap["parent"]);
|
||||
}
|
||||
var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
|
||||
data.length = GetFloat(boneMap, "length", 0) * scale;
|
||||
data.x = GetFloat(boneMap, "x", 0) * scale;
|
||||
data.y = GetFloat(boneMap, "y", 0) * scale;
|
||||
data.rotation = GetFloat(boneMap, "rotation", 0);
|
||||
data.scaleX = GetFloat(boneMap, "scaleX", 1);
|
||||
data.scaleY = GetFloat(boneMap, "scaleY", 1);
|
||||
data.shearX = GetFloat(boneMap, "shearX", 0);
|
||||
data.shearY = GetFloat(boneMap, "shearY", 0);
|
||||
|
||||
string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
|
||||
data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
|
||||
data.skinRequired = GetBoolean(boneMap, "skin", false);
|
||||
|
||||
skeletonData.bones.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Slots.
|
||||
if (root.ContainsKey("slots")) {
|
||||
foreach (Dictionary<string, Object> slotMap in (List<Object>)root["slots"]) {
|
||||
var slotName = (string)slotMap["name"];
|
||||
var boneName = (string)slotMap["bone"];
|
||||
BoneData boneData = skeletonData.FindBone(boneName);
|
||||
if (boneData == null) throw new Exception("Slot bone not found: " + boneName);
|
||||
var data = new SlotData(skeletonData.Slots.Count, slotName, boneData);
|
||||
|
||||
if (slotMap.ContainsKey("color")) {
|
||||
string color = (string)slotMap["color"];
|
||||
data.r = ToColor(color, 0);
|
||||
data.g = ToColor(color, 1);
|
||||
data.b = ToColor(color, 2);
|
||||
data.a = ToColor(color, 3);
|
||||
}
|
||||
|
||||
if (slotMap.ContainsKey("dark")) {
|
||||
var color2 = (string)slotMap["dark"];
|
||||
data.r2 = ToColor(color2, 0, 6); // expectedLength = 6. ie. "RRGGBB"
|
||||
data.g2 = ToColor(color2, 1, 6);
|
||||
data.b2 = ToColor(color2, 2, 6);
|
||||
data.hasSecondColor = true;
|
||||
}
|
||||
|
||||
data.attachmentName = GetString(slotMap, "attachment", null);
|
||||
if (slotMap.ContainsKey("blend"))
|
||||
data.blendMode = (BlendMode)Enum.Parse(typeof(BlendMode), (string)slotMap["blend"], true);
|
||||
else
|
||||
data.blendMode = BlendMode.Normal;
|
||||
skeletonData.slots.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// IK constraints.
|
||||
if (root.ContainsKey("ik")) {
|
||||
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
|
||||
IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
|
||||
data.order = GetInt(constraintMap, "order", 0);
|
||||
data.skinRequired = GetBoolean(constraintMap,"skin", false);
|
||||
|
||||
if (constraintMap.ContainsKey("bones")) {
|
||||
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
|
||||
BoneData bone = skeletonData.FindBone(boneName);
|
||||
if (bone == null) throw new Exception("IK bone not found: " + boneName);
|
||||
data.bones.Add(bone);
|
||||
}
|
||||
}
|
||||
|
||||
string targetName = (string)constraintMap["target"];
|
||||
data.target = skeletonData.FindBone(targetName);
|
||||
if (data.target == null) throw new Exception("IK target bone not found: " + targetName);
|
||||
data.mix = GetFloat(constraintMap, "mix", 1);
|
||||
data.softness = GetFloat(constraintMap, "softness", 0) * scale;
|
||||
data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
|
||||
data.compress = GetBoolean(constraintMap, "compress", false);
|
||||
data.stretch = GetBoolean(constraintMap, "stretch", false);
|
||||
data.uniform = GetBoolean(constraintMap, "uniform", false);
|
||||
|
||||
skeletonData.ikConstraints.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform constraints.
|
||||
if (root.ContainsKey("transform")) {
|
||||
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
|
||||
TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
|
||||
data.order = GetInt(constraintMap, "order", 0);
|
||||
data.skinRequired = GetBoolean(constraintMap,"skin", false);
|
||||
|
||||
if (constraintMap.ContainsKey("bones")) {
|
||||
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
|
||||
BoneData bone = skeletonData.FindBone(boneName);
|
||||
if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
|
||||
data.bones.Add(bone);
|
||||
}
|
||||
}
|
||||
|
||||
string targetName = (string)constraintMap["target"];
|
||||
data.target = skeletonData.FindBone(targetName);
|
||||
if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName);
|
||||
|
||||
data.local = GetBoolean(constraintMap, "local", false);
|
||||
data.relative = GetBoolean(constraintMap, "relative", false);
|
||||
|
||||
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
|
||||
data.offsetX = GetFloat(constraintMap, "x", 0) * scale;
|
||||
data.offsetY = GetFloat(constraintMap, "y", 0) * scale;
|
||||
data.offsetScaleX = GetFloat(constraintMap, "scaleX", 0);
|
||||
data.offsetScaleY = GetFloat(constraintMap, "scaleY", 0);
|
||||
data.offsetShearY = GetFloat(constraintMap, "shearY", 0);
|
||||
|
||||
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
|
||||
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
|
||||
data.scaleMix = GetFloat(constraintMap, "scaleMix", 1);
|
||||
data.shearMix = GetFloat(constraintMap, "shearMix", 1);
|
||||
|
||||
skeletonData.transformConstraints.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Path constraints.
|
||||
if(root.ContainsKey("path")) {
|
||||
foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
|
||||
PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
|
||||
data.order = GetInt(constraintMap, "order", 0);
|
||||
data.skinRequired = GetBoolean(constraintMap,"skin", false);
|
||||
|
||||
if (constraintMap.ContainsKey("bones")) {
|
||||
foreach (string boneName in (List<Object>)constraintMap["bones"]) {
|
||||
BoneData bone = skeletonData.FindBone(boneName);
|
||||
if (bone == null) throw new Exception("Path bone not found: " + boneName);
|
||||
data.bones.Add(bone);
|
||||
}
|
||||
}
|
||||
|
||||
string targetName = (string)constraintMap["target"];
|
||||
data.target = skeletonData.FindSlot(targetName);
|
||||
if (data.target == null) throw new Exception("Path target slot not found: " + targetName);
|
||||
|
||||
data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
|
||||
data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
|
||||
data.rotateMode = (RotateMode)Enum.Parse(typeof(RotateMode), GetString(constraintMap, "rotateMode", "tangent"), true);
|
||||
data.offsetRotation = GetFloat(constraintMap, "rotation", 0);
|
||||
data.position = GetFloat(constraintMap, "position", 0);
|
||||
if (data.positionMode == PositionMode.Fixed) data.position *= scale;
|
||||
data.spacing = GetFloat(constraintMap, "spacing", 0);
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) data.spacing *= scale;
|
||||
data.rotateMix = GetFloat(constraintMap, "rotateMix", 1);
|
||||
data.translateMix = GetFloat(constraintMap, "translateMix", 1);
|
||||
|
||||
skeletonData.pathConstraints.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Skins.
|
||||
if (root.ContainsKey("skins")) {
|
||||
foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) {
|
||||
Skin skin = new Skin((string)skinMap["name"]);
|
||||
if (skinMap.ContainsKey("bones")) {
|
||||
foreach (string entryName in (List<Object>)skinMap["bones"]) {
|
||||
BoneData bone = skeletonData.FindBone(entryName);
|
||||
if (bone == null) throw new Exception("Skin bone not found: " + entryName);
|
||||
skin.bones.Add(bone);
|
||||
}
|
||||
}
|
||||
if (skinMap.ContainsKey("ik")) {
|
||||
foreach (string entryName in (List<Object>)skinMap["ik"]) {
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(entryName);
|
||||
if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName);
|
||||
skin.constraints.Add(constraint);
|
||||
}
|
||||
}
|
||||
if (skinMap.ContainsKey("transform")) {
|
||||
foreach (string entryName in (List<Object>)skinMap["transform"]) {
|
||||
TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName);
|
||||
if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName);
|
||||
skin.constraints.Add(constraint);
|
||||
}
|
||||
}
|
||||
if (skinMap.ContainsKey("path")) {
|
||||
foreach (string entryName in (List<Object>)skinMap["path"]) {
|
||||
PathConstraintData constraint = skeletonData.FindPathConstraint(entryName);
|
||||
if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName);
|
||||
skin.constraints.Add(constraint);
|
||||
}
|
||||
}
|
||||
if (skinMap.ContainsKey("attachments")) {
|
||||
foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
|
||||
int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
|
||||
foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
|
||||
try {
|
||||
Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
|
||||
if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
skeletonData.skins.Add(skin);
|
||||
if (skin.name == "default") skeletonData.defaultSkin = skin;
|
||||
}
|
||||
}
|
||||
|
||||
// Linked meshes.
|
||||
for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
|
||||
LinkedMesh linkedMesh = linkedMeshes[i];
|
||||
Skin skin = linkedMesh.skin == null ? skeletonData.defaultSkin : skeletonData.FindSkin(linkedMesh.skin);
|
||||
if (skin == null) throw new Exception("Slot not found: " + linkedMesh.skin);
|
||||
Attachment parent = skin.GetAttachment(linkedMesh.slotIndex, linkedMesh.parent);
|
||||
if (parent == null) throw new Exception("Parent mesh not found: " + linkedMesh.parent);
|
||||
linkedMesh.mesh.DeformAttachment = linkedMesh.inheritDeform ? (VertexAttachment)parent : linkedMesh.mesh;
|
||||
linkedMesh.mesh.ParentMesh = (MeshAttachment)parent;
|
||||
linkedMesh.mesh.UpdateUVs();
|
||||
}
|
||||
linkedMeshes.Clear();
|
||||
|
||||
// Events.
|
||||
if (root.ContainsKey("events")) {
|
||||
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["events"]) {
|
||||
var entryMap = (Dictionary<string, Object>)entry.Value;
|
||||
var data = new EventData(entry.Key);
|
||||
data.Int = GetInt(entryMap, "int", 0);
|
||||
data.Float = GetFloat(entryMap, "float", 0);
|
||||
data.String = GetString(entryMap, "string", string.Empty);
|
||||
data.AudioPath = GetString(entryMap, "audio", null);
|
||||
if (data.AudioPath != null) {
|
||||
data.Volume = GetFloat(entryMap, "volume", 1);
|
||||
data.Balance = GetFloat(entryMap, "balance", 0);
|
||||
}
|
||||
skeletonData.events.Add(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Animations.
|
||||
if (root.ContainsKey("animations")) {
|
||||
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)root["animations"]) {
|
||||
try {
|
||||
ReadAnimation((Dictionary<string, Object>)entry.Value, entry.Key, skeletonData);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Error reading animation: " + entry.Key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
skeletonData.bones.TrimExcess();
|
||||
skeletonData.slots.TrimExcess();
|
||||
skeletonData.skins.TrimExcess();
|
||||
skeletonData.events.TrimExcess();
|
||||
skeletonData.animations.TrimExcess();
|
||||
skeletonData.ikConstraints.TrimExcess();
|
||||
return skeletonData;
|
||||
}
|
||||
|
||||
private Attachment ReadAttachment (Dictionary<string, Object> map, Skin skin, int slotIndex, string name, SkeletonData skeletonData) {
|
||||
float scale = this.Scale;
|
||||
name = GetString(map, "name", name);
|
||||
|
||||
var typeName = GetString(map, "type", "region");
|
||||
var type = (AttachmentType)Enum.Parse(typeof(AttachmentType), typeName, true);
|
||||
|
||||
string path = GetString(map, "path", name);
|
||||
|
||||
switch (type) {
|
||||
case AttachmentType.Region:
|
||||
RegionAttachment region = attachmentLoader.NewRegionAttachment(skin, name, path);
|
||||
if (region == null) return null;
|
||||
region.Path = path;
|
||||
region.x = GetFloat(map, "x", 0) * scale;
|
||||
region.y = GetFloat(map, "y", 0) * scale;
|
||||
region.scaleX = GetFloat(map, "scaleX", 1);
|
||||
region.scaleY = GetFloat(map, "scaleY", 1);
|
||||
region.rotation = GetFloat(map, "rotation", 0);
|
||||
region.width = GetFloat(map, "width", 32) * scale;
|
||||
region.height = GetFloat(map, "height", 32) * scale;
|
||||
|
||||
if (map.ContainsKey("color")) {
|
||||
var color = (string)map["color"];
|
||||
region.r = ToColor(color, 0);
|
||||
region.g = ToColor(color, 1);
|
||||
region.b = ToColor(color, 2);
|
||||
region.a = ToColor(color, 3);
|
||||
}
|
||||
|
||||
region.UpdateOffset();
|
||||
return region;
|
||||
case AttachmentType.Boundingbox:
|
||||
BoundingBoxAttachment box = attachmentLoader.NewBoundingBoxAttachment(skin, name);
|
||||
if (box == null) return null;
|
||||
ReadVertices(map, box, GetInt(map, "vertexCount", 0) << 1);
|
||||
return box;
|
||||
case AttachmentType.Mesh:
|
||||
case AttachmentType.Linkedmesh: {
|
||||
MeshAttachment mesh = attachmentLoader.NewMeshAttachment(skin, name, path);
|
||||
if (mesh == null) return null;
|
||||
mesh.Path = path;
|
||||
|
||||
if (map.ContainsKey("color")) {
|
||||
var color = (string)map["color"];
|
||||
mesh.r = ToColor(color, 0);
|
||||
mesh.g = ToColor(color, 1);
|
||||
mesh.b = ToColor(color, 2);
|
||||
mesh.a = ToColor(color, 3);
|
||||
}
|
||||
|
||||
mesh.Width = GetFloat(map, "width", 0) * scale;
|
||||
mesh.Height = GetFloat(map, "height", 0) * scale;
|
||||
|
||||
string parent = GetString(map, "parent", null);
|
||||
if (parent != null) {
|
||||
linkedMeshes.Add(new LinkedMesh(mesh, GetString(map, "skin", null), slotIndex, parent, GetBoolean(map, "deform", true)));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
float[] uvs = GetFloatArray(map, "uvs", 1);
|
||||
ReadVertices(map, mesh, uvs.Length);
|
||||
mesh.triangles = GetIntArray(map, "triangles");
|
||||
mesh.regionUVs = uvs;
|
||||
mesh.UpdateUVs();
|
||||
|
||||
if (map.ContainsKey("hull")) mesh.HullLength = GetInt(map, "hull", 0) * 2;
|
||||
if (map.ContainsKey("edges")) mesh.Edges = GetIntArray(map, "edges");
|
||||
return mesh;
|
||||
}
|
||||
case AttachmentType.Path: {
|
||||
PathAttachment pathAttachment = attachmentLoader.NewPathAttachment(skin, name);
|
||||
if (pathAttachment == null) return null;
|
||||
pathAttachment.closed = GetBoolean(map, "closed", false);
|
||||
pathAttachment.constantSpeed = GetBoolean(map, "constantSpeed", true);
|
||||
|
||||
int vertexCount = GetInt(map, "vertexCount", 0);
|
||||
ReadVertices(map, pathAttachment, vertexCount << 1);
|
||||
|
||||
// potential BOZO see Java impl
|
||||
pathAttachment.lengths = GetFloatArray(map, "lengths", scale);
|
||||
return pathAttachment;
|
||||
}
|
||||
case AttachmentType.Point: {
|
||||
PointAttachment point = attachmentLoader.NewPointAttachment(skin, name);
|
||||
if (point == null) return null;
|
||||
point.x = GetFloat(map, "x", 0) * scale;
|
||||
point.y = GetFloat(map, "y", 0) * scale;
|
||||
point.rotation = GetFloat(map, "rotation", 0);
|
||||
|
||||
//string color = GetString(map, "color", null);
|
||||
//if (color != null) point.color = color;
|
||||
return point;
|
||||
}
|
||||
case AttachmentType.Clipping: {
|
||||
ClippingAttachment clip = attachmentLoader.NewClippingAttachment(skin, name);
|
||||
if (clip == null) return null;
|
||||
|
||||
string end = GetString(map, "end", null);
|
||||
if (end != null) {
|
||||
SlotData slot = skeletonData.FindSlot(end);
|
||||
if (slot == null) throw new Exception("Clipping end slot not found: " + end);
|
||||
clip.EndSlot = slot;
|
||||
}
|
||||
|
||||
ReadVertices(map, clip, GetInt(map, "vertexCount", 0) << 1);
|
||||
|
||||
//string color = GetString(map, "color", null);
|
||||
// if (color != null) clip.color = color;
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ReadVertices (Dictionary<string, Object> map, VertexAttachment attachment, int verticesLength) {
|
||||
attachment.WorldVerticesLength = verticesLength;
|
||||
float[] vertices = GetFloatArray(map, "vertices", 1);
|
||||
float scale = Scale;
|
||||
if (verticesLength == vertices.Length) {
|
||||
if (scale != 1) {
|
||||
for (int i = 0; i < vertices.Length; i++) {
|
||||
vertices[i] *= scale;
|
||||
}
|
||||
}
|
||||
attachment.vertices = vertices;
|
||||
return;
|
||||
}
|
||||
ExposedList<float> weights = new ExposedList<float>(verticesLength * 3 * 3);
|
||||
ExposedList<int> bones = new ExposedList<int>(verticesLength * 3);
|
||||
for (int i = 0, n = vertices.Length; i < n;) {
|
||||
int boneCount = (int)vertices[i++];
|
||||
bones.Add(boneCount);
|
||||
for (int nn = i + boneCount * 4; i < nn; i += 4) {
|
||||
bones.Add((int)vertices[i]);
|
||||
weights.Add(vertices[i + 1] * this.Scale);
|
||||
weights.Add(vertices[i + 2] * this.Scale);
|
||||
weights.Add(vertices[i + 3]);
|
||||
}
|
||||
}
|
||||
attachment.bones = bones.ToArray();
|
||||
attachment.vertices = weights.ToArray();
|
||||
}
|
||||
|
||||
private void ReadAnimation (Dictionary<string, Object> map, string name, SkeletonData skeletonData) {
|
||||
var scale = this.Scale;
|
||||
var timelines = new ExposedList<Timeline>();
|
||||
float duration = 0;
|
||||
|
||||
// Slot timelines.
|
||||
if (map.ContainsKey("slots")) {
|
||||
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["slots"]) {
|
||||
string slotName = entry.Key;
|
||||
int slotIndex = skeletonData.FindSlotIndex(slotName);
|
||||
var timelineMap = (Dictionary<string, Object>)entry.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "attachment") {
|
||||
var timeline = new AttachmentTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
|
||||
} else if (timelineName == "color") {
|
||||
var timeline = new ColorTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
string c = (string)valueMap["color"];
|
||||
timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * ColorTimeline.ENTRIES]);
|
||||
|
||||
} else if (timelineName == "twoColor") {
|
||||
var timeline = new TwoColorTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
string light = (string)valueMap["light"];
|
||||
string dark = (string)valueMap["dark"];
|
||||
timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
|
||||
ToColor(dark, 0, 6), ToColor(dark, 1, 6), ToColor(dark, 2, 6));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TwoColorTimeline.ENTRIES]);
|
||||
|
||||
} else
|
||||
throw new Exception("Invalid timeline type for a slot: " + timelineName + " (" + slotName + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bone timelines.
|
||||
if (map.ContainsKey("bones")) {
|
||||
foreach (KeyValuePair<string, Object> entry in (Dictionary<string, Object>)map["bones"]) {
|
||||
string boneName = entry.Key;
|
||||
int boneIndex = skeletonData.FindBoneIndex(boneName);
|
||||
if (boneIndex == -1) throw new Exception("Bone not found: " + boneName);
|
||||
var timelineMap = (Dictionary<string, Object>)entry.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "rotate") {
|
||||
var timeline = new RotateTimeline(values.Count);
|
||||
timeline.boneIndex = boneIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * RotateTimeline.ENTRIES]);
|
||||
|
||||
} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
|
||||
TranslateTimeline timeline;
|
||||
float timelineScale = 1, defaultValue = 0;
|
||||
if (timelineName == "scale") {
|
||||
timeline = new ScaleTimeline(values.Count);
|
||||
defaultValue = 1;
|
||||
}
|
||||
else if (timelineName == "shear")
|
||||
timeline = new ShearTimeline(values.Count);
|
||||
else {
|
||||
timeline = new TranslateTimeline(values.Count);
|
||||
timelineScale = scale;
|
||||
}
|
||||
timeline.boneIndex = boneIndex;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float time = GetFloat(valueMap, "time", 0);
|
||||
float x = GetFloat(valueMap, "x", defaultValue);
|
||||
float y = GetFloat(valueMap, "y", defaultValue);
|
||||
timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TranslateTimeline.ENTRIES]);
|
||||
|
||||
} else
|
||||
throw new Exception("Invalid timeline type for a bone: " + timelineName + " (" + boneName + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IK constraint timelines.
|
||||
if (map.ContainsKey("ik")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["ik"]) {
|
||||
IkConstraintData constraint = skeletonData.FindIkConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new IkConstraintTimeline(values.Count);
|
||||
timeline.ikConstraintIndex = skeletonData.ikConstraints.IndexOf(constraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1),
|
||||
GetFloat(valueMap, "softness", 0) * scale, GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
|
||||
GetBoolean(valueMap, "compress", false), GetBoolean(valueMap, "stretch", false));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * IkConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform constraint timelines.
|
||||
if (map.ContainsKey("transform")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["transform"]) {
|
||||
TransformConstraintData constraint = skeletonData.FindTransformConstraint(constraintMap.Key);
|
||||
var values = (List<Object>)constraintMap.Value;
|
||||
var timeline = new TransformConstraintTimeline(values.Count);
|
||||
timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1),
|
||||
GetFloat(valueMap, "translateMix", 1), GetFloat(valueMap, "scaleMix", 1), GetFloat(valueMap, "shearMix", 1));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * TransformConstraintTimeline.ENTRIES]);
|
||||
}
|
||||
}
|
||||
|
||||
// Path constraint timelines.
|
||||
if (map.ContainsKey("path")) {
|
||||
foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["path"]) {
|
||||
int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
|
||||
if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
|
||||
PathConstraintData data = skeletonData.pathConstraints.Items[index];
|
||||
var timelineMap = (Dictionary<string, Object>)constraintMap.Value;
|
||||
foreach (KeyValuePair<string, Object> timelineEntry in timelineMap) {
|
||||
var values = (List<Object>)timelineEntry.Value;
|
||||
var timelineName = (string)timelineEntry.Key;
|
||||
if (timelineName == "position" || timelineName == "spacing") {
|
||||
PathConstraintPositionTimeline timeline;
|
||||
float timelineScale = 1;
|
||||
if (timelineName == "spacing") {
|
||||
timeline = new PathConstraintSpacingTimeline(values.Count);
|
||||
if (data.spacingMode == SpacingMode.Length || data.spacingMode == SpacingMode.Fixed) timelineScale = scale;
|
||||
}
|
||||
else {
|
||||
timeline = new PathConstraintPositionTimeline(values.Count);
|
||||
if (data.positionMode == PositionMode.Fixed) timelineScale = scale;
|
||||
}
|
||||
timeline.pathConstraintIndex = index;
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale);
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintPositionTimeline.ENTRIES]);
|
||||
}
|
||||
else if (timelineName == "mix") {
|
||||
PathConstraintMixTimeline timeline = new PathConstraintMixTimeline(values.Count);
|
||||
timeline.pathConstraintIndex = index;
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1),
|
||||
GetFloat(valueMap, "translateMix", 1));
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[(timeline.FrameCount - 1) * PathConstraintMixTimeline.ENTRIES]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deform timelines.
|
||||
if (map.ContainsKey("deform")) {
|
||||
foreach (KeyValuePair<string, Object> deformMap in (Dictionary<string, Object>)map["deform"]) {
|
||||
Skin skin = skeletonData.FindSkin(deformMap.Key);
|
||||
foreach (KeyValuePair<string, Object> slotMap in (Dictionary<string, Object>)deformMap.Value) {
|
||||
int slotIndex = skeletonData.FindSlotIndex(slotMap.Key);
|
||||
if (slotIndex == -1) throw new Exception("Slot not found: " + slotMap.Key);
|
||||
foreach (KeyValuePair<string, Object> timelineMap in (Dictionary<string, Object>)slotMap.Value) {
|
||||
var values = (List<Object>)timelineMap.Value;
|
||||
VertexAttachment attachment = (VertexAttachment)skin.GetAttachment(slotIndex, timelineMap.Key);
|
||||
if (attachment == null) throw new Exception("Deform attachment not found: " + timelineMap.Key);
|
||||
bool weighted = attachment.bones != null;
|
||||
float[] vertices = attachment.vertices;
|
||||
int deformLength = weighted ? vertices.Length / 3 * 2 : vertices.Length;
|
||||
|
||||
var timeline = new DeformTimeline(values.Count);
|
||||
timeline.slotIndex = slotIndex;
|
||||
timeline.attachment = attachment;
|
||||
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> valueMap in values) {
|
||||
float[] deform;
|
||||
if (!valueMap.ContainsKey("vertices")) {
|
||||
deform = weighted ? new float[deformLength] : vertices;
|
||||
} else {
|
||||
deform = new float[deformLength];
|
||||
int start = GetInt(valueMap, "offset", 0);
|
||||
float[] verticesValue = GetFloatArray(valueMap, "vertices", 1);
|
||||
Array.Copy(verticesValue, 0, deform, start, verticesValue.Length);
|
||||
if (scale != 1) {
|
||||
for (int i = start, n = i + verticesValue.Length; i < n; i++)
|
||||
deform[i] *= scale;
|
||||
}
|
||||
|
||||
if (!weighted) {
|
||||
for (int i = 0; i < deformLength; i++)
|
||||
deform[i] += vertices[i];
|
||||
}
|
||||
}
|
||||
|
||||
timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform);
|
||||
ReadCurve(valueMap, timeline, frameIndex);
|
||||
frameIndex++;
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw order timeline.
|
||||
if (map.ContainsKey("drawOrder") || map.ContainsKey("draworder")) {
|
||||
var values = (List<Object>)map[map.ContainsKey("drawOrder") ? "drawOrder" : "draworder"];
|
||||
var timeline = new DrawOrderTimeline(values.Count);
|
||||
int slotCount = skeletonData.slots.Count;
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> drawOrderMap in values) {
|
||||
int[] drawOrder = null;
|
||||
if (drawOrderMap.ContainsKey("offsets")) {
|
||||
drawOrder = new int[slotCount];
|
||||
for (int i = slotCount - 1; i >= 0; i--)
|
||||
drawOrder[i] = -1;
|
||||
var offsets = (List<Object>)drawOrderMap["offsets"];
|
||||
int[] unchanged = new int[slotCount - offsets.Count];
|
||||
int originalIndex = 0, unchangedIndex = 0;
|
||||
foreach (Dictionary<string, Object> offsetMap in offsets) {
|
||||
int slotIndex = skeletonData.FindSlotIndex((string)offsetMap["slot"]);
|
||||
if (slotIndex == -1) throw new Exception("Slot not found: " + offsetMap["slot"]);
|
||||
// Collect unchanged items.
|
||||
while (originalIndex != slotIndex)
|
||||
unchanged[unchangedIndex++] = originalIndex++;
|
||||
// Set changed items.
|
||||
int index = originalIndex + (int)(float)offsetMap["offset"];
|
||||
drawOrder[index] = originalIndex++;
|
||||
}
|
||||
// Collect remaining unchanged items.
|
||||
while (originalIndex < slotCount)
|
||||
unchanged[unchangedIndex++] = originalIndex++;
|
||||
// Fill in unchanged items.
|
||||
for (int i = slotCount - 1; i >= 0; i--)
|
||||
if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
|
||||
}
|
||||
timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
}
|
||||
|
||||
// Event timeline.
|
||||
if (map.ContainsKey("events")) {
|
||||
var eventsMap = (List<Object>)map["events"];
|
||||
var timeline = new EventTimeline(eventsMap.Count);
|
||||
int frameIndex = 0;
|
||||
foreach (Dictionary<string, Object> eventMap in eventsMap) {
|
||||
EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
|
||||
if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
|
||||
var e = new Event(GetFloat(eventMap, "time", 0), eventData) {
|
||||
intValue = GetInt(eventMap, "int", eventData.Int),
|
||||
floatValue = GetFloat(eventMap, "float", eventData.Float),
|
||||
stringValue = GetString(eventMap, "string", eventData.String)
|
||||
};
|
||||
if (e.data.AudioPath != null) {
|
||||
e.volume = GetFloat(eventMap, "volume", eventData.Volume);
|
||||
e.balance = GetFloat(eventMap, "balance", eventData.Balance);
|
||||
}
|
||||
timeline.SetFrame(frameIndex++, e);
|
||||
}
|
||||
timelines.Add(timeline);
|
||||
duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
|
||||
}
|
||||
|
||||
timelines.TrimExcess();
|
||||
skeletonData.animations.Add(new Animation(name, timelines, duration));
|
||||
}
|
||||
|
||||
static void ReadCurve (Dictionary<string, Object> valueMap, CurveTimeline timeline, int frameIndex) {
|
||||
if (!valueMap.ContainsKey("curve"))
|
||||
return;
|
||||
Object curveObject = valueMap["curve"];
|
||||
if (curveObject is string)
|
||||
timeline.SetStepped(frameIndex);
|
||||
else
|
||||
timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1));
|
||||
}
|
||||
|
||||
internal class LinkedMesh {
|
||||
internal string parent, skin;
|
||||
internal int slotIndex;
|
||||
internal MeshAttachment mesh;
|
||||
internal bool inheritDeform;
|
||||
|
||||
public LinkedMesh (MeshAttachment mesh, string skin, int slotIndex, string parent, bool inheritDeform) {
|
||||
this.mesh = mesh;
|
||||
this.skin = skin;
|
||||
this.slotIndex = slotIndex;
|
||||
this.parent = parent;
|
||||
this.inheritDeform = inheritDeform;
|
||||
}
|
||||
}
|
||||
|
||||
static float[] GetFloatArray(Dictionary<string, Object> map, string name, float scale) {
|
||||
var list = (List<Object>)map[name];
|
||||
var values = new float[list.Count];
|
||||
if (scale == 1) {
|
||||
for (int i = 0, n = list.Count; i < n; i++)
|
||||
values[i] = (float)list[i];
|
||||
} else {
|
||||
for (int i = 0, n = list.Count; i < n; i++)
|
||||
values[i] = (float)list[i] * scale;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static int[] GetIntArray(Dictionary<string, Object> map, string name) {
|
||||
var list = (List<Object>)map[name];
|
||||
var values = new int[list.Count];
|
||||
for (int i = 0, n = list.Count; i < n; i++)
|
||||
values[i] = (int)(float)list[i];
|
||||
return values;
|
||||
}
|
||||
|
||||
static float GetFloat(Dictionary<string, Object> map, string name, float defaultValue) {
|
||||
if (!map.ContainsKey(name))
|
||||
return defaultValue;
|
||||
return (float)map[name];
|
||||
}
|
||||
|
||||
static int GetInt(Dictionary<string, Object> map, string name, int defaultValue) {
|
||||
if (!map.ContainsKey(name))
|
||||
return defaultValue;
|
||||
return (int)(float)map[name];
|
||||
}
|
||||
|
||||
static bool GetBoolean(Dictionary<string, Object> map, string name, bool defaultValue) {
|
||||
if (!map.ContainsKey(name))
|
||||
return defaultValue;
|
||||
return (bool)map[name];
|
||||
}
|
||||
|
||||
static string GetString(Dictionary<string, Object> map, string name, string defaultValue) {
|
||||
if (!map.ContainsKey(name))
|
||||
return defaultValue;
|
||||
return (string)map[name];
|
||||
}
|
||||
|
||||
static float ToColor(string hexString, int colorIndex, int expectedLength = 8) {
|
||||
if (hexString.Length != expectedLength)
|
||||
throw new ArgumentException("Color hexidecimal length must be " + expectedLength + ", recieved: " + hexString, "hexString");
|
||||
return Convert.ToInt32(hexString.Substring(colorIndex * 2, 2), 16) / (float)255;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c4ab7992894bdb44a480981b1953f76
|
||||
timeCreated: 1456265154
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,193 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
using System.Collections.Generic;
|
||||
using Spine.Collections;
|
||||
|
||||
namespace Spine {
|
||||
/// <summary>Stores attachments by slot index and attachment name.
|
||||
/// <para>See SkeletonData <see cref="Spine.SkeletonData.DefaultSkin"/>, Skeleton <see cref="Spine.Skeleton.Skin"/>, and
|
||||
/// <a href="http://esotericsoftware.com/spine-runtime-skins">Runtime skins</a> in the Spine Runtimes Guide.</para>
|
||||
/// </summary>
|
||||
public class Skin {
|
||||
internal string name;
|
||||
private OrderedDictionary<SkinEntry, Attachment> attachments = new OrderedDictionary<SkinEntry, Attachment>(SkinEntryComparer.Instance);
|
||||
internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
|
||||
internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
|
||||
|
||||
public string Name { get { return name; } }
|
||||
public OrderedDictionary<SkinEntry, Attachment> Attachments { get { return attachments; } }
|
||||
public ExposedList<BoneData> Bones { get { return bones; } }
|
||||
public ExposedList<ConstraintData> Constraints { get { return constraints; } }
|
||||
|
||||
public Skin (string name) {
|
||||
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/// <summary>Adds an attachment to the skin for the specified slot index and name.
|
||||
/// If the name already exists for the slot, the previous value is replaced.</summary>
|
||||
public void SetAttachment (int slotIndex, string name, Attachment attachment) {
|
||||
if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
|
||||
if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0.");
|
||||
attachments[new SkinEntry(slotIndex, name, attachment)] = attachment;
|
||||
}
|
||||
|
||||
///<summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
|
||||
public void AddSkin (Skin skin) {
|
||||
foreach (BoneData data in skin.bones)
|
||||
if (!bones.Contains(data)) bones.Add(data);
|
||||
|
||||
foreach (ConstraintData data in skin.constraints)
|
||||
if (!constraints.Contains(data)) constraints.Add(data);
|
||||
|
||||
foreach (SkinEntry entry in skin.attachments.Keys)
|
||||
SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment);
|
||||
}
|
||||
|
||||
///<summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
|
||||
public void CopySkin (Skin skin) {
|
||||
foreach (BoneData data in skin.bones)
|
||||
if (!bones.Contains(data)) bones.Add(data);
|
||||
|
||||
foreach (ConstraintData data in skin.constraints)
|
||||
if (!constraints.Contains(data)) constraints.Add(data);
|
||||
|
||||
foreach (SkinEntry entry in skin.attachments.Keys) {
|
||||
if (entry.Attachment is MeshAttachment)
|
||||
SetAttachment(entry.SlotIndex, entry.Name,
|
||||
entry.Attachment != null ? ((MeshAttachment)entry.Attachment).NewLinkedMesh() : null);
|
||||
else
|
||||
SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment != null ? entry.Attachment.Copy() : null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
|
||||
/// <returns>May be null.</returns>
|
||||
public Attachment GetAttachment (int slotIndex, string name) {
|
||||
var lookup = new SkinEntry(slotIndex, name, null);
|
||||
Attachment attachment = null;
|
||||
bool containsKey = attachments.TryGetValue(lookup, out attachment);
|
||||
return containsKey ? attachment : null;
|
||||
}
|
||||
|
||||
/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
|
||||
public void RemoveAttachment (int slotIndex, string name) {
|
||||
if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
|
||||
var lookup = new SkinEntry(slotIndex, name, null);
|
||||
attachments.Remove(lookup);
|
||||
}
|
||||
|
||||
///<summary>Returns all attachments contained in this skin.</summary>
|
||||
public ICollection<SkinEntry> GetAttachments () {
|
||||
return this.attachments.Keys;
|
||||
}
|
||||
|
||||
/// <summary>Returns all attachments in this skin for the specified slot index.</summary>
|
||||
/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
|
||||
public void GetAttachments (int slotIndex, List<SkinEntry> attachments) {
|
||||
foreach (SkinEntry entry in this.attachments.Keys)
|
||||
if (entry.SlotIndex == slotIndex) attachments.Add(entry);
|
||||
}
|
||||
|
||||
///<summary>Clears all attachments, bones, and constraints.</summary>
|
||||
public void Clear () {
|
||||
attachments.Clear();
|
||||
bones.Clear();
|
||||
constraints.Clear();
|
||||
}
|
||||
|
||||
override public string ToString () {
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
|
||||
internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
|
||||
foreach (SkinEntry entry in oldSkin.attachments.Keys) {
|
||||
int slotIndex = entry.SlotIndex;
|
||||
Slot slot = skeleton.slots.Items[slotIndex];
|
||||
if (slot.Attachment == entry.Attachment) {
|
||||
Attachment attachment = GetAttachment(slotIndex, entry.Name);
|
||||
if (attachment != null) slot.Attachment = attachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stores an entry in the skin consisting of the slot index, name, and attachment.</summary>
|
||||
public struct SkinEntry {
|
||||
private readonly int slotIndex;
|
||||
private readonly string name;
|
||||
private readonly Attachment attachment;
|
||||
internal readonly int hashCode;
|
||||
|
||||
public SkinEntry (int slotIndex, string name, Attachment attachment) {
|
||||
this.slotIndex = slotIndex;
|
||||
this.name = name;
|
||||
this.attachment = attachment;
|
||||
this.hashCode = this.name.GetHashCode() + this.slotIndex * 37;
|
||||
}
|
||||
|
||||
public int SlotIndex {
|
||||
get {
|
||||
return slotIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor.</summary>
|
||||
public String Name {
|
||||
get {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public Attachment Attachment {
|
||||
get {
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison.
|
||||
class SkinEntryComparer : IEqualityComparer<SkinEntry> {
|
||||
internal static readonly SkinEntryComparer Instance = new SkinEntryComparer();
|
||||
|
||||
bool IEqualityComparer<SkinEntry>.Equals (SkinEntry e1, SkinEntry e2) {
|
||||
if (e1.SlotIndex != e2.SlotIndex) return false;
|
||||
if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int IEqualityComparer<SkinEntry>.GetHashCode (SkinEntry e) {
|
||||
return e.Name.GetHashCode() + e.SlotIndex * 37;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7df8caa3a771f464e803316a6b18c909
|
||||
timeCreated: 1456265154
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,196 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
|
||||
/// <summary>
|
||||
/// Stores a slot's current pose. Slots organize attachments for {@link Skeleton#drawOrder} purposes and provide a place to store
|
||||
/// state for an attachment.State cannot be stored in an attachment itself because attachments are stateless and may be shared
|
||||
/// across multiple skeletons.
|
||||
/// </summary>
|
||||
public class Slot {
|
||||
internal SlotData data;
|
||||
internal Bone bone;
|
||||
internal float r, g, b, a;
|
||||
internal float r2, g2, b2;
|
||||
internal bool hasSecondColor;
|
||||
internal Attachment attachment;
|
||||
internal float attachmentTime;
|
||||
internal ExposedList<float> deform = new ExposedList<float>();
|
||||
internal int attachmentState;
|
||||
|
||||
public Slot (SlotData data, Bone bone) {
|
||||
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
|
||||
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
|
||||
this.data = data;
|
||||
this.bone = bone;
|
||||
|
||||
// darkColor = data.darkColor == null ? null : new Color();
|
||||
if (data.hasSecondColor) {
|
||||
r2 = g2 = b2 = 0;
|
||||
}
|
||||
|
||||
SetToSetupPose();
|
||||
}
|
||||
|
||||
/// <summary>Copy constructor.</summary>
|
||||
public Slot(Slot slot, Bone bone) {
|
||||
if (slot == null) throw new ArgumentNullException("slot", "slot cannot be null.");
|
||||
if (bone == null) throw new ArgumentNullException("bone", "bone cannot be null.");
|
||||
data = slot.data;
|
||||
this.bone = bone;
|
||||
r = slot.r;
|
||||
g = slot.g;
|
||||
b = slot.b;
|
||||
a = slot.a;
|
||||
|
||||
// darkColor = slot.darkColor == null ? null : new Color(slot.darkColor);
|
||||
if (slot.hasSecondColor) {
|
||||
r2 = slot.r2;
|
||||
g2 = slot.g2;
|
||||
b2 = slot.b2;
|
||||
} else {
|
||||
r2 = g2 = b2 = 0;
|
||||
}
|
||||
hasSecondColor = slot.hasSecondColor;
|
||||
|
||||
attachment = slot.attachment;
|
||||
attachmentTime = slot.attachmentTime;
|
||||
deform.AddRange(slot.deform);
|
||||
}
|
||||
|
||||
/// <summary>The slot's setup pose data.</summary>
|
||||
public SlotData Data { get { return data; } }
|
||||
/// <summary>The bone this slot belongs to.</summary>
|
||||
public Bone Bone { get { return bone; } }
|
||||
/// <summary>The skeleton this slot belongs to.</summary>
|
||||
public Skeleton Skeleton { get { return bone.skeleton; } }
|
||||
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
|
||||
/// color tinting.</summary>
|
||||
public float R { get { return r; } set { r = value; } }
|
||||
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
|
||||
/// color tinting.</summary>
|
||||
public float G { get { return g; } set { g = value; } }
|
||||
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
|
||||
/// color tinting.</summary>
|
||||
public float B { get { return b; } set { b = value; } }
|
||||
/// <summary>The color used to tint the slot's attachment. If <see cref="HasSecondColor"/> is set, this is used as the light color for two
|
||||
/// color tinting.</summary>
|
||||
public float A { get { return a; } set { a = value; } }
|
||||
|
||||
public void ClampColor() {
|
||||
r = MathUtils.Clamp(r, 0, 1);
|
||||
g = MathUtils.Clamp(g, 0, 1);
|
||||
b = MathUtils.Clamp(b, 0, 1);
|
||||
a = MathUtils.Clamp(a, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
|
||||
/// <seealso cref="HasSecondColor"/>
|
||||
public float R2 { get { return r2; } set { r2 = value; } }
|
||||
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
|
||||
/// <seealso cref="HasSecondColor"/>
|
||||
public float G2 { get { return g2; } set { g2 = value; } }
|
||||
/// <summary>The dark color used to tint the slot's attachment for two color tinting, ignored if two color tinting is not used.</summary>
|
||||
/// <seealso cref="HasSecondColor"/>
|
||||
public float B2 { get { return b2; } set { b2 = value; } }
|
||||
/// <summary>Whether R2 G2 B2 are used to tint the slot's attachment for two color tinting. False if two color tinting is not used.</summary>
|
||||
public bool HasSecondColor { get { return data.hasSecondColor; } set { data.hasSecondColor = value; } }
|
||||
|
||||
public void ClampSecondColor () {
|
||||
r2 = MathUtils.Clamp(r2, 0, 1);
|
||||
g2 = MathUtils.Clamp(g2, 0, 1);
|
||||
b2 = MathUtils.Clamp(b2, 0, 1);
|
||||
}
|
||||
|
||||
public Attachment Attachment {
|
||||
/// <summary>The current attachment for the slot, or null if the slot has no attachment.</summary>
|
||||
get { return attachment; }
|
||||
/// <summary>
|
||||
/// Sets the slot's attachment and, if the attachment changed, resets <see cref="AttachmentTime"/> and clears
|
||||
/// <see cref="Deform">.</summary>
|
||||
/// <param name="value">May be null.</param>
|
||||
set {
|
||||
if (attachment == value) return;
|
||||
attachment = value;
|
||||
attachmentTime = bone.skeleton.time;
|
||||
deform.Clear(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> The time that has elapsed since the last time the attachment was set or cleared. Relies on Skeleton
|
||||
/// <see cref="Skeleton.Time"/></summary>
|
||||
public float AttachmentTime {
|
||||
get { return bone.skeleton.time - attachmentTime; }
|
||||
set { attachmentTime = bone.skeleton.time - value; }
|
||||
}
|
||||
|
||||
/// <summary> Vertices to deform the slot's attachment. For an unweighted mesh, the entries are local positions for each vertex. For a
|
||||
/// weighted mesh, the entries are an offset for each vertex which will be added to the mesh's local vertex positions.
|
||||
/// <para />
|
||||
/// See <see cref="VertexAttachment.ComputeWorldVertices(Slot, int, int, float[], int, int)"/> and <see cref="DeformTimeline"/>.</summary>
|
||||
public ExposedList<float> Deform {
|
||||
get {
|
||||
return deform;
|
||||
}
|
||||
set {
|
||||
if (deform == null) throw new ArgumentNullException("deform", "deform cannot be null.");
|
||||
deform = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Sets this slot to the setup pose.</summary>
|
||||
public void SetToSetupPose () {
|
||||
r = data.r;
|
||||
g = data.g;
|
||||
b = data.b;
|
||||
a = data.a;
|
||||
|
||||
// if (darkColor != null) darkColor.set(data.darkColor);
|
||||
if (HasSecondColor) {
|
||||
r2 = data.r2;
|
||||
g2 = data.g2;
|
||||
b2 = data.b2;
|
||||
}
|
||||
|
||||
if (data.attachmentName == null)
|
||||
Attachment = null;
|
||||
else {
|
||||
attachment = null;
|
||||
Attachment = bone.skeleton.GetAttachment(data.index, data.attachmentName);
|
||||
}
|
||||
}
|
||||
|
||||
override public string ToString () {
|
||||
return data.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6974c4b5c87687140a2417201ea43066
|
||||
timeCreated: 1456265154
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,77 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
|
||||
namespace Spine {
|
||||
public class SlotData {
|
||||
internal int index;
|
||||
internal string name;
|
||||
internal BoneData boneData;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float r2 = 0, g2 = 0, b2 = 0;
|
||||
internal bool hasSecondColor = false;
|
||||
internal string attachmentName;
|
||||
internal BlendMode blendMode;
|
||||
|
||||
/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
|
||||
public int Index { get { return index; } }
|
||||
/// <summary>The name of the slot, which is unique across all slots in the skeleton.</summary>
|
||||
public string Name { get { return name; } }
|
||||
/// <summary>The bone this slot belongs to.</summary>
|
||||
public BoneData BoneData { get { return boneData; } }
|
||||
public float R { get { return r; } set { r = value; } }
|
||||
public float G { get { return g; } set { g = value; } }
|
||||
public float B { get { return b; } set { b = value; } }
|
||||
public float A { get { return a; } set { a = value; } }
|
||||
|
||||
public float R2 { get { return r2; } set { r2 = value; } }
|
||||
public float G2 { get { return g2; } set { g2 = value; } }
|
||||
public float B2 { get { return b2; } set { b2 = value; } }
|
||||
public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
|
||||
|
||||
/// <summary>The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible.</summary>
|
||||
public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
|
||||
/// <summary>The blend mode for drawing the slot's attachment.</summary>
|
||||
public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
|
||||
|
||||
public SlotData (int index, String name, BoneData boneData) {
|
||||
if (index < 0) throw new ArgumentException ("index must be >= 0.", "index");
|
||||
if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
|
||||
if (boneData == null) throw new ArgumentNullException("boneData", "boneData cannot be null.");
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.boneData = boneData;
|
||||
}
|
||||
|
||||
override public string ToString () {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f28cb47bc1e8b434c85e6f69b2c9e15e
|
||||
timeCreated: 1456265156
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,262 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
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:
|
||||
@@ -1,215 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4224df6e20549f0449154531ae080201
|
||||
timeCreated: 1567002861
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,39 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79a44aba1f342f440965874280b4c318
|
||||
timeCreated: 1536412736
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,121 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95813afe390494344a6ce2cbc8bfb7d1
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,157 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f21c9538588898a45a3da22bf4779ab3
|
||||
timeCreated: 1591121072
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,322 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc23a4f220b20024ab0592719f92587d
|
||||
timeCreated: 1592849332
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,251 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d247ba06193faa74d9335f5481b2b56c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,825 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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:
|
||||
@@ -1,661 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9db98c60740638449864eb028fbe7ad
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a361f5ac799a5149b340f9e20da27d1
|
||||
folderAsset: yes
|
||||
timeCreated: 1457405502
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,151 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c0b968d1e7333b499e347acb644f1c1
|
||||
timeCreated: 1458045480
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,269 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c70a5b35f6ff2541aed8e8346b7e4d5
|
||||
timeCreated: 1457405791
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,711 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e075b9a3e08e2f74fbd651c858ab16ed
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7236dbdc6a4e5a4989483dac97aee0b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,211 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c8717e10b272bf42b05d363ac2679a6
|
||||
timeCreated: 1588789074
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,212 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26947ae098a8447408d80c0c86e35b48
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6e0caaafe294de48af468a6a9321473
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -1,469 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f726fb798ad621458c431cb9966d91d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,243 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b238dfcde8209044b97d23f62bcaadf6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,62 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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 ();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 522dbfcc6c916df4396f14f35048d185
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfdd78a071ca1a04bb64c6cc41e14aa0
|
||||
folderAsset: yes
|
||||
timeCreated: 1496447038
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,230 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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:
|
||||
@@ -1,87 +0,0 @@
|
||||
%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: SkeletonGraphicDefault
|
||||
m_Shader: {fileID: 4800000, guid: fa95b0fb6983c0f40a152e6f9aa82bfb, type: 3}
|
||||
m_ShaderKeywords:
|
||||
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
|
||||
- _Parallax: 0.02
|
||||
- _SrcBlend: 1
|
||||
- _Stencil: 0
|
||||
- _StencilComp: 8
|
||||
- _StencilOp: 0
|
||||
- _StencilReadMask: 255
|
||||
- _StencilWriteMask: 255
|
||||
- _UVSec: 0
|
||||
- _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}
|
||||
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b66cf7a186d13054989b33a5c90044e4
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,95 +0,0 @@
|
||||
%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}
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4ee0f8f4be17434aa3df5774a03b366
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,79 +0,0 @@
|
||||
%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}
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfcea0e11aa80bb4b8d05790b905fc31
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,88 +0,0 @@
|
||||
%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}
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94fe565c79b0aeb418cd05e4f1f8343c
|
||||
timeCreated: 1455140322
|
||||
licenseType: Free
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,178 +0,0 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d07866ade25bd0b44a7bb1d59bacf4cb
|
||||
timeCreated: 1563322425
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef8189a68a74bec4eba582e65fb98dbd
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -1,128 +0,0 @@
|
||||
// Spine/Skeleton PMA Screen
|
||||
// - single color multiply tint
|
||||
// - unlit
|
||||
// - Premultiplied alpha Multiply blending
|
||||
// - No depth, no backface culling, no fog.
|
||||
// - ShadowCaster pass
|
||||
|
||||
Shader "Spine/Blend Modes/Skeleton PMA Additive" {
|
||||
Properties {
|
||||
_Color ("Tint Color", Color) = (1,1,1,1)
|
||||
[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend One One
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Normal"
|
||||
|
||||
CGPROGRAM
|
||||
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#include "UnityCG.cginc"
|
||||
uniform sampler2D _MainTex;
|
||||
uniform float4 _Color;
|
||||
|
||||
struct VertexInput {
|
||||
float4 vertex : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
};
|
||||
|
||||
VertexOutput vert (VertexInput v) {
|
||||
VertexOutput o;
|
||||
o.pos = UnityObjectToClipPos(v.vertex);
|
||||
o.uv = v.uv;
|
||||
o.vertexColor = v.vertexColor * float4(_Color.rgb * _Color.a, _Color.a); // Combine a PMA version of _Color with vertexColor.
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 frag (VertexOutput i) : SV_Target {
|
||||
float4 texColor = tex2D(_MainTex, i.uv);
|
||||
|
||||
#if defined(_STRAIGHT_ALPHA_INPUT)
|
||||
texColor.rgb *= texColor.a;
|
||||
#endif
|
||||
|
||||
return (texColor * i.vertexColor);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Caster"
|
||||
Tags { "LightMode"="ShadowCaster" }
|
||||
Offset 1, 1
|
||||
|
||||
ZWrite On
|
||||
ZTest LEqual
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile_shadowcaster
|
||||
#pragma fragmentoption ARB_precision_hint_fastest
|
||||
#include "UnityCG.cginc"
|
||||
struct v2f {
|
||||
V2F_SHADOW_CASTER;
|
||||
float4 uvAndAlpha : TEXCOORD1;
|
||||
};
|
||||
|
||||
uniform float4 _MainTex_ST;
|
||||
|
||||
v2f vert (appdata_base v, float4 vertexColor : COLOR) {
|
||||
v2f o;
|
||||
TRANSFER_SHADOW_CASTER(o)
|
||||
o.uvAndAlpha.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
|
||||
o.uvAndAlpha.z = 0;
|
||||
o.uvAndAlpha.a = vertexColor.a;
|
||||
return o;
|
||||
}
|
||||
|
||||
uniform sampler2D _MainTex;
|
||||
uniform fixed _Cutoff;
|
||||
|
||||
float4 frag (v2f i) : SV_Target {
|
||||
fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
|
||||
clip(texcol.a * i.uvAndAlpha.a - _Cutoff);
|
||||
SHADOW_CASTER_FRAGMENT(i)
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53efa1d97f5d9f74285d4330cda14e36
|
||||
timeCreated: 1496446742
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef SPRITES_DEPTH_ONLY_PASS_INCLUDED
|
||||
#define SPRITES_DEPTH_ONLY_PASS_INCLUDED
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
sampler2D _MainTex;
|
||||
float _Cutoff;
|
||||
float _ZWriteOffset;
|
||||
|
||||
struct VertexInput {
|
||||
float4 positionOS : POSITION;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
float4 positionCS : SV_POSITION;
|
||||
float4 texcoordAndAlpha: TEXCOORD0;
|
||||
};
|
||||
|
||||
VertexOutput DepthOnlyVertex (VertexInput v) {
|
||||
VertexOutput o;
|
||||
o.positionCS = UnityObjectToClipPos(v.positionOS - float4(0, 0, _ZWriteOffset, 0));
|
||||
o.texcoordAndAlpha.xy = v.texcoord;
|
||||
o.texcoordAndAlpha.z = 0;
|
||||
o.texcoordAndAlpha.a = v.vertexColor.a;
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 DepthOnlyFragment (VertexOutput input) : SV_Target{
|
||||
float4 texColor = tex2D(_MainTex, input.texcoordAndAlpha.rg);
|
||||
|
||||
#if defined(_STRAIGHT_ALPHA_INPUT)
|
||||
texColor.rgb *= texColor.a;
|
||||
#endif
|
||||
|
||||
clip(texColor.a * input.texcoordAndAlpha.a - _Cutoff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27351ce55d3beb643ae8d9385db21941
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef SPINE_OUTLINE_COMMON_INCLUDED
|
||||
#define SPINE_OUTLINE_COMMON_INCLUDED
|
||||
|
||||
float4 computeOutlinePixel(sampler2D mainTexture, float2 mainTextureTexelSize,
|
||||
float2 uv, float vertexColorAlpha,
|
||||
float OutlineWidth, float OutlineReferenceTexWidth, float OutlineMipLevel,
|
||||
float OutlineSmoothness, float ThresholdEnd, float4 OutlineColor) {
|
||||
|
||||
float4 texColor = fixed4(0, 0, 0, 0);
|
||||
|
||||
float outlineWidthCompensated = OutlineWidth / (OutlineReferenceTexWidth * mainTextureTexelSize.x);
|
||||
float xOffset = mainTextureTexelSize.x * outlineWidthCompensated;
|
||||
float yOffset = mainTextureTexelSize.y * outlineWidthCompensated;
|
||||
float xOffsetDiagonal = mainTextureTexelSize.x * outlineWidthCompensated * 0.7;
|
||||
float yOffsetDiagonal = mainTextureTexelSize.y * outlineWidthCompensated * 0.7;
|
||||
|
||||
float pixelCenter = tex2D(mainTexture, uv).a;
|
||||
|
||||
float4 uvCenterWithLod = float4(uv, 0, OutlineMipLevel);
|
||||
float pixelTop = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, yOffset, 0, 0)).a;
|
||||
float pixelBottom = tex2Dlod(mainTexture, uvCenterWithLod + float4(0, -yOffset, 0, 0)).a;
|
||||
float pixelLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffset, 0, 0, 0)).a;
|
||||
float pixelRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffset, 0, 0, 0)).a;
|
||||
#if _USE8NEIGHBOURHOOD_ON
|
||||
float numSamples = 8;
|
||||
float pixelTopLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
|
||||
float pixelTopRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, yOffsetDiagonal, 0, 0)).a;
|
||||
float pixelBottomLeft = tex2Dlod(mainTexture, uvCenterWithLod + float4(-xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
|
||||
float pixelBottomRight = tex2Dlod(mainTexture, uvCenterWithLod + float4(xOffsetDiagonal, -yOffsetDiagonal, 0, 0)).a;
|
||||
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight +
|
||||
pixelTopLeft + pixelTopRight + pixelBottomLeft + pixelBottomRight)
|
||||
* vertexColorAlpha / numSamples;
|
||||
#else // 4 neighbourhood
|
||||
float numSamples = 1;
|
||||
float average = (pixelTop + pixelBottom + pixelLeft + pixelRight) * vertexColorAlpha / numSamples;
|
||||
#endif
|
||||
float thresholdStart = ThresholdEnd * (1.0 - OutlineSmoothness);
|
||||
float outlineAlpha = saturate((average - thresholdStart) / (ThresholdEnd - thresholdStart)) - pixelCenter;
|
||||
return lerp(texColor, OutlineColor, outlineAlpha);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8d610b87be4e82409e18a63a930d335
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,30 +0,0 @@
|
||||
#ifndef SKELETON_LIT_COMMON_SHADOW_INCLUDED
|
||||
#define SKELETON_LIT_COMMON_SHADOW_INCLUDED
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
struct v2f {
|
||||
V2F_SHADOW_CASTER;
|
||||
float4 uvAndAlpha : TEXCOORD1;
|
||||
};
|
||||
|
||||
uniform float4 _MainTex_ST;
|
||||
|
||||
v2f vertShadow(appdata_base v, float4 vertexColor : COLOR) {
|
||||
v2f o;
|
||||
TRANSFER_SHADOW_CASTER(o)
|
||||
o.uvAndAlpha.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
|
||||
o.uvAndAlpha.z = 0;
|
||||
o.uvAndAlpha.a = vertexColor.a;
|
||||
return o;
|
||||
}
|
||||
|
||||
uniform sampler2D _MainTex;
|
||||
uniform fixed SHADOW_CUTOFF;
|
||||
|
||||
float4 fragShadow (v2f i) : SV_Target {
|
||||
fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
|
||||
clip(texcol.a * i.uvAndAlpha.a - SHADOW_CUTOFF);
|
||||
SHADOW_CASTER_FRAGMENT(i)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7297b25c81d2494e8e73b742e3c7345
|
||||
timeCreated: 1564083752
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,120 +0,0 @@
|
||||
#ifndef SKELETON_LIT_COMMON_INCLUDED
|
||||
#define SKELETON_LIT_COMMON_INCLUDED
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
// ES2.0/WebGL/3DS can not do loops with non-constant-expression iteration counts :(
|
||||
#if defined(SHADER_API_GLES)
|
||||
#define LIGHT_LOOP_LIMIT 8
|
||||
#elif defined(SHADER_API_N3DS)
|
||||
#define LIGHT_LOOP_LIMIT 4
|
||||
#else
|
||||
#define LIGHT_LOOP_LIMIT unity_VertexLightParams.x
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// Alpha Clipping
|
||||
//
|
||||
|
||||
#if defined(_ALPHA_CLIP)
|
||||
uniform fixed _Cutoff;
|
||||
#define ALPHA_CLIP(pixel, color) clip((pixel.a * color.a) - _Cutoff);
|
||||
#else
|
||||
#define ALPHA_CLIP(pixel, color)
|
||||
#endif
|
||||
|
||||
half3 computeLighting (int idx, half3 dirToLight, half3 eyeNormal, half4 diffuseColor, half atten) {
|
||||
half NdotL = max(dot(eyeNormal, dirToLight), 0.0);
|
||||
// diffuse
|
||||
half3 color = NdotL * diffuseColor.rgb * unity_LightColor[idx].rgb;
|
||||
return color * atten;
|
||||
}
|
||||
|
||||
half3 computeOneLight (int idx, float3 eyePosition, half3 eyeNormal, half4 diffuseColor) {
|
||||
float3 dirToLight = unity_LightPosition[idx].xyz;
|
||||
half att = 1.0;
|
||||
|
||||
#if defined(POINT) || defined(SPOT)
|
||||
dirToLight -= eyePosition * unity_LightPosition[idx].w;
|
||||
|
||||
// distance attenuation
|
||||
float distSqr = dot(dirToLight, dirToLight);
|
||||
att /= (1.0 + unity_LightAtten[idx].z * distSqr);
|
||||
if (unity_LightPosition[idx].w != 0 && distSqr > unity_LightAtten[idx].w) att = 0.0; // set to 0 if outside of range
|
||||
distSqr = max(distSqr, 0.000001); // don't produce NaNs if some vertex position overlaps with the light
|
||||
dirToLight *= rsqrt(distSqr);
|
||||
#if defined(SPOT)
|
||||
|
||||
// spot angle attenuation
|
||||
half rho = max(dot(dirToLight, unity_SpotDirection[idx].xyz), 0.0);
|
||||
half spotAtt = (rho - unity_LightAtten[idx].x) * unity_LightAtten[idx].y;
|
||||
att *= saturate(spotAtt);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
att *= 0.5; // passed in light colors are 2x brighter than what used to be in FFP
|
||||
return min (computeLighting (idx, dirToLight, eyeNormal, diffuseColor, att), 1.0);
|
||||
}
|
||||
|
||||
int4 unity_VertexLightParams; // x: light count, y: zero, z: one (y/z needed by d3d9 vs loop instruction)
|
||||
|
||||
struct appdata {
|
||||
float3 pos : POSITION;
|
||||
float3 normal : NORMAL;
|
||||
half4 color : COLOR;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
fixed4 color : COLOR0;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
float4 pos : SV_POSITION;
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
VertexOutput vert (appdata v) {
|
||||
VertexOutput o;
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||||
|
||||
half4 color = v.color;
|
||||
float3 eyePos = UnityObjectToViewPos(float4(v.pos, 1)).xyz; //mul(UNITY_MATRIX_MV, float4(v.pos,1)).xyz;
|
||||
half3 fixedNormal = half3(0,0,-1);
|
||||
half3 eyeNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, fixedNormal));
|
||||
|
||||
#ifdef _DOUBLE_SIDED_LIGHTING
|
||||
// unfortunately we have to compute the sign here in the vertex shader
|
||||
// instead of using VFACE in fragment shader stage.
|
||||
half faceSign = sign(eyeNormal.z);
|
||||
eyeNormal *= faceSign;
|
||||
#endif
|
||||
|
||||
// Lights
|
||||
half3 lcolor = half4(0,0,0,1).rgb + color.rgb * glstate_lightmodel_ambient.rgb;
|
||||
for (int il = 0; il < LIGHT_LOOP_LIMIT; ++il) {
|
||||
lcolor += computeOneLight(il, eyePos, eyeNormal, color);
|
||||
}
|
||||
|
||||
color.rgb = lcolor.rgb;
|
||||
o.color = saturate(color);
|
||||
o.uv0 = v.uv0;
|
||||
o.pos = UnityObjectToClipPos(v.pos);
|
||||
return o;
|
||||
}
|
||||
|
||||
sampler2D _MainTex;
|
||||
|
||||
fixed4 frag (VertexOutput i) : SV_Target {
|
||||
fixed4 tex = tex2D(_MainTex, i.uv0);
|
||||
ALPHA_CLIP(tex, i.color);
|
||||
#if defined(_STRAIGHT_ALPHA_INPUT)
|
||||
tex.rgb *= tex.a;
|
||||
#endif
|
||||
fixed4 col = tex * i.color;
|
||||
col.rgb *= 2;
|
||||
return col;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70c77c02aabd5e44f94aab48dd0be7b2
|
||||
timeCreated: 1564083752
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,46 +0,0 @@
|
||||
// Outline shader variant of "Spine/Blend Modes/Skeleton PMA Additive"
|
||||
|
||||
Shader "Spine/Outline/Blend Modes/Skeleton PMA Additive" {
|
||||
Properties {
|
||||
_Color ("Tint Color", Color) = (1,1,1,1)
|
||||
[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend One One
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
UsePass "Spine/Outline/Skeleton/OUTLINE"
|
||||
|
||||
UsePass "Spine/Blend Modes/Skeleton PMA Additive/NORMAL"
|
||||
|
||||
UsePass "Spine/Blend Modes/Skeleton PMA Additive/CASTER"
|
||||
}
|
||||
FallBack "Spine/Blend Modes/Skeleton PMA Additive"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0299ffae826705448b6c80ccc6a53b75
|
||||
timeCreated: 1573829476
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,46 +0,0 @@
|
||||
// Outline shader variant of "Spine/Blend Modes/Skeleton PMA Multiply"
|
||||
|
||||
Shader "Spine/Outline/Blend Modes/Skeleton PMA Multiply" {
|
||||
Properties {
|
||||
_Color ("Tint Color", Color) = (1,1,1,1)
|
||||
[NoScaleOffset] _MainTex ("MainTex", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend DstColor OneMinusSrcAlpha
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
UsePass "Spine/Outline/Skeleton/OUTLINE"
|
||||
|
||||
UsePass "Spine/Blend Modes/Skeleton PMA Multiply/NORMAL"
|
||||
|
||||
UsePass "Spine/Blend Modes/Skeleton PMA Multiply/CASTER"
|
||||
}
|
||||
FallBack "Spine/Blend Modes/Skeleton PMA Multiply"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b3566a937643b8498d1ec6df5880b77
|
||||
timeCreated: 1573829476
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,92 +0,0 @@
|
||||
#ifndef SPINE_OUTLINE_PASS_INCLUDED
|
||||
#define SPINE_OUTLINE_PASS_INCLUDED
|
||||
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
#ifdef SKELETON_GRAPHIC
|
||||
#include "UnityUI.cginc"
|
||||
#endif
|
||||
|
||||
#include "../../CGIncludes/Spine-Outline-Common.cginc"
|
||||
|
||||
sampler2D _MainTex;
|
||||
|
||||
float _OutlineWidth;
|
||||
float4 _OutlineColor;
|
||||
float4 _MainTex_TexelSize;
|
||||
float _ThresholdEnd;
|
||||
float _OutlineSmoothness;
|
||||
float _OutlineMipLevel;
|
||||
int _OutlineReferenceTexWidth;
|
||||
|
||||
#ifdef SKELETON_GRAPHIC
|
||||
float4 _ClipRect;
|
||||
#endif
|
||||
|
||||
struct VertexInput {
|
||||
float4 vertex : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float vertexColorAlpha : COLOR;
|
||||
#ifdef SKELETON_GRAPHIC
|
||||
float4 worldPosition : TEXCOORD1;
|
||||
#endif
|
||||
UNITY_VERTEX_OUTPUT_STEREO
|
||||
};
|
||||
|
||||
|
||||
#ifdef SKELETON_GRAPHIC
|
||||
|
||||
VertexOutput vertOutlineGraphic(VertexInput v) {
|
||||
VertexOutput o;
|
||||
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||||
|
||||
o.worldPosition = v.vertex;
|
||||
o.pos = UnityObjectToClipPos(o.worldPosition);
|
||||
o.uv = v.uv;
|
||||
|
||||
#ifdef UNITY_HALF_TEXEL_OFFSET
|
||||
o.pos.xy += (_ScreenParams.zw - 1.0) * float2(-1, 1);
|
||||
#endif
|
||||
|
||||
o.vertexColorAlpha = v.vertexColor.a;
|
||||
return o;
|
||||
}
|
||||
|
||||
#else // !SKELETON_GRAPHIC
|
||||
|
||||
VertexOutput vertOutline(VertexInput v) {
|
||||
VertexOutput o;
|
||||
|
||||
UNITY_SETUP_INSTANCE_ID(v);
|
||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||||
|
||||
o.pos = UnityObjectToClipPos(v.vertex);
|
||||
o.uv = v.uv;
|
||||
o.vertexColorAlpha = v.vertexColor.a;
|
||||
return o;
|
||||
}
|
||||
#endif
|
||||
|
||||
float4 fragOutline(VertexOutput i) : SV_Target {
|
||||
|
||||
float4 texColor = computeOutlinePixel(_MainTex, _MainTex_TexelSize.xy, i.uv, i.vertexColorAlpha,
|
||||
_OutlineWidth, _OutlineReferenceTexWidth, _OutlineMipLevel,
|
||||
_OutlineSmoothness, _ThresholdEnd, _OutlineColor);
|
||||
|
||||
#ifdef SKELETON_GRAPHIC
|
||||
texColor *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
|
||||
#endif
|
||||
|
||||
return texColor;
|
||||
}
|
||||
|
||||
#endif // SPINE_OUTLINE_PASS_INCLUDED
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ec781e799f97504c8a418e168759f70
|
||||
timeCreated: 1574096529
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e8ed065898e65f4d9303492725fb912
|
||||
folderAsset: yes
|
||||
timeCreated: 1573829873
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,43 +0,0 @@
|
||||
// Outline shader variant of "Spine/Skeleton Fill"
|
||||
|
||||
Shader "Spine/Outline/Skeleton Fill" {
|
||||
Properties {
|
||||
_FillColor ("FillColor", Color) = (1,1,1,1)
|
||||
_FillPhase ("FillPhase", Range(0, 1)) = 0
|
||||
[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
|
||||
Blend One OneMinusSrcAlpha
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
UsePass "Spine/Outline/Skeleton/OUTLINE"
|
||||
|
||||
UsePass "Spine/Skeleton Fill/NORMAL"
|
||||
|
||||
UsePass "Spine/Skeleton Fill/CASTER"
|
||||
}
|
||||
FallBack "Spine/Skeleton Fill"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e158cbe58baa093438feb3d691f3daba
|
||||
timeCreated: 1573817434
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,40 +0,0 @@
|
||||
// Outline shader variant of "Spine/Skeleton Lit"
|
||||
|
||||
Shader "Spine/Outline/Skeleton Lit" {
|
||||
Properties {
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[NoScaleOffset] _MainTex ("Main Texture", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[Toggle(_DOUBLE_SIDED_LIGHTING)] _DoubleSidedLighting("Double-Sided Lighting", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
UsePass "Spine/Outline/Skeleton/OUTLINE"
|
||||
|
||||
UsePass "Spine/Skeleton Lit/NORMAL"
|
||||
|
||||
UsePass "Spine/Skeleton Lit/CASTER"
|
||||
}
|
||||
FallBack "Spine/Skeleton Lit"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10fab3f69a099be4391fe8a1ad880c65
|
||||
timeCreated: 1573828963
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,41 +0,0 @@
|
||||
// Outline shader variant of "Spine/Skeleton Lit ZWrite"
|
||||
|
||||
Shader "Spine/Outline/Skeleton Lit ZWrite" {
|
||||
Properties {
|
||||
_Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.1
|
||||
_ShadowAlphaCutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[NoScaleOffset] _MainTex ("Main Texture", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[Toggle(_DOUBLE_SIDED_LIGHTING)] _DoubleSidedLighting("Double-Sided Lighting", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
UsePass "Spine/Outline/Skeleton/OUTLINE"
|
||||
|
||||
UsePass "Spine/Skeleton Lit ZWrite/NORMAL"
|
||||
|
||||
UsePass "Spine/Skeleton Lit ZWrite/CASTER"
|
||||
}
|
||||
FallBack "Spine/Skeleton Lit ZWrite"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 756be4f2f738f6c4583bb1c90e16bf0b
|
||||
timeCreated: 1573828964
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,52 +0,0 @@
|
||||
// Outline shader variant of "Spine/Skeleton"
|
||||
|
||||
Shader "Spine/Outline/Skeleton" {
|
||||
Properties {
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[NoScaleOffset] _MainTex ("Main Texture", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend One OneMinusSrcAlpha
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Outline"
|
||||
CGPROGRAM
|
||||
#pragma vertex vertOutline
|
||||
#pragma fragment fragOutline
|
||||
#pragma shader_feature _ _USE8NEIGHBOURHOOD_ON
|
||||
#include "CGIncludes/Spine-Outline-Pass.cginc"
|
||||
ENDCG
|
||||
}
|
||||
|
||||
UsePass "Spine/Skeleton/NORMAL"
|
||||
|
||||
UsePass "Spine/Skeleton/CASTER"
|
||||
}
|
||||
FallBack "Spine/Skeleton"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28b5cf4804845fe4b868531fd0bb81d5
|
||||
timeCreated: 1573817434
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,62 +0,0 @@
|
||||
Shader "Spine/Outline/OutlineOnly-ZWrite" {
|
||||
Properties {
|
||||
_Cutoff ("Depth alpha cutoff", Range(0,1)) = 0.1
|
||||
_ZWriteOffset ("Depth offset", Range(0,1)) = 0.01
|
||||
[NoScaleOffset] _MainTex ("Main Texture", 2D) = "black" {}
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Blend One OneMinusSrcAlpha
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "DepthOnly"
|
||||
|
||||
ZWrite On
|
||||
ColorMask 0
|
||||
Cull Off
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex DepthOnlyVertex
|
||||
#pragma fragment DepthOnlyFragment
|
||||
#include "../CGIncludes/Spine-DepthOnlyPass.cginc"
|
||||
ENDCG
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Outline"
|
||||
CGPROGRAM
|
||||
#pragma vertex vertOutline
|
||||
#pragma fragment fragOutline
|
||||
#pragma shader_feature _ _USE8NEIGHBOURHOOD_ON
|
||||
#include "CGIncludes/Spine-Outline-Pass.cginc"
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
FallBack "Spine/Skeleton"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 177da18c3d2e0aa4cb39990ea011973c
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a42d52714848d64f8ff99dddb93500e
|
||||
folderAsset: yes
|
||||
timeCreated: 1563289150
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,126 +0,0 @@
|
||||
// - Unlit
|
||||
// - Premultiplied Alpha Blending (Optional straight alpha input)
|
||||
// - Double-sided, no depth
|
||||
|
||||
Shader "Spine/Skeleton Fill" {
|
||||
Properties {
|
||||
_FillColor ("FillColor", Color) = (1,1,1,1)
|
||||
_FillPhase ("FillPhase", Range(0, 1)) = 0
|
||||
[NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}
|
||||
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
|
||||
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
|
||||
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
|
||||
[HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
|
||||
|
||||
// Outline properties are drawn via custom editor.
|
||||
[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
|
||||
[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
|
||||
[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
|
||||
[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
|
||||
[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
|
||||
[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
|
||||
[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
|
||||
}
|
||||
SubShader {
|
||||
Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
|
||||
Blend One OneMinusSrcAlpha
|
||||
Cull Off
|
||||
ZWrite Off
|
||||
Lighting Off
|
||||
|
||||
Stencil {
|
||||
Ref[_StencilRef]
|
||||
Comp[_StencilComp]
|
||||
Pass Keep
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Normal"
|
||||
|
||||
CGPROGRAM
|
||||
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#include "UnityCG.cginc"
|
||||
sampler2D _MainTex;
|
||||
float4 _FillColor;
|
||||
float _FillPhase;
|
||||
|
||||
struct VertexInput {
|
||||
float4 vertex : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertexColor : COLOR;
|
||||
};
|
||||
|
||||
VertexOutput vert (VertexInput v) {
|
||||
VertexOutput o = (VertexOutput)0;
|
||||
o.uv = v.uv;
|
||||
o.vertexColor = v.vertexColor;
|
||||
o.pos = UnityObjectToClipPos(v.vertex);
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 frag (VertexOutput i) : SV_Target {
|
||||
float4 rawColor = tex2D(_MainTex,i.uv);
|
||||
float finalAlpha = (rawColor.a * i.vertexColor.a);
|
||||
|
||||
#if defined(_STRAIGHT_ALPHA_INPUT)
|
||||
rawColor.rgb *= rawColor.a;
|
||||
#endif
|
||||
|
||||
float3 finalColor = lerp((rawColor.rgb * i.vertexColor.rgb), (_FillColor.rgb * finalAlpha), _FillPhase); // make sure to PMA _FillColor.
|
||||
return fixed4(finalColor, finalAlpha);
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
|
||||
Pass {
|
||||
Name "Caster"
|
||||
Tags { "LightMode"="ShadowCaster" }
|
||||
Offset 1, 1
|
||||
ZWrite On
|
||||
ZTest LEqual
|
||||
|
||||
Fog { Mode Off }
|
||||
Cull Off
|
||||
Lighting Off
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile_shadowcaster
|
||||
#pragma fragmentoption ARB_precision_hint_fastest
|
||||
#include "UnityCG.cginc"
|
||||
sampler2D _MainTex;
|
||||
fixed _Cutoff;
|
||||
|
||||
struct VertexOutput {
|
||||
V2F_SHADOW_CASTER;
|
||||
float4 uvAndAlpha : TEXCOORD1;
|
||||
};
|
||||
|
||||
VertexOutput vert (appdata_base v, float4 vertexColor : COLOR) {
|
||||
VertexOutput o;
|
||||
o.uvAndAlpha = v.texcoord;
|
||||
o.uvAndAlpha.a = vertexColor.a;
|
||||
TRANSFER_SHADOW_CASTER(o)
|
||||
return o;
|
||||
}
|
||||
|
||||
float4 frag (VertexOutput i) : SV_Target {
|
||||
fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
|
||||
clip(texcol.a * i.uvAndAlpha.a - _Cutoff);
|
||||
SHADOW_CASTER_FRAGMENT(i)
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
FallBack "Diffuse"
|
||||
CustomEditor "SpineShaderWithOutlineGUI"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45495790b394f894a967dbf44489b57b
|
||||
timeCreated: 1492385797
|
||||
licenseType: Free
|
||||
ShaderImporter:
|
||||
defaultTextures: []
|
||||
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