Created
March 22, 2013 02:14
-
-
Save jmcguirk/5218443 to your computer and use it in GitHub Desktop.
TK2D Spine Controller Runtime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using System.Collections.Generic; | |
using Spine; | |
/// <summary> | |
/// A Unity specific implementation of a skeleton controller. | |
/// </summary> | |
public class SkeletonController : MonoBehaviour | |
{ | |
public TextAsset skeletonDataFile = null; | |
public TextAsset[] animationDataFiles; | |
/// <summary> | |
/// The current animation being played | |
/// </summary> | |
protected string m_currentAnimation; | |
protected Skeleton skeleton; | |
protected Dictionary<string, SpineAnimation> boneAnimations; | |
protected bool isDebugDrawOn = true; | |
private float animationTime = 0; | |
private bool isSpineAnimationReady = false; | |
#region Unity Functions | |
/// <summary> | |
/// Call down to the protected member functions, | |
/// so deriving classes can override. | |
/// </summary> | |
void Awake() | |
{ | |
this.AwakeController(); | |
} | |
/// <summary> | |
/// Call down to the protected member functions, | |
/// so deriving classes can override. | |
/// </summary> | |
void Update() | |
{ | |
this.UpdateController(); | |
} | |
#endregion | |
protected virtual void AwakeController() | |
{ | |
if(skeletonDataFile == null) | |
throw new MissingComponentException("Skeleton data missing from sprite"); | |
if(animationDataFiles.Length < 1) | |
throw new MissingComponentException("Animation data missing from sprite"); | |
this.BuildSpineAnimations(); | |
} | |
protected virtual void UpdateController() | |
{ | |
if(this.isSpineAnimationReady) | |
{ | |
//Stepped animation play | |
/*if(Input.anyKeyDown && this.boneAnimations.Length > 0) | |
{ | |
Debug.Log(animationTime.ToString()); | |
animationTime += 0.05f; | |
SpineAnimation animationToPlay = this.boneAnimations[0]; | |
animationToPlay.Apply(this.skeleton, animationTime, true); | |
}*/ | |
//Continuous Animation play | |
if (this.boneAnimations != null && !string.IsNullOrEmpty(m_currentAnimation)) | |
{ | |
animationTime += Time.deltaTime; | |
SpineAnimation animationToPlay = this.boneAnimations[m_currentAnimation]; | |
animationToPlay.Apply(this.skeleton, animationTime, true); | |
} | |
this.skeleton.UpdateWorldTransform(); | |
if(this.isDebugDrawOn) | |
this.Debug_Draw(); | |
} | |
} | |
/// <summary> | |
/// Gets or sets the current animation. | |
/// </summary> | |
/// <value> | |
/// The current animation. | |
/// </value> | |
public string currentAnimation { | |
get { | |
return m_currentAnimation; | |
} | |
set { | |
animationTime = 0; | |
m_currentAnimation = value; | |
} | |
} | |
protected void Debug_Draw() | |
{ | |
foreach(Slot slot in this.skeleton.drawOrder) | |
{ | |
Bone bone = slot.bone; | |
Vector3 worldStartPos = this.gameObject.transform.position; | |
Vector3 worldEndPos = Vector3.zero; | |
worldStartPos.x += bone.worldX; | |
worldStartPos.y += bone.worldY; | |
worldEndPos.x = worldStartPos.x + bone.m00 * bone.length; | |
worldEndPos.y = worldStartPos.y + bone.m10 * bone.length; | |
Debug.DrawLine(worldStartPos, worldEndPos); | |
} | |
} | |
protected void BuildSpineAnimations() | |
{ | |
IDictionary<string, object> jsonData = MiniJSON.Json.Deserialize(skeletonDataFile.text) as IDictionary<string, object>; | |
SkeletonData skeletonData = new SkeletonData(jsonData); | |
this.skeleton = new Skeleton(skeletonData); | |
this.skeleton.SetToBindPose(); | |
this.skeleton.UpdateWorldTransform(); | |
//Todo: Compress animation data into one file for better performance | |
this.boneAnimations = new Dictionary<string, SpineAnimation>(); | |
for(int i = 0; i < animationDataFiles.Length; i ++) | |
{ | |
TextAsset animationDataFile = animationDataFiles[i]; | |
IDictionary<string, object> animationData = MiniJSON.Json.Deserialize(animationDataFile.text) as IDictionary<string, object>; | |
SpineAnimation newAnimation = new SpineAnimation(this.skeleton, animationData); | |
this.boneAnimations[animationDataFile.name.Split('.')[0]] = newAnimation; | |
} | |
this.isSpineAnimationReady = true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
[AddComponentMenu("2D Toolkit/Backend/tk2dBaseSprite")] | |
/// <summary> | |
/// Sprite base class. Performs target agnostic functionality and manages state parameters. | |
/// </summary> | |
public abstract class tk2dBaseSprite : MonoBehaviour, tk2dRuntime.ISpriteCollectionForceBuild | |
{ | |
/// <summary> | |
/// This is now private. You should use <see cref="tk2dBaseSprite.Collection">Collection</see> if you wish to read this value. | |
/// Use <see cref="tk2dBaseSprite.SwitchCollectionAndSprite">SwitchCollectionAndSprite</see> when you need to switch sprite collection. | |
/// </summary> | |
[SerializeField] | |
private tk2dSpriteCollectionData collection; | |
/// <summary> | |
/// Deprecation warning: the set accessor will be removed in a future version. | |
/// Use <see cref="tk2dBaseSprite.SwitchCollectionAndSprite">SwitchCollectionAndSprite</see> when you need to switch sprite collection. | |
/// </summary> | |
public tk2dSpriteCollectionData Collection | |
{ | |
get { return collection; } | |
set { collection = value; collectionInst = collection.inst; } | |
} | |
// This is the active instance of the sprite collection | |
protected tk2dSpriteCollectionData collectionInst; | |
[SerializeField] protected Color _color = Color.white; | |
[SerializeField] protected Vector3 _scale = new Vector3(1.0f, 1.0f, 1.0f); | |
[SerializeField] protected int _spriteId = 0; | |
/// <summary> | |
/// Specifies if this sprite is kept pixel perfect | |
/// </summary> | |
public bool pixelPerfect = false; | |
/// <summary> | |
/// Internal cached version of the box collider created for this sprite, if present. | |
/// </summary> | |
public BoxCollider boxCollider = null; | |
/// <summary> | |
/// Internal cached version of the mesh collider created for this sprite, if present. | |
/// </summary> | |
public MeshCollider meshCollider = null; | |
public Vector3[] meshColliderPositions = null; | |
public Mesh meshColliderMesh = null; | |
// This is unfortunate, but required due to the unpredictable script execution order in Unity. | |
// The only problem happens in Awake(), where if another class is Awaken before this one, and tries to | |
// modify this instance before it is initialized, very bad things could happen. | |
void InitInstance() | |
{ | |
if (collectionInst == null && collection != null) | |
collectionInst = collection.inst; | |
} | |
/// <summary> | |
/// Gets or sets the color. | |
/// </summary> | |
/// <value> | |
/// Please note the range for a Unity Color is 0..1 and not 0..255. | |
/// </value> | |
public Color color | |
{ | |
get { return _color; } | |
set | |
{ | |
if (value != _color) | |
{ | |
_color = value; | |
InitInstance(); | |
UpdateColors(); | |
} | |
} | |
} | |
/// <summary> | |
/// Gets or sets the scale. | |
/// </summary> | |
/// <value> | |
/// Use the sprite scale method as opposed to transform.localScale to avoid breaking dynamic batching. | |
/// </value> | |
public Vector3 scale | |
{ | |
get { return _scale; } | |
set | |
{ | |
if (value != _scale) | |
{ | |
_scale = value; | |
InitInstance(); | |
UpdateVertices(); | |
#if UNITY_EDITOR | |
EditMode__CreateCollider(); | |
#else | |
UpdateCollider(); | |
#endif | |
} | |
} | |
} | |
/// <summary> | |
/// Flips the sprite horizontally. | |
/// </summary> | |
public void FlipX() | |
{ | |
scale = new Vector3(-_scale.x, _scale.y, _scale.z); | |
} | |
/// <summary> | |
/// Flips the sprite vertically. | |
/// </summary> | |
public void FlipY() | |
{ | |
scale = new Vector3(_scale.x, -_scale.y, _scale.z); | |
} | |
/// <summary> | |
/// Gets or sets the sprite identifier. | |
/// </summary> | |
/// <value> | |
/// The spriteId is a unique number identifying each sprite. | |
/// Use <see cref="tk2dBaseSprite.GetSpriteIdByName">GetSpriteIdByName</see> to resolve an identifier from the current sprite collection. | |
/// </value> | |
public int spriteId | |
{ | |
get { return _spriteId; } | |
set | |
{ | |
if (value != _spriteId) | |
{ | |
InitInstance(); | |
value = Mathf.Clamp(value, 0, collectionInst.spriteDefinitions.Length - 1); | |
if (_spriteId < 0 || _spriteId >= collectionInst.spriteDefinitions.Length || | |
GetCurrentVertexCount() != collectionInst.spriteDefinitions[value].positions.Length || | |
collectionInst.spriteDefinitions[_spriteId].complexGeometry != collectionInst.spriteDefinitions[value].complexGeometry) | |
{ | |
_spriteId = value; | |
UpdateGeometry(); | |
} | |
else | |
{ | |
_spriteId = value; | |
UpdateVertices(); | |
} | |
UpdateMaterial(); | |
UpdateCollider(); | |
} | |
} | |
} | |
/// <summary> | |
/// Sets the sprite by identifier. | |
/// </summary> | |
public void SetSprite(int newSpriteId) { | |
this.spriteId = newSpriteId; | |
} | |
/// <summary> | |
/// Sets the sprite by name. The sprite will be selected from the current collection. | |
/// </summary> | |
public bool SetSprite(string spriteName) { | |
int spriteId = collection.GetSpriteIdByName(spriteName, -1); | |
if (spriteId != -1) { | |
SetSprite(spriteId); | |
} | |
return spriteId != -1; | |
} | |
/// <summary> | |
/// Sets sprite by identifier from the new collection. | |
/// </summary> | |
public void SetSprite(tk2dSpriteCollectionData newCollection, int spriteId) { | |
SwitchCollectionAndSprite(newCollection, spriteId); | |
} | |
/// <summary> | |
/// Sets sprite by name from the new collection. | |
/// </summary> | |
public bool SetSprite(tk2dSpriteCollectionData newCollection, string spriteName) { | |
return SwitchCollectionAndSprite(newCollection, spriteName); | |
} | |
/// <summary> | |
/// Switches the sprite collection and sprite. | |
/// Simply set the <see cref="tk2dBaseSprite.spriteId">spriteId</see> property when you don't need to switch the sprite collection. | |
/// This will be deprecated in a future release, use SetSprite instead. | |
/// </summary> | |
/// <param name='newCollection'> | |
/// A reference to the sprite collection to switch to. | |
/// </param> | |
/// <param name='newSpriteId'> | |
/// New sprite identifier. | |
/// </param> | |
public void SwitchCollectionAndSprite(tk2dSpriteCollectionData newCollection, int newSpriteId) | |
{ | |
bool switchedCollection = false; | |
if (Collection != newCollection) | |
{ | |
collection = newCollection; | |
collectionInst = collection.inst; | |
_spriteId = -1; // force an update, but only when the collection has changed | |
switchedCollection = true; | |
} | |
spriteId = newSpriteId; | |
if (switchedCollection) | |
{ | |
UpdateMaterial(); | |
} | |
} | |
/// <summary> | |
/// Switches the sprite collection and sprite. | |
/// Simply set the <see cref="tk2dBaseSprite.spriteId">spriteId</see> property when you don't need to switch the sprite collection. | |
/// This will be deprecated in a future release, use SetSprite instead. | |
/// </summary> | |
/// <param name='newCollection'> | |
/// A reference to the sprite collection to switch to. | |
/// </param> | |
/// <param name='spriteName'> | |
/// Sprite name. | |
/// </param> | |
public bool SwitchCollectionAndSprite(tk2dSpriteCollectionData newCollection, string spriteName) | |
{ | |
int spriteId = newCollection.GetSpriteIdByName(spriteName, -1); | |
if (spriteId != -1) { | |
SwitchCollectionAndSprite(newCollection, spriteId); | |
} | |
return spriteId != -1; | |
} | |
/// <summary> | |
/// Makes the sprite pixel perfect to the active camera. | |
/// Automatically detects <see cref="tk2dCamera"/> if present | |
/// Otherwise uses Camera.main | |
/// </summary> | |
public void MakePixelPerfect() | |
{ | |
float s = 1.0f; | |
tk2dPixelPerfectHelper pph = tk2dPixelPerfectHelper.inst; | |
if (pph) | |
{ | |
if (pph.CameraIsOrtho) | |
{ | |
s = pph.scaleK; | |
} | |
else | |
{ | |
s = pph.scaleK + pph.scaleD * transform.position.z; | |
} | |
} | |
else if (tk2dCamera.inst) | |
{ | |
if (Collection.version < 2) | |
{ | |
Debug.LogError("Need to rebuild sprite collection."); | |
} | |
s = Collection.halfTargetHeight; | |
} | |
else if (Camera.main) | |
{ | |
if (Camera.main.isOrthoGraphic) | |
{ | |
s = Camera.main.orthographicSize; | |
} | |
else | |
{ | |
float zdist = (transform.position.z - Camera.main.transform.position.z); | |
s = tk2dPixelPerfectHelper.CalculateScaleForPerspectiveCamera(Camera.main.fov, zdist); | |
} | |
} | |
else | |
{ | |
Debug.LogError("Main camera not found."); | |
} | |
s *= Collection.invOrthoSize; | |
scale = new Vector3(Mathf.Sign(scale.x) * s * pixelPerfectScaleX, Mathf.Sign(scale.y) * s * pixelPerfectScaleY, Mathf.Sign(scale.z) * s); | |
} | |
public float pixelPerfectScaleX = 1; | |
public float pixelPerfectScaleY = 1; | |
protected abstract void UpdateMaterial(); // update material when switching spritecollection | |
protected abstract void UpdateColors(); // reupload color data only | |
protected abstract void UpdateVertices(); // reupload vertex data only | |
protected abstract void UpdateGeometry(); // update full geometry (including indices) | |
protected abstract int GetCurrentVertexCount(); // return current vertex count | |
/// <summary> | |
/// Rebuilds the mesh data for this sprite. Not usually necessary to call this, unless some internal states are modified. | |
/// </summary> | |
public abstract void Build(); | |
/// <summary> | |
/// Resolves a sprite name and returns a unique id for the sprite. | |
/// Convenience alias of <see cref="tk2dSpriteCollectionData.GetSpriteIdByName"/> | |
/// </summary> | |
/// <returns> | |
/// Unique Sprite Id. | |
/// </returns> | |
/// <param name='name'>Case sensitive sprite name, as defined in the sprite collection. This is usually the source filename excluding the extension</param> | |
public int GetSpriteIdByName(string name) | |
{ | |
InitInstance(); | |
return collectionInst.GetSpriteIdByName(name); | |
} | |
/// <summary> | |
/// Adds a tk2dBaseSprite derived class as a component to the gameObject passed in, setting up necessary parameters | |
/// and building geometry. | |
/// </summary> | |
public static T AddComponent<T>(GameObject go, tk2dSpriteCollectionData spriteCollection, int spriteId) where T : tk2dBaseSprite | |
{ | |
T sprite = go.AddComponent<T>(); | |
sprite._spriteId = -1; | |
sprite.SetSprite(spriteCollection, spriteId); | |
sprite.Build(); | |
return sprite; | |
} | |
/// <summary> | |
/// Adds a tk2dBaseSprite derived class as a component to the gameObject passed in, setting up necessary parameters | |
/// and building geometry. Shorthand using sprite name | |
/// </summary> | |
public static T AddComponent<T>(GameObject go, tk2dSpriteCollectionData spriteCollection, string spriteName) where T : tk2dBaseSprite | |
{ | |
int spriteId = spriteCollection.GetSpriteIdByName(spriteName, -1); | |
if (spriteId == -1) { | |
Debug.LogError( string.Format("Unable to find sprite named {0} in sprite collection {1}", spriteName, spriteCollection.spriteCollectionName) ); | |
return null; | |
} | |
else { | |
return AddComponent<T>(go, spriteCollection, spriteId); | |
} | |
} | |
protected int GetNumVertices() | |
{ | |
InitInstance(); | |
return collectionInst.spriteDefinitions[spriteId].positions.Length; | |
} | |
protected int GetNumIndices() | |
{ | |
InitInstance(); | |
return collectionInst.spriteDefinitions[spriteId].indices.Length; | |
} | |
protected void SetPositions(Vector3[] positions, Vector3[] normals, Vector4[] tangents) | |
{ | |
var sprite = collectionInst.spriteDefinitions[spriteId]; | |
int numVertices = GetNumVertices(); | |
for (int i = 0; i < numVertices; ++i) | |
{ | |
positions[i].x = sprite.positions[i].x * _scale.x; | |
positions[i].y = sprite.positions[i].y * _scale.y; | |
positions[i].z = sprite.positions[i].z * _scale.z; | |
} | |
// The secondary test sprite.normals != null must have been performed prior to this function call | |
if (normals.Length > 0) | |
{ | |
for (int i = 0; i < numVertices; ++i) | |
{ | |
normals[i] = sprite.normals[i]; | |
} | |
} | |
// The secondary test sprite.tangents != null must have been performed prior to this function call | |
if (tangents.Length > 0) | |
{ | |
for (int i = 0; i < numVertices; ++i) | |
{ | |
tangents[i] = sprite.tangents[i]; | |
} | |
} | |
} | |
protected void SetColors(Color[] dest) | |
{ | |
Color c = _color; | |
if (collectionInst.premultipliedAlpha) { c.r *= c.a; c.g *= c.a; c.b *= c.a; } | |
int numVertices = GetNumVertices(); | |
for (int i = 0; i < numVertices; ++i) | |
dest[i] = c; | |
} | |
/// <summary> | |
/// Gets the local space bounds of the sprite. | |
/// </summary> | |
/// <returns> | |
/// Local space bounds | |
/// </returns> | |
public Bounds GetBounds() | |
{ | |
InitInstance(); | |
var sprite = collectionInst.spriteDefinitions[_spriteId]; | |
return new Bounds(new Vector3(sprite.boundsData[0].x * _scale.x, sprite.boundsData[0].y * _scale.y, sprite.boundsData[0].z * _scale.z), | |
new Vector3(sprite.boundsData[1].x * _scale.x, sprite.boundsData[1].y * _scale.y, sprite.boundsData[1].z * _scale.z)); | |
} | |
/// <summary> | |
/// Gets untrimmed local space bounds of the sprite. This is the size of the sprite before 2D Toolkit trims away empty space in the sprite. | |
/// Use this when you need to position sprites in a grid, etc, when the trimmed bounds is not sufficient. | |
/// </summary> | |
/// <returns> | |
/// Local space untrimmed bounds | |
/// </returns> | |
public Bounds GetUntrimmedBounds() | |
{ | |
InitInstance(); | |
var sprite = collectionInst.spriteDefinitions[_spriteId]; | |
return new Bounds(new Vector3(sprite.untrimmedBoundsData[0].x * _scale.x, sprite.untrimmedBoundsData[0].y * _scale.y, sprite.untrimmedBoundsData[0].z * _scale.z), | |
new Vector3(sprite.untrimmedBoundsData[1].x * _scale.x, sprite.untrimmedBoundsData[1].y * _scale.y, sprite.untrimmedBoundsData[1].z * _scale.z)); | |
} | |
/// <summary> | |
/// Gets the current sprite definition. | |
/// </summary> | |
/// <returns> | |
/// <see cref="tk2dSpriteDefinition"/> for the currently active sprite. | |
/// </returns> | |
public tk2dSpriteDefinition GetCurrentSpriteDef() | |
{ | |
InitInstance(); | |
return collectionInst.spriteDefinitions[_spriteId]; | |
} | |
/// <summary> | |
/// Gets the current sprite definition. | |
/// </summary> | |
/// <returns> | |
/// <see cref="tk2dSpriteDefinition"/> for the currently active sprite. | |
/// </returns> | |
public tk2dSpriteDefinition CurrentSprite { | |
get { | |
InitInstance(); | |
return collectionInst.spriteDefinitions[_spriteId]; | |
} | |
} | |
// Unity functions | |
public void Start() | |
{ | |
if (pixelPerfect) | |
MakePixelPerfect(); | |
} | |
// Collider setup | |
protected virtual bool NeedBoxCollider() { return false; } | |
protected void UpdateCollider() | |
{ | |
var sprite = collectionInst.spriteDefinitions[_spriteId]; | |
if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Box && boxCollider == null) | |
{ | |
// Has the user created a box collider? | |
boxCollider = gameObject.GetComponent<BoxCollider>(); | |
if (boxCollider == null) | |
{ | |
// create box collider at runtime. this won't get removed from the object | |
boxCollider = gameObject.AddComponent<BoxCollider>(); | |
} | |
} | |
if (boxCollider != null) | |
{ | |
if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Box) | |
{ | |
boxCollider.center = new Vector3(sprite.colliderVertices[0].x * _scale.x, sprite.colliderVertices[0].y * _scale.y, sprite.colliderVertices[0].z * _scale.z); | |
boxCollider.extents = new Vector3(sprite.colliderVertices[1].x * _scale.x, sprite.colliderVertices[1].y * _scale.y, sprite.colliderVertices[1].z * _scale.z); | |
} | |
else if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Unset) | |
{ | |
// Don't do anything here, for backwards compatibility | |
} | |
else // in all cases, if the collider doesn't match is requested, null it out | |
{ | |
if (boxCollider != null) | |
{ | |
// move the box far far away, boxes with zero extents still collide | |
boxCollider.center = new Vector3(0, 0, -100000.0f); | |
boxCollider.extents = Vector3.zero; | |
} | |
} | |
} | |
} | |
// This is separate to UpdateCollider, as UpdateCollider can only work with BoxColliders, and will NOT create colliders | |
protected void CreateCollider() | |
{ | |
var sprite = collectionInst.spriteDefinitions[_spriteId]; | |
if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Unset) | |
{ | |
// do not attempt to create or modify anything if it is Unset | |
return; | |
} | |
// User has created a collider | |
if (collider != null) | |
{ | |
boxCollider = GetComponent<BoxCollider>(); | |
meshCollider = GetComponent<MeshCollider>(); | |
} | |
if ((NeedBoxCollider() || sprite.colliderType == tk2dSpriteDefinition.ColliderType.Box) && meshCollider == null) | |
{ | |
if (boxCollider == null) | |
{ | |
boxCollider = gameObject.AddComponent<BoxCollider>(); | |
} | |
} | |
else if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Mesh && boxCollider == null) | |
{ | |
// this should not be updated again (apart from scale changes in the editor, where we force regeneration of colliders) | |
if (meshCollider == null) | |
meshCollider = gameObject.AddComponent<MeshCollider>(); | |
if (meshColliderMesh == null) | |
meshColliderMesh = new Mesh(); | |
meshColliderMesh.Clear(); | |
meshColliderPositions = new Vector3[sprite.colliderVertices.Length]; | |
for (int i = 0; i < meshColliderPositions.Length; ++i) | |
meshColliderPositions[i] = new Vector3(sprite.colliderVertices[i].x * _scale.x, sprite.colliderVertices[i].y * _scale.y, sprite.colliderVertices[i].z * _scale.z); | |
meshColliderMesh.vertices = meshColliderPositions; | |
float s = _scale.x * _scale.y * _scale.z; | |
meshColliderMesh.triangles = (s >= 0.0f)?sprite.colliderIndicesFwd:sprite.colliderIndicesBack; | |
meshCollider.sharedMesh = meshColliderMesh; | |
meshCollider.convex = sprite.colliderConvex; | |
// this is required so our mesh pivot is at the right point | |
if (rigidbody) rigidbody.centerOfMass = Vector3.zero; | |
} | |
else if (sprite.colliderType != tk2dSpriteDefinition.ColliderType.None) | |
{ | |
// This warning is not applicable in the editor | |
if (Application.isPlaying) | |
{ | |
Debug.LogError("Invalid mesh collider on sprite, please remove and try again."); | |
} | |
} | |
UpdateCollider(); | |
} | |
#if UNITY_EDITOR | |
public void EditMode__CreateCollider() | |
{ | |
// Revert to runtime behaviour when the game is running | |
if (Application.isPlaying) | |
{ | |
UpdateCollider(); | |
return; | |
} | |
var sprite = collectionInst.spriteDefinitions[_spriteId]; | |
if (sprite.colliderType == tk2dSpriteDefinition.ColliderType.Unset) | |
return; | |
PhysicMaterial physicsMaterial = collider?collider.sharedMaterial:null; | |
bool isTrigger = collider?collider.isTrigger:false; | |
if (boxCollider) | |
{ | |
DestroyImmediate(boxCollider, true); | |
} | |
if (meshCollider) | |
{ | |
DestroyImmediate(meshCollider, true); | |
if (meshColliderMesh) | |
DestroyImmediate(meshColliderMesh, true); | |
} | |
CreateCollider(); | |
if (collider) | |
{ | |
collider.isTrigger = isTrigger; | |
collider.material = physicsMaterial; | |
} | |
} | |
#endif | |
protected void Awake() | |
{ | |
if (collection != null) | |
{ | |
collectionInst = collection.inst; | |
} | |
} | |
// tk2dRuntime.ISpriteCollectionEditor | |
public bool UsesSpriteCollection(tk2dSpriteCollectionData spriteCollection) | |
{ | |
return Collection == spriteCollection; | |
} | |
public virtual void ForceBuild() | |
{ | |
collectionInst = collection.inst; | |
if (spriteId < 0 || spriteId >= collectionInst.spriteDefinitions.Length) | |
spriteId = 0; | |
Build(); | |
#if UNITY_EDITOR | |
EditMode__CreateCollider(); | |
#endif | |
} | |
/// <summary> | |
/// Create a sprite (and gameObject) displaying the region of the texture specified. | |
/// Use <see cref="tk2dSpriteCollectionData.CreateFromTexture"/> if you need to create a sprite collection | |
/// with multiple sprites. | |
/// </summary> | |
public static GameObject CreateFromTexture<T>(Texture2D texture, tk2dRuntime.SpriteCollectionSize size, Rect region, Vector2 anchor) where T : tk2dBaseSprite | |
{ | |
tk2dSpriteCollectionData data = tk2dRuntime.SpriteCollectionGenerator.CreateFromTexture(texture, size, region, anchor); | |
if (data == null) | |
return null; | |
GameObject spriteGo = new GameObject(); | |
tk2dBaseSprite.AddComponent<T>(spriteGo, data, 0); | |
return spriteGo; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System.Collections; | |
using Spine; | |
/// <summary> | |
/// TK2D version of spine skeleton renderer | |
/// </summary> | |
public class TK2DSkeletonController : SkeletonController | |
{ | |
/// <summary> | |
/// references to a sprite collection data file | |
/// </summary> | |
public GameObject spriteAtlas; | |
/// <summary> | |
/// How much to scale the rig by | |
/// </summary> | |
public float rigScale = 1.0f; | |
/** | |
* Which direction this rig should be moving initially | |
*/ | |
public int rigDirection = 1; | |
/// <summary> | |
/// The initial animation the rig should be playing | |
/// </summary> | |
public string initialAnimation; | |
/// <summary> | |
/// A collection of spawned attachment sprites | |
/// </summary> | |
private tk2dSprite[] attachmentSprites = null; | |
/// <summary> | |
/// The skin name to use for this rig | |
/// </summary> | |
public string SkinName; | |
/// <summary> | |
/// The is atlas parsed | |
/// </summary> | |
private bool isAtlasParsed = false; | |
/// <summary> | |
/// Have the bones for this rig been rendered | |
/// </summary> | |
private bool isBonesRendered = false; | |
/// <summary> | |
/// Awakes the controller. | |
/// </summary> | |
/// <exception cref="UnityEngine.MissingComponentException"> | |
/// Skeleton data missing from sprite | |
/// or | |
/// Animation data missing from sprite | |
/// or | |
/// OT - missing componenet | |
/// </exception> | |
protected override void AwakeController() | |
{ | |
if(skeletonDataFile == null) | |
throw new MissingComponentException("Skeleton data missing from sprite"); | |
if(animationDataFiles.Length < 1) | |
throw new MissingComponentException("Animation data missing from sprite"); | |
if(spriteAtlas == null) | |
throw new MissingComponentException("OT - missing componenet"); | |
} | |
/// <summary> | |
/// Plays the given animation. | |
/// </summary> | |
/// <param name="animationName">Name of the animation.</param> | |
public void playAnimation(string animationName) { | |
this.currentAnimation = animationName; | |
} | |
/// <summary> | |
/// Updates the controller. | |
/// </summary> | |
protected override void UpdateController() | |
{ | |
if (!this.isBonesRendered) { | |
this.isBonesRendered = true; | |
this.OnAtlasReady(); | |
} | |
base.UpdateController(); | |
if(this.isAtlasParsed) | |
{ | |
for(int i=0; i <this.skeleton.drawOrder.Count; i++) | |
{ | |
Vector2 worldPos = Vector2.zero; | |
Vector2 worldScale = Vector2.zero; | |
float worldRotation = 0; | |
Slot slot = this.skeleton.slots[i]; | |
Attachment attachment = slot.attachment; | |
Bone bone = slot.bone; | |
tk2dSprite sprite = this.attachmentSprites[i]; | |
if(sprite != null) | |
{ | |
if (attachment != null) { | |
worldPos = this.ResolveWorldCoordinates(slot); | |
worldRotation = bone.worldRotation + attachment.rotation; | |
sprite.gameObject.SetActive(true); | |
} else { | |
sprite.gameObject.SetActive(false); | |
} | |
sprite.transform.localPosition = new Vector3(worldPos.x * rigScale * rigDirection, worldPos.y * rigScale, (float)(i * -0.1)); | |
sprite.transform.eulerAngles = new Vector3(0, 0, rigDirection * worldRotation); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Resolves the world coordinates. | |
/// </summary> | |
/// <param name="slot">The slot.</param> | |
/// <returns></returns> | |
private Vector2 ResolveWorldCoordinates(Slot slot) | |
{ | |
Vector2 worldPosition = Vector2.zero; | |
Attachment attachment = slot.attachment; | |
Bone bone = slot.bone; | |
worldPosition.x = attachment.x * bone.m00 + attachment.y * bone.m01 + bone.worldX; | |
worldPosition.y = attachment.x * bone.m10 + attachment.y * bone.m11 + bone.worldY; | |
return worldPosition; | |
} | |
/// <summary> | |
/// Called when [atlas ready]. | |
/// </summary> | |
void OnAtlasReady() | |
{ | |
base.BuildSpineAnimations(); | |
if (!string.IsNullOrEmpty(this.SkinName)) { | |
Skin skin = this.skeleton.data.FindSkin(this.SkinName); | |
skin.AttachAll(this.skeleton, this.skeleton.data.defaultSkin); | |
} | |
this.attachmentSprites = new tk2dSprite[this.skeleton.drawOrder.Count]; | |
for(int i=0; i <this.skeleton.drawOrder.Count; i++) | |
{ | |
Slot slot = this.skeleton.slots[i]; | |
Attachment attachment = slot.attachment; | |
Bone bone = slot.bone; | |
GameObject obj = new GameObject(); | |
obj.transform.parent = this.transform; | |
obj.name = bone.name; | |
if (attachment != null) { | |
if (!string.IsNullOrEmpty(attachment.name)) { | |
string atlasSpriteName = attachment.name; | |
tk2dSpriteCollectionData dat = spriteAtlas.GetComponent<tk2dSpriteCollectionData>(); | |
int id = dat.GetSpriteIdByName(attachment.name, -1); | |
if (id >= 0) { | |
tk2dSprite sprite = obj.AddComponent<tk2dSprite>(); | |
sprite.pixelPerfect = true; | |
sprite.pixelPerfectScaleX = rigDirection * rigScale; | |
sprite.pixelPerfectScaleY = rigScale; | |
sprite.name = bone.name; | |
sprite.SetSprite(dat, attachment.name); | |
this.attachmentSprites[i] = sprite; | |
} | |
} | |
} | |
} | |
this.currentAnimation = this.initialAnimation; | |
this.isAtlasParsed = true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment