Meshes, FFD and skinning for spine-unity.

This commit is contained in:
NathanSweet 2014-04-25 18:21:00 +02:00
parent 15b3f0979b
commit 09fac79d03
19 changed files with 468 additions and 425 deletions

View File

@ -33,6 +33,7 @@ using System.IO;
using UnityEngine;
using Spine;
/// <summary>Loads and stores a Spine atlas and list of materials.</summary>
public class AtlasAsset : ScriptableObject {
public TextAsset atlasFile;
public Material[] materials;
@ -45,13 +46,13 @@ public class AtlasAsset : ScriptableObject {
/// <returns>The atlas or null if it could not be loaded.</returns>
public Atlas GetAtlas () {
if (atlasFile == null) {
Debug.LogWarning("Atlas file not set for atlas asset: " + name, this);
Debug.LogError("Atlas file not set for atlas asset: " + name, this);
Clear();
return null;
}
if (materials == null || materials.Length == 0) {
Debug.LogWarning("Materials not set for atlas asset: " + name, this);
Debug.LogError("Materials not set for atlas asset: " + name, this);
Clear();
return null;
}
@ -63,7 +64,7 @@ public class AtlasAsset : ScriptableObject {
atlas = new Atlas(new StringReader(atlasFile.text), "", new MaterialsTextureLoader(this));
return atlas;
} catch (Exception ex) {
Debug.Log("Error reading atlas file for atlas asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
Debug.LogError("Error reading atlas file for atlas asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
return null;
}
}
@ -81,7 +82,7 @@ public class MaterialsTextureLoader : TextureLoader {
Material material = null;
foreach (Material other in atlasAsset.materials) {
if (other.mainTexture == null) {
Debug.LogWarning("Material is missing texture: " + other.name, other);
Debug.LogError("Material is missing texture: " + other.name, other);
return;
}
if (other.mainTexture.name == name) {
@ -90,7 +91,7 @@ public class MaterialsTextureLoader : TextureLoader {
}
}
if (material == null) {
Debug.LogWarning("Material with texture name \"" + name + "\" not found for atlas asset: " + atlasAsset.name, atlasAsset);
Debug.LogError("Material with texture name \"" + name + "\" not found for atlas asset: " + atlasAsset.name, atlasAsset);
return;
}
page.rendererObject = material;

View File

@ -38,44 +38,52 @@ using Spine;
[ExecuteInEditMode]
[AddComponentMenu("Spine/BoneComponent")]
public class BoneComponent : MonoBehaviour {
public SkeletonComponent skeletonComponent;
public bool valid;
public SkeletonRenderer skeletonRenderer;
public Bone bone;
/// <summary>If a bone isn't set, boneName is used to find the bone.</summary>
public String boneName;
protected Transform cachedTransform;
protected Transform skeletonComponentTransform;
protected Transform skeletonTransform;
void Awake () {
public void Reset () {
bone = null;
cachedTransform = transform;
valid = skeletonRenderer != null && skeletonRenderer.valid;
if (!valid) return;
skeletonTransform = skeletonRenderer.transform;
}
if(skeletonComponent == null) return;
skeletonComponentTransform = skeletonComponent.transform;
public void Awake () {
Reset();
}
public void LateUpdate () {
if (skeletonComponent == null || skeletonComponent.skeleton == null) return;
if (!valid) {
Reset();
return;
}
if (bone == null) {
if (boneName == null) return;
bone = skeletonComponent.skeleton.FindBone(boneName);
if (boneName == null || boneName.Length == 0) return;
bone = skeletonRenderer.skeleton.FindBone(boneName);
if (bone == null) {
Debug.Log("Bone not found: " + boneName, this);
Debug.LogError("Bone not found: " + boneName, this);
return;
}
}
if (cachedTransform.parent == skeletonComponentTransform) {
if (cachedTransform.parent == skeletonTransform) {
cachedTransform.localPosition = new Vector3(bone.worldX, bone.worldY, cachedTransform.localPosition.z);
Vector3 rotation = cachedTransform.localRotation.eulerAngles;
cachedTransform.localRotation = Quaternion.Euler(rotation.x, rotation.y, bone.worldRotation);
} else {
cachedTransform.position = skeletonComponentTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY, cachedTransform.position.z));
Vector3 rotation = skeletonComponentTransform.rotation.eulerAngles;
cachedTransform.position = skeletonTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY, cachedTransform.position.z));
Vector3 rotation = skeletonTransform.rotation.eulerAngles;
cachedTransform.rotation = Quaternion.Euler(rotation.x, rotation.y,
skeletonComponentTransform.rotation.eulerAngles.z + bone.worldRotation);
skeletonTransform.rotation.eulerAngles.z + bone.worldRotation);
}
}
}
}

View File

@ -34,10 +34,10 @@ using UnityEngine;
[CustomEditor(typeof(BoneComponent))]
public class BoneComponentInspector : Editor {
private SerializedProperty boneName, skeletonComponent;
private SerializedProperty boneName, skeletonRenderer;
void OnEnable () {
skeletonComponent = serializedObject.FindProperty("skeletonComponent");
skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
boneName = serializedObject.FindProperty("boneName");
}
@ -45,13 +45,13 @@ public class BoneComponentInspector : Editor {
serializedObject.Update();
BoneComponent component = (BoneComponent)target;
EditorGUILayout.PropertyField(skeletonComponent);
EditorGUILayout.PropertyField(skeletonRenderer);
if (component.skeletonComponent != null) {
String[] bones = new String[component.skeletonComponent.skeleton.Data.Bones.Count + 1];
if (component.valid) {
String[] bones = new String[component.skeletonRenderer.skeleton.Data.Bones.Count + 1];
bones[0] = "<None>";
for (int i = 0; i < bones.Length - 1; i++)
bones[i + 1] = component.skeletonComponent.skeleton.Data.Bones[i].Name;
bones[i + 1] = component.skeletonRenderer.skeleton.Data.Bones[i].Name;
Array.Sort<String>(bones);
int boneIndex = Math.Max(0, Array.IndexOf(bones, boneName.stringValue));
@ -61,14 +61,13 @@ public class BoneComponentInspector : Editor {
boneIndex = EditorGUILayout.Popup(boneIndex, bones);
EditorGUILayout.EndHorizontal();
boneName.stringValue = bones[boneIndex];;
boneName.stringValue = boneIndex == 0 ? null : bones[boneIndex];
}
if (serializedObject.ApplyModifiedProperties() ||
(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
) {
component.bone = null;
component.LateUpdate();
component.Reset();
}
}
}

View File

@ -59,9 +59,9 @@ public class SpineEditor {
Selection.activeObject = asset;
}
[MenuItem("GameObject/Create Other/Spine SkeletonComponent")]
static public void CreateSkeletonComponentGameObject () {
GameObject gameObject = new GameObject("New SkeletonComponent", typeof(SkeletonComponent));
[MenuItem("GameObject/Create Other/Spine SkeletonRenderer")]
static public void CreateSkeletonRendererGameObject () {
GameObject gameObject = new GameObject("New SkeletonRenderer", typeof(SkeletonRenderer));
EditorUtility.FocusProjectWindow();
Selection.activeObject = gameObject;
}

View File

@ -89,10 +89,8 @@ public class SkeletonAnimationInspector : Editor {
animationIndex = EditorGUILayout.Popup(animationIndex, animations);
EditorGUILayout.EndHorizontal();
if (animationIndex == 0)
component.animationName = null;
else
component.animationName = animations[animationIndex];
component.animationName = animationIndex == 0 ? null : animations[animationIndex];
animationName.stringValue = component.animationName;
}
// Animation loop.
@ -109,8 +107,7 @@ public class SkeletonAnimationInspector : Editor {
(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
) {
if (!Application.isPlaying) {
component.Clear();
component.Update();
component.Reset();
}
}
}

View File

@ -32,8 +32,8 @@ using System;
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(SkeletonComponent))]
public class SkeletonComponentInspector : Editor {
[CustomEditor(typeof(SkeletonRenderer))]
public class SkeletonRendererInspector : Editor {
private SerializedProperty skeletonDataAsset, initialSkinName, timeScale, normals, tangents;
void OnEnable () {
@ -46,7 +46,7 @@ public class SkeletonComponentInspector : Editor {
override public void OnInspectorGUI () {
serializedObject.Update();
SkeletonComponent component = (SkeletonComponent)target;
SkeletonRenderer component = (SkeletonRenderer)target;
EditorGUILayout.PropertyField(skeletonDataAsset);
@ -78,8 +78,7 @@ public class SkeletonComponentInspector : Editor {
(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
) {
if (!Application.isPlaying) {
component.Clear();
component.Update();
component.Reset();
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 75f45221af7dd564081b96d877373967
guid: d0fc5db9788bce4418ad3252d43faa8a
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@ -34,10 +34,10 @@ using System.Collections.Generic;
using UnityEngine;
using Spine;
/** Extends SkeletonComponent to apply an animation. */
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
[ExecuteInEditMode]
[AddComponentMenu("Spine/SkeletonAnimation")]
public class SkeletonAnimation : SkeletonComponent {
public class SkeletonAnimation : SkeletonRenderer {
public float timeScale = 1;
public bool loop;
public Spine.AnimationState state;
@ -60,23 +60,29 @@ public class SkeletonAnimation : SkeletonComponent {
}
}
override public void Initialize () {
if (Initialized) return;
base.Initialize();
public override void Reset () {
base.Reset();
if (!valid) return;
state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
if (_animationName != null && _animationName.Length > 0) state.SetAnimation(0, _animationName, loop);
if (_animationName != null && _animationName.Length > 0) {
state.SetAnimation(0, _animationName, loop);
Update(0);
}
}
override public void UpdateSkeleton (float deltaTime) {
// Apply the animation.
public virtual void Update () {
Update(Time.deltaTime);
}
public virtual void Update (float deltaTime) {
if (!valid) return;
deltaTime *= timeScale;
skeleton.Update(deltaTime);
state.Update(deltaTime * timeScale);
state.Apply(skeleton);
if (UpdateBones != null) UpdateBones(this);
// Call overridden method to call skeleton Update and UpdateWorldTransform.
base.UpdateSkeleton(deltaTime);
skeleton.UpdateWorldTransform();
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d247ba06193faa74d9335f5481b2b56c
guid: 77556b6aeacded74798bf9b33de3ae5a
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@ -1,334 +0,0 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Spine;
/** Renders a skeleton. Extend to apply animations, get bones and manipulate them, etc. */
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
[AddComponentMenu("Spine/SkeletonComponent")]
public class SkeletonComponent : MonoBehaviour {
public SkeletonDataAsset skeletonDataAsset;
public Skeleton skeleton;
public String initialSkinName;
public float timeScale = 1;
public bool calculateNormals;
public bool calculateTangents;
public float zSpacing;
private MeshFilter meshFilter;
private Mesh mesh, mesh1, mesh2;
private bool useMesh1;
private float[] vertexPositions = new float[8];
private int lastVertexCount;
private Vector3[] vertices;
private Color32[] colors;
private Vector2[] uvs;
private Material[] sharedMaterials = new Material[0];
private List<Material> submeshMaterials = new List<Material>();
private List<Submesh> submeshes = new List<Submesh>();
/// <summary>False if Initialize needs to be called.</summary>
public bool Initialized {
get {
if (skeletonDataAsset == null) return true;
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
if (skeletonData == null) return true;
return skeleton != null && skeleton.Data == skeletonData;
}
}
public virtual void Clear () {
if (meshFilter != null) meshFilter.sharedMesh = null;
if (mesh != null) DestroyImmediate(mesh);
if (renderer != null) renderer.sharedMaterial = null;
mesh = null;
mesh1 = null;
mesh2 = null;
lastVertexCount = 0;
vertices = null;
colors = null;
uvs = null;
sharedMaterials = new Material[0];
submeshMaterials.Clear();
submeshes.Clear();
skeleton = null;
}
public virtual void Initialize () {
if (Initialized) return;
meshFilter = GetComponent<MeshFilter>();
mesh1 = newMesh();
mesh2 = newMesh();
vertices = new Vector3[0];
skeleton = new Skeleton(skeletonDataAsset.GetSkeletonData(false));
if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default") {
skeleton.SetSkin(initialSkinName);
skeleton.SetSlotsToSetupPose();
}
}
private Mesh newMesh () {
Mesh mesh = new Mesh();
mesh.name = "Skeleton Mesh";
mesh.hideFlags = HideFlags.HideAndDontSave;
mesh.MarkDynamic();
return mesh;
}
public virtual void UpdateSkeleton (float deltaTime) {
skeleton.Update(deltaTime * timeScale);
skeleton.UpdateWorldTransform();
}
public virtual void Update () {
if (skeletonDataAsset == null) {
Clear();
return;
}
SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(false);
if (skeletonData == null) {
Clear();
return;
}
// Initialize fields.
if (skeleton == null || skeleton.Data != skeletonData)
Initialize();
UpdateSkeleton(Time.deltaTime);
// Count quads and submeshes.
int quadCount = 0, submeshQuadCount = 0;
Material lastMaterial = null;
submeshMaterials.Clear();
List<Slot> drawOrder = skeleton.DrawOrder;
for (int i = 0, n = drawOrder.Count; i < n; i++) {
RegionAttachment regionAttachment = drawOrder[i].Attachment as RegionAttachment;
if (regionAttachment == null)
continue;
// Add submesh when material changes.
Material material = (Material)((AtlasRegion)regionAttachment.RendererObject).page.rendererObject;
if (lastMaterial != material && lastMaterial != null) {
addSubmesh(lastMaterial, quadCount, submeshQuadCount, false);
submeshQuadCount = 0;
}
lastMaterial = material;
quadCount++;
submeshQuadCount++;
}
addSubmesh(lastMaterial, quadCount, submeshQuadCount, true);
// Set materials.
if (submeshMaterials.Count == sharedMaterials.Length)
submeshMaterials.CopyTo(sharedMaterials);
else
sharedMaterials = submeshMaterials.ToArray();
renderer.sharedMaterials = sharedMaterials;
// Double buffer mesh.
Mesh mesh = useMesh1 ? mesh1 : mesh2;
meshFilter.sharedMesh = mesh;
// Ensure mesh data is the right size.
Vector3[] vertices = this.vertices;
int vertexCount = quadCount * 4;
bool newTriangles = vertexCount > vertices.Length;
if (newTriangles) {
// Not enough vertices, increase size.
this.vertices = vertices = new Vector3[vertexCount];
this.colors = new Color32[vertexCount];
this.uvs = new Vector2[vertexCount];
mesh1.Clear();
mesh2.Clear();
} else {
// Too many vertices, zero the extra.
Vector3 zero = Vector3.zero;
for (int i = vertexCount, n = lastVertexCount; i < n; i++)
vertices[i] = zero;
}
lastVertexCount = vertexCount;
// Setup mesh.
float[] vertexPositions = this.vertexPositions;
Vector2[] uvs = this.uvs;
Color32[] colors = this.colors;
int vertexIndex = 0;
Color32 color = new Color32();
float a = skeleton.A * 255, r = skeleton.R, g = skeleton.G, b = skeleton.B, zSpacing = this.zSpacing;
for (int i = 0, n = drawOrder.Count; i < n; i++) {
Slot slot = drawOrder[i];
RegionAttachment regionAttachment = slot.Attachment as RegionAttachment;
if (regionAttachment == null)
continue;
regionAttachment.ComputeWorldVertices(skeleton.X, skeleton.Y, slot.Bone, vertexPositions);
float z = i * zSpacing;
vertices[vertexIndex] = new Vector3(vertexPositions[RegionAttachment.X1], vertexPositions[RegionAttachment.Y1], z);
vertices[vertexIndex + 1] = new Vector3(vertexPositions[RegionAttachment.X4], vertexPositions[RegionAttachment.Y4], z);
vertices[vertexIndex + 2] = new Vector3(vertexPositions[RegionAttachment.X2], vertexPositions[RegionAttachment.Y2], z);
vertices[vertexIndex + 3] = new Vector3(vertexPositions[RegionAttachment.X3], vertexPositions[RegionAttachment.Y3], z);
color.a = (byte)(a * slot.A);
color.r = (byte)(r * slot.R * color.a);
color.g = (byte)(g * slot.G * color.a);
color.b = (byte)(b * slot.B * color.a);
if (slot.Data.AdditiveBlending) color.a = 0;
colors[vertexIndex] = color;
colors[vertexIndex + 1] = color;
colors[vertexIndex + 2] = color;
colors[vertexIndex + 3] = color;
float[] regionUVs = regionAttachment.UVs;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], 1 - regionUVs[RegionAttachment.Y1]);
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], 1 - regionUVs[RegionAttachment.Y4]);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], 1 - regionUVs[RegionAttachment.Y2]);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], 1 - regionUVs[RegionAttachment.Y3]);
vertexIndex += 4;
}
mesh.vertices = vertices;
mesh.colors32 = colors;
mesh.uv = uvs;
int submeshCount = submeshMaterials.Count;
mesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; ++i)
mesh.SetTriangles(submeshes[i].indexes, i);
mesh.RecalculateBounds();
if (newTriangles && calculateNormals) {
Vector3[] normals = new Vector3[vertexCount];
Vector3 normal = new Vector3(0, 0, -1);
for (int i = 0; i < vertexCount; i++)
normals[i] = normal;
(useMesh1 ? mesh2 : mesh1).vertices = vertices; // Set other mesh vertices.
mesh1.normals = normals;
mesh2.normals = normals;
if (calculateTangents) {
Vector4[] tangents = new Vector4[vertexCount];
Vector3 tangent = new Vector3(0, 0, 1);
for (int i = 0; i < vertexCount; i++)
tangents[i] = tangent;
mesh1.tangents = tangents;
mesh2.tangents = tangents;
}
}
useMesh1 = !useMesh1;
}
/** Adds a material. Adds submesh indexes if existing indexes aren't sufficient. */
private void addSubmesh (Material material, int endQuadCount, int submeshQuadCount, bool lastSubmesh) {
int submeshIndex = submeshMaterials.Count;
submeshMaterials.Add(material);
int indexCount = submeshQuadCount * 6;
int vertexIndex = (endQuadCount - submeshQuadCount) * 4;
if (submeshes.Count <= submeshIndex) submeshes.Add(new Submesh());
Submesh submesh = submeshes[submeshIndex];
int[] indexes = submesh.indexes;
if (lastSubmesh && submesh.indexCount > indexCount) {
// Last submesh may have more indices than required, so zero indexes to the end.
submesh.indexCount = indexCount;
for (int i = indexCount, n = indexes.Length; i < n; i++)
indexes[i] = 0;
} else if (indexes.Length != indexCount) {
// Reallocate indexes if not the right size.
submesh.indexes = indexes = new int[indexCount];
submesh.indexCount = 0;
}
// Set indexes if not already set.
if (submesh.firstVertex != vertexIndex || submesh.indexCount < indexCount) {
submesh.indexCount = indexCount;
submesh.firstVertex = vertexIndex;
for (int i = 0; i < indexCount; i += 6, vertexIndex += 4) {
indexes[i] = vertexIndex;
indexes[i + 1] = vertexIndex + 2;
indexes[i + 2] = vertexIndex + 1;
indexes[i + 3] = vertexIndex + 2;
indexes[i + 4] = vertexIndex + 3;
indexes[i + 5] = vertexIndex + 1;
}
}
}
public virtual void OnEnable () {
Initialize();
}
public virtual void Reset () {
Initialize();
}
#if UNITY_EDITOR
void OnDrawGizmos() {
// Make selection easier by drawing a clear gizmo over the skeleton.
if (vertices == null) return;
Vector3 gizmosCenter = new Vector3();
Vector3 gizmosSize = new Vector3();
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, 0f);
Vector3 max = new Vector3(float.MinValue, float.MinValue, 0f);
foreach (Vector3 vert in vertices) {
min = Vector3.Min (min, vert);
max = Vector3.Max (max, vert);
}
float width = max.x - min.x;
float height = max.y - min.y;
gizmosCenter = new Vector3(min.x + (width / 2f), min.y + (height / 2f), 0f);
gizmosSize = new Vector3(width, height, 1f);
Gizmos.color = Color.clear;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(gizmosCenter, gizmosSize);
}
#endif
}
class Submesh {
public int[] indexes = new int[0];
public int firstVertex = -1;
public int indexCount;
}

View File

@ -52,14 +52,14 @@ public class SkeletonDataAsset : ScriptableObject {
public SkeletonData GetSkeletonData (bool quiet) {
if (atlasAsset == null) {
if (!quiet)
Debug.LogWarning("Atlas not set for skeleton data asset: " + name, this);
Debug.LogError("Atlas not set for SkeletonData asset: " + name, this);
Clear();
return null;
}
if (skeletonJSON == null) {
if (!quiet)
Debug.LogWarning("Skeleton JSON file not set for skeleton data asset: " + name, this);
Debug.LogError("Skeleton JSON file not set for SkeletonData asset: " + name, this);
Clear();
return null;
}
@ -73,13 +73,14 @@ public class SkeletonDataAsset : ScriptableObject {
if (skeletonData != null)
return skeletonData;
atlas.FlipV();
SkeletonJson json = new SkeletonJson(atlas);
json.Scale = scale;
try {
skeletonData = json.ReadSkeletonData(new StringReader(skeletonJSON.text));
} catch (Exception ex) {
if (!quiet)
Debug.Log("Error reading skeleton JSON file for skeleton data asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
Debug.LogError("Error reading skeleton JSON file for SkeletonData asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
return null;
}

View File

@ -0,0 +1,366 @@
/******************************************************************************
* Spine Runtimes Software License
* Version 2.1
*
* Copyright (c) 2013, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable and
* non-transferable license to install, execute and perform the Spine Runtimes
* Software (the "Software") solely for internal use. Without the written
* permission of Esoteric Software (typically granted by licensing Spine), you
* may not (a) modify, translate, adapt or otherwise create derivative works,
* improvements of the Software or develop new applications using the Software
* or (b) remove, delete, alter or obscure any trademarks or any copyright,
* trademark, patent or other intellectual property or proprietary rights
* notices on or in the Software, including any copy thereof. Redistributions
* in binary or source form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) 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 THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using Spine;
/// <summary>Renders a skeleton.</summary>
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SkeletonRenderer : MonoBehaviour {
public bool valid;
public Skeleton skeleton;
public SkeletonDataAsset skeletonDataAsset;
public String initialSkinName;
public bool calculateNormals;
public bool calculateTangents;
public float zSpacing;
private MeshFilter meshFilter;
private Mesh mesh, mesh1, mesh2;
private bool useMesh1;
private float[] tempVertices = new float[8];
private int lastVertexCount;
private Vector3[] vertices;
private Color32[] colors;
private Vector2[] uvs;
private Material[] sharedMaterials = new Material[0];
private readonly List<Material> submeshMaterials = new List<Material>();
private readonly List<Submesh> submeshes = new List<Submesh>();
private readonly int[] quadTriangles = {0, 2, 1, 2, 3, 1};
public virtual void Reset () {
if (meshFilter != null) meshFilter.sharedMesh = null;
if (mesh != null) DestroyImmediate(mesh);
if (renderer != null) renderer.sharedMaterial = null;
mesh = null;
mesh1 = null;
mesh2 = null;
lastVertexCount = 0;
vertices = null;
colors = null;
uvs = null;
sharedMaterials = new Material[0];
submeshMaterials.Clear();
submeshes.Clear();
skeleton = null;
if (!skeletonDataAsset) {
Debug.LogError("Missing SkeletonData asset.", this);
valid = false;
return;
}
valid = true;
meshFilter = GetComponent<MeshFilter>();
mesh1 = newMesh();
mesh2 = newMesh();
vertices = new Vector3[0];
skeleton = new Skeleton(skeletonDataAsset.GetSkeletonData(false));
if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
skeleton.SetSkin(initialSkinName);
}
public void Awake () {
Reset();
}
private Mesh newMesh () {
Mesh mesh = new Mesh();
mesh.name = "Skeleton Mesh";
mesh.hideFlags = HideFlags.HideAndDontSave;
mesh.MarkDynamic();
return mesh;
}
public virtual void OnWillRenderObject () {
if (!valid) return;
// Count vertices and submesh triangles.
int vertexCount = 0;
int submeshTriangleCount = 0, submeshFirstVertex = 0, submeshStartSlotIndex = 0;
Material lastMaterial = null;
submeshMaterials.Clear();
List<Slot> drawOrder = skeleton.DrawOrder;
int drawOrderCount = drawOrder.Count;
for (int i = 0; i < drawOrderCount; i++) {
Attachment attachment = drawOrder[i].attachment;
object rendererObject;
int attachmentVertexCount, attachmentTriangleCount;
if (attachment is RegionAttachment) {
rendererObject = ((RegionAttachment)attachment).RendererObject;
attachmentVertexCount = 4;
attachmentTriangleCount = 6;
} else if (attachment is MeshAttachment) {
MeshAttachment meshAttachment = (MeshAttachment)attachment;
rendererObject = meshAttachment.RendererObject;
attachmentVertexCount = meshAttachment.vertices.Length / 2;
attachmentTriangleCount = meshAttachment.triangles.Length;
} else if (attachment is SkinnedMeshAttachment) {
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
rendererObject = meshAttachment.RendererObject;
attachmentVertexCount = meshAttachment.uvs.Length / 2;
attachmentTriangleCount = meshAttachment.triangles.Length;
} else
continue;
// Populate submesh when material changes.
Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
if (lastMaterial != material && lastMaterial != null) {
addSubmesh(lastMaterial, submeshStartSlotIndex, i, submeshTriangleCount, submeshFirstVertex, false);
submeshTriangleCount = 0;
submeshFirstVertex = vertexCount;
submeshStartSlotIndex = i;
}
lastMaterial = material;
submeshTriangleCount += attachmentTriangleCount;
vertexCount += attachmentVertexCount;
}
addSubmesh(lastMaterial, submeshStartSlotIndex, drawOrderCount, submeshTriangleCount, submeshFirstVertex, true);
// Set materials.
if (submeshMaterials.Count == sharedMaterials.Length)
submeshMaterials.CopyTo(sharedMaterials);
else
sharedMaterials = submeshMaterials.ToArray();
renderer.sharedMaterials = sharedMaterials;
// Ensure mesh data is the right size.
Vector3[] vertices = this.vertices;
bool newTriangles = vertexCount > vertices.Length;
if (newTriangles) {
// Not enough vertices, increase size.
this.vertices = vertices = new Vector3[vertexCount];
this.colors = new Color32[vertexCount];
this.uvs = new Vector2[vertexCount];
mesh1.Clear();
mesh2.Clear();
} else {
// Too many vertices, zero the extra.
Vector3 zero = Vector3.zero;
for (int i = vertexCount, n = lastVertexCount; i < n; i++)
vertices[i] = zero;
}
lastVertexCount = vertexCount;
// Setup mesh.
float[] tempVertices = this.tempVertices;
Vector2[] uvs = this.uvs;
Color32[] colors = this.colors;
int vertexIndex = 0;
Color32 color = new Color32();
float x = skeleton.x, y = skeleton.y, zSpacing = this.zSpacing;
float a = skeleton.a * 255, r = skeleton.r, g = skeleton.g, b = skeleton.b;
for (int i = 0; i < drawOrderCount; i++) {
Slot slot = drawOrder[i];
Attachment attachment = slot.attachment;
if (attachment is RegionAttachment) {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.ComputeWorldVertices(x, y, slot.bone, tempVertices);
float z = i * zSpacing;
vertices[vertexIndex] = new Vector3(tempVertices[RegionAttachment.X1], tempVertices[RegionAttachment.Y1], z);
vertices[vertexIndex + 1] = new Vector3(tempVertices[RegionAttachment.X4], tempVertices[RegionAttachment.Y4], z);
vertices[vertexIndex + 2] = new Vector3(tempVertices[RegionAttachment.X2], tempVertices[RegionAttachment.Y2], z);
vertices[vertexIndex + 3] = new Vector3(tempVertices[RegionAttachment.X3], tempVertices[RegionAttachment.Y3], z);
color.a = (byte)(a * slot.a * regionAttachment.a);
color.r = (byte)(r * slot.r * regionAttachment.r * color.a);
color.g = (byte)(g * slot.g * regionAttachment.g * color.a);
color.b = (byte)(b * slot.b * regionAttachment.b * color.a);
if (slot.data.additiveBlending) color.a = 0;
colors[vertexIndex] = color;
colors[vertexIndex + 1] = color;
colors[vertexIndex + 2] = color;
colors[vertexIndex + 3] = color;
float[] regionUVs = regionAttachment.uvs;
uvs[vertexIndex] = new Vector2(regionUVs[RegionAttachment.X1], regionUVs[RegionAttachment.Y1]);
uvs[vertexIndex + 1] = new Vector2(regionUVs[RegionAttachment.X4], regionUVs[RegionAttachment.Y4]);
uvs[vertexIndex + 2] = new Vector2(regionUVs[RegionAttachment.X2], regionUVs[RegionAttachment.Y2]);
uvs[vertexIndex + 3] = new Vector2(regionUVs[RegionAttachment.X3], regionUVs[RegionAttachment.Y3]);
vertexIndex += 4;
} else if (attachment is MeshAttachment) {
MeshAttachment meshAttachment = (MeshAttachment)attachment;
int meshVertexCount = meshAttachment.vertices.Length;
if (tempVertices.Length < meshVertexCount) tempVertices = new float[meshVertexCount];
meshAttachment.ComputeWorldVertices(x, y, slot, tempVertices);
color.a = (byte)(a * slot.a * meshAttachment.a);
color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
if (slot.data.additiveBlending) color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;
for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++) {
vertices[vertexIndex] = new Vector3(tempVertices[ii], tempVertices[ii + 1], z);
colors[vertexIndex] = color;
uvs[vertexIndex] = new Vector2(meshUVs[ii], meshUVs[ii + 1]);
}
} else if (attachment is SkinnedMeshAttachment) {
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
int meshVertexCount = meshAttachment.uvs.Length;
if (tempVertices.Length < meshVertexCount) tempVertices = new float[meshVertexCount];
meshAttachment.ComputeWorldVertices(x, y, slot, tempVertices);
color.a = (byte)(a * slot.a * meshAttachment.a);
color.r = (byte)(r * slot.r * meshAttachment.r * color.a);
color.g = (byte)(g * slot.g * meshAttachment.g * color.a);
color.b = (byte)(b * slot.b * meshAttachment.b * color.a);
if (slot.data.additiveBlending) color.a = 0;
float[] meshUVs = meshAttachment.uvs;
float z = i * zSpacing;
for (int ii = 0; ii < meshVertexCount; ii += 2, vertexIndex++) {
vertices[vertexIndex] = new Vector3(tempVertices[ii], tempVertices[ii + 1], z);
colors[vertexIndex] = color;
uvs[vertexIndex] = new Vector2(meshUVs[ii], meshUVs[ii + 1]);
}
}
}
// Double buffer mesh.
Mesh mesh = useMesh1 ? mesh1 : mesh2;
meshFilter.sharedMesh = mesh;
mesh.vertices = vertices;
mesh.colors32 = colors;
mesh.uv = uvs;
int submeshCount = submeshMaterials.Count;
mesh.subMeshCount = submeshCount;
for (int i = 0; i < submeshCount; ++i)
mesh.SetTriangles(submeshes[i].triangles, i);
mesh.RecalculateBounds();
if (newTriangles && calculateNormals) {
Vector3[] normals = new Vector3[vertexCount];
Vector3 normal = new Vector3(0, 0, -1);
for (int i = 0; i < vertexCount; i++)
normals[i] = normal;
(useMesh1 ? mesh2 : mesh1).vertices = vertices; // Set other mesh vertices.
mesh1.normals = normals;
mesh2.normals = normals;
if (calculateTangents) {
Vector4[] tangents = new Vector4[vertexCount];
Vector3 tangent = new Vector3(0, 0, 1);
for (int i = 0; i < vertexCount; i++)
tangents[i] = tangent;
mesh1.tangents = tangents;
mesh2.tangents = tangents;
}
}
useMesh1 = !useMesh1;
}
/** Stores vertices and triangles for a single material. */
private void addSubmesh (Material material, int startSlot, int endSlot, int triangleCount, int firstVertex, bool lastSubmesh) {
int submeshIndex = submeshMaterials.Count;
submeshMaterials.Add(material);
if (submeshes.Count <= submeshIndex) submeshes.Add(new Submesh());
Submesh submesh = submeshes[submeshIndex];
int[] triangles = submesh.triangles;
int trianglesCapacity = triangles.Length;
if (lastSubmesh && trianglesCapacity > triangleCount) {
// Last submesh may have more triangles than required, so zero triangles to the end.
for (int i = triangleCount; i < trianglesCapacity; i++)
triangles[i] = 0;
submesh.triangleCount = triangleCount;
} else if (trianglesCapacity != triangleCount) {
// Reallocate triangles when not the exact size needed.
submesh.triangles = triangles = new int[triangleCount];
submesh.triangleCount = 0;
}
List<Slot> drawOrder = skeleton.DrawOrder;
for (int i = startSlot, triangleIndex = 0; i < endSlot; i++) {
Attachment attachment = drawOrder[i].attachment;
int[] attachmentTriangles;
int attachmentVertexCount;
if (attachment is RegionAttachment) {
attachmentVertexCount = 4;
attachmentTriangles = quadTriangles;
} else if (attachment is MeshAttachment) {
MeshAttachment meshAttachment = (MeshAttachment)attachment;
attachmentVertexCount = meshAttachment.vertices.Length / 2;
attachmentTriangles = meshAttachment.triangles;
} else if (attachment is SkinnedMeshAttachment) {
SkinnedMeshAttachment meshAttachment = (SkinnedMeshAttachment)attachment;
attachmentVertexCount = meshAttachment.uvs.Length / 2;
attachmentTriangles = meshAttachment.triangles;
} else
continue;
for (int ii = 0, nn = attachmentTriangles.Length; ii < nn; ii++, triangleIndex++)
triangles[triangleIndex] = firstVertex + attachmentTriangles[ii];
firstVertex += attachmentVertexCount;
}
}
#if UNITY_EDITOR
void OnDrawGizmos() {
// Make selection easier by drawing a clear gizmo over the skeleton.
if (vertices == null) return;
Vector3 gizmosCenter = new Vector3();
Vector3 gizmosSize = new Vector3();
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, 0f);
Vector3 max = new Vector3(float.MinValue, float.MinValue, 0f);
foreach (Vector3 vert in vertices) {
min = Vector3.Min (min, vert);
max = Vector3.Max (max, vert);
}
float width = max.x - min.x;
float height = max.y - min.y;
gizmosCenter = new Vector3(min.x + (width / 2f), min.y + (height / 2f), 0f);
gizmosSize = new Vector3(width, height, 1f);
Gizmos.color = Color.clear;
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(gizmosCenter, gizmosSize);
}
#endif
}
class Submesh {
public int[] triangles = new int[0];
public int triangleCount;
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a0d5e4e6acf3fd8489cb53ffe164e0b4
guid: e075b9a3e08e2f74fbd651c858ab16ed
MonoImporter:
serializedVersion: 2
defaultReferences: []

View File

@ -44,8 +44,8 @@ public class Goblins : MonoBehaviour {
}
// This is called after the animation is applied to the skeleton and can be used to adjust the bones dynamically.
public void UpdateBones (SkeletonComponent skeletonAnimation) {
headBone.Rotation -= 15;
public void UpdateBones (SkeletonAnimation skeletonAnimation) {
headBone.Rotation += 15;
}
public void OnMouseDown () {

View File

@ -41,8 +41,8 @@ public class Spineboy : MonoBehaviour {
skeletonAnimation = GetComponent<SkeletonAnimation>();
// Call our method any time an animation fires an event.
skeletonAnimation.state.Event += Event;
// Queue jump to be played on track 0 after the starting animation.
skeletonAnimation.state.AddAnimation(0, "jump", false, 0);
// Queue jump to be played on track 0 tree seconds after the starting animation.
skeletonAnimation.state.AddAnimation(0, "jump", false, 3);
// Queue walk to be looped on track 0 after the jump animation.
skeletonAnimation.state.AddAnimation(0, "run", true, 0);
}

View File

@ -1815,55 +1815,55 @@
"slots": {
"front_fist": {
"attachment": [
{ "time": 0, "name": "front_fist_closed" },
{ "time": 0.2666, "name": "front_fist_open" }
{ "time": 0.1333, "name": "front_fist_closed" },
{ "time": 0.4, "name": "front_fist_open" }
]
},
"mouth": {
"attachment": [
{ "time": 0, "name": "mouth_grind" }
{ "time": 0.1333, "name": "mouth_grind" }
]
},
"muzzle": {
"attachment": [
{ "time": 0, "name": "muzzle" },
{ "time": 0.1333, "name": null }
{ "time": 0.1333, "name": "muzzle" },
{ "time": 0.2666, "name": null }
],
"color": [
{
"time": 0,
"time": 0.1333,
"color": "ffffff00",
"curve": [ 0.118, 0.99, 0.75, 1 ]
},
{
"time": 0.0333,
"time": 0.1666,
"color": "ffffffff",
"curve": [ 0.821, 0, 0.909, 0.89 ]
},
{ "time": 0.1333, "color": "ffffff00" }
{ "time": 0.2666, "color": "ffffff00" }
]
}
},
"bones": {
"front_fist": {
"scale": [
{ "time": 0, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.2666, "x": 1, "y": 1 }
{ "time": 0.1333, "x": 1, "y": 1, "curve": "stepped" },
{ "time": 0.4, "x": 1, "y": 1 }
]
},
"gunTip": {
"translate": [
{ "time": 0, "x": 0, "y": 0 },
{ "time": 0.0666, "x": 20.93, "y": 1.57 }
{ "time": 0.1333, "x": 0, "y": 0 },
{ "time": 0.2, "x": 20.93, "y": 1.57 }
],
"scale": [
{ "time": 0, "x": 1, "y": 1 },
{ "time": 0.0666, "x": 1.247, "y": 1.516 }
{ "time": 0.1333, "x": 1, "y": 1 },
{ "time": 0.2, "x": 1.247, "y": 1.516 }
]
},
"gun": {
"rotate": [
{ "time": 0, "angle": 0 }
{ "time": 0, "angle": 1.9 }
],
"translate": [
{
@ -1872,13 +1872,13 @@
"y": 5.84,
"curve": [ 0, 0.3, 0.678, 1 ]
},
{ "time": 0.1666, "x": -9.3, "y": -1.41 },
{ "time": 0.2666, "x": 0, "y": 0 }
{ "time": 0.3, "x": -9.3, "y": -1.41 },
{ "time": 0.4, "x": 0, "y": 0 }
]
},
"rear_bracer": {
"rotate": [
{ "time": 0, "angle": 0 }
{ "time": 0, "angle": -30.47 }
],
"translate": [
{
@ -1887,13 +1887,13 @@
"y": 0,
"curve": [ 0, 0.3, 0.678, 1 ]
},
{ "time": 0.1666, "x": -5.99, "y": -3.71 },
{ "time": 0.2666, "x": 0, "y": 0 }
{ "time": 0.3, "x": -5.99, "y": -3.71 },
{ "time": 0.4, "x": 0, "y": 0 }
]
},
"rear_upper_arm": {
"rotate": [
{ "time": 0, "angle": 0 }
{ "time": 0, "angle": 62.3 }
],
"translate": [
{
@ -1902,8 +1902,8 @@
"y": 0,
"curve": [ 0, 0.3, 0.678, 1 ]
},
{ "time": 0.1666, "x": 2.81, "y": 11.41 },
{ "time": 0.2666, "x": 0, "y": 0 }
{ "time": 0.3, "x": 2.81, "y": 11.41 },
{ "time": 0.4, "x": 0, "y": 0 }
]
}
}