Merge remote-tracking branch 'origin/master'

This commit is contained in:
NathanSweet 2016-02-06 17:49:49 +01:00
commit 230542fb7f
7 changed files with 186 additions and 111 deletions

View File

@ -35,33 +35,47 @@ using Spine;
public class CustomSkin : MonoBehaviour {
[System.Serializable]
public class SkinPair {
/// <summary>SpineAttachment attachment path to help find the attachment.</summary>
/// <remarks>This use of SpineAttachment generates an attachment path string that can only be used by SpineAttachment.GetAttachment.</remarks>
[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skinSource")]
public string sourceAttachment;
[UnityEngine.Serialization.FormerlySerializedAs("sourceAttachment")]
public string sourceAttachmentPath;
[SpineSlot]
public string targetSlot;
/// <summary>The name of the skin placeholder/skin dictionary entry this attachment should be associated with.</summary>
/// <remarks>This name is used by the skin dictionary, used in the method Skin.AddAttachment as well as setting a slot attachment</remarks>
[SpineAttachment(currentSkinOnly: true, placeholdersOnly: true)]
public string targetAttachment;
}
#region Inspector
public SkeletonDataAsset skinSource;
public SkinPair[] skinning;
[UnityEngine.Serialization.FormerlySerializedAs("skinning")]
public SkinPair[] skinItems;
public Skin customSkin;
#endregion
SkeletonRenderer skeletonRenderer;
void Start () {
skeletonRenderer = GetComponent<SkeletonRenderer>();
Skeleton skeleton = skeletonRenderer.skeleton;
customSkin = new Skin("CustomSkin");
foreach (var pair in skinning) {
var attachment = SpineAttachment.GetAttachment(pair.sourceAttachment, skinSource);
foreach (var pair in skinItems) {
var attachment = SpineAttachment.GetAttachment(pair.sourceAttachmentPath, skinSource);
customSkin.AddAttachment(skeleton.FindSlotIndex(pair.targetSlot), pair.targetAttachment, attachment);
}
// The custom skin does not need to be added to the skeleton data for it to work.
// But it's useful for your script to keep a reference to it.
skeleton.SetSkin(customSkin);
}
}

View File

@ -38,7 +38,7 @@ using Spine;
public class SkeletonAnimationInspector : SkeletonRendererInspector {
protected SerializedProperty animationName, loop, timeScale, autoReset;
protected bool m_isPrefab;
protected GUIContent autoResetLabel;
protected bool wasAnimationNameChanged;
protected override void OnEnable () {
base.OnEnable();
@ -56,43 +56,45 @@ public class SkeletonAnimationInspector : SkeletonRendererInspector {
SkeletonAnimation component = (SkeletonAnimation)target;
if (!component.valid)
return;
if (wasAnimationNameChanged) {
if (!Application.isPlaying) {
component.state.ClearTrack(0);
component.skeleton.SetToSetupPose();
}
//catch case where SetAnimation was used to set track 0 without using AnimationName
Spine.Animation animationToUse = component.skeleton.Data.FindAnimation(animationName.stringValue);
if (!Application.isPlaying) {
if (animationToUse != null) animationToUse.Apply(component.skeleton, 0f, 0f, false, null);
component.Update();
component.LateUpdate();
SceneView.RepaintAll();
} else {
if (animationToUse != null)
component.state.SetAnimation(0, animationToUse, loop.boolValue);
else
component.state.ClearTrack(0);
}
wasAnimationNameChanged = false;
}
// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
if (Application.isPlaying) {
TrackEntry currentState = component.state.GetCurrent(0);
if (currentState != null) {
TrackEntry current = component.state.GetCurrent(0);
if (current != null) {
if (component.AnimationName != animationName.stringValue) {
animationName.stringValue = currentState.Animation.Name;
animationName.stringValue = current.Animation.Name;
}
}
}
EditorGUILayout.Space();
//TODO: Refactor this to use GenericMenu and callbacks to avoid interfering with control by other behaviours.
// Animation name.
{
String[] animations = new String[component.skeleton.Data.Animations.Count + 1];
animations[0] = "<None>";
int animationIndex = 0;
for (int i = 0; i < animations.Length - 1; i++) {
String name = component.skeleton.Data.Animations.Items[i].Name;
animations[i + 1] = name;
if (name == animationName.stringValue)
animationIndex = i + 1;
}
animationIndex = EditorGUILayout.Popup("Animation", animationIndex, animations);
String selectedAnimationName = animationIndex == 0 ? null : animations[animationIndex];
if (component.AnimationName != selectedAnimationName) {
component.AnimationName = selectedAnimationName;
animationName.stringValue = selectedAnimationName;
}
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(animationName);
wasAnimationNameChanged |= EditorGUI.EndChangeCheck();
EditorGUILayout.PropertyField(loop);
EditorGUILayout.PropertyField(timeScale);
component.timeScale = Math.Max(component.timeScale, 0);

View File

@ -5,17 +5,19 @@
*****************************************************************************/
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Spine;
public struct SpineDrawerValuePair {
public string str;
public SerializedProperty property;
public SpineDrawerValuePair(string val, SerializedProperty property) {
public SpineDrawerValuePair (string val, SerializedProperty property) {
this.str = val;
this.property = property;
}
@ -23,6 +25,7 @@ public struct SpineDrawerValuePair {
public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineAttributeBase {
protected SkeletonDataAsset skeletonDataAsset;
protected T TargetAttribute { get { return (T)attribute; } }
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
@ -72,7 +75,7 @@ public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineA
return;
GenericMenu menu = new GenericMenu();
PopulateMenu (menu, property, this.TargetAttribute, data);
PopulateMenu(menu, property, this.TargetAttribute, data);
menu.ShowAsContext();
}
@ -84,7 +87,7 @@ public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineA
pair.property.serializedObject.ApplyModifiedProperties();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
return 18;
}
@ -96,7 +99,7 @@ public class SpineSlotDrawer : SpineTreeItemDrawerBase<SpineSlot> {
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
for (int i = 0; i < data.Slots.Count; i++) {
string name = data.Slots.Items[i].Name;
if (name.StartsWith(targetAttribute.startsWith)) {
if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
if (targetAttribute.containsBoundingBoxes) {
int slotIndex = i;
@ -139,7 +142,7 @@ public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
for (int i = 0; i < data.Skins.Count; i++) {
string name = data.Skins.Items[i].Name;
if (name.StartsWith(targetAttribute.startsWith))
if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
}
@ -150,9 +153,13 @@ public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
public class SpineAnimationDrawer : SpineTreeItemDrawerBase<SpineAnimation> {
protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineAnimation targetAttribute, SkeletonData data) {
var animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
// <None> item
menu.AddItem(new GUIContent("<None>"), property.stringValue == "", HandleSelect, new SpineDrawerValuePair("", property));
for (int i = 0; i < animations.Count; i++) {
string name = animations.Items[i].Name;
if (name.StartsWith(targetAttribute.startsWith))
if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
}
@ -165,7 +172,7 @@ public class SpineEventNameDrawer : SpineTreeItemDrawerBase<SpineEvent> {
var events = skeletonDataAsset.GetSkeletonData(false).Events;
for (int i = 0; i < events.Count; i++) {
string name = events.Items[i].Name;
if (name.StartsWith(targetAttribute.startsWith))
if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
}
}
@ -289,8 +296,8 @@ public class SpineBoneDrawer : SpineTreeItemDrawerBase<SpineBone> {
public class SpineAtlasRegionDrawer : PropertyDrawer {
Component component;
SerializedProperty atlasProp;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
if (property.propertyType != SerializedPropertyType.String) {
EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
return;
@ -321,7 +328,7 @@ public class SpineAtlasRegionDrawer : PropertyDrawer {
}
}
void Selector (SerializedProperty property) {
GenericMenu menu = new GenericMenu();
AtlasAsset atlasAsset = (AtlasAsset)atlasProp.objectReferenceValue;
@ -337,7 +344,7 @@ public class SpineAtlasRegionDrawer : PropertyDrawer {
menu.ShowAsContext();
}
static void HandleSelect (object val) {
var pair = (SpineDrawerValuePair)val;
pair.property.stringValue = pair.str;

View File

@ -68,6 +68,7 @@ public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation {
}
[SerializeField]
[SpineAnimation]
private String _animationName;
public String AnimationName {
@ -104,39 +105,25 @@ public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation {
public bool loop;
/// <summary>
/// The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.
/// AnimationState and TrackEntry also have their own timeScale. These are combined multiplicatively.</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>
#if UNITY_5
[Tooltip("The rate at which animations progress over time. 1 means 100%. 0.5 means 50%.")]
#endif
public float timeScale = 1;
#region AutoReset
/**
[Tooltip("Setting this to true makes the SkeletonAnimation behave similar to Spine editor. New animations will not inherit the pose from a previous animation. If you need to intermittently and programmatically pose your skeleton, leave this false.")]
[SerializeField]
protected bool autoReset = false;
/// <summary>
/// Setting this to true makes the SkeletonAnimation behave similar to Spine editor.
/// New animations will not inherit the pose from a previous animation.
/// If you need to intermittently and programmatically pose your skeleton, leave this false.</summary>
public bool AutoReset {
get { return this.autoReset; }
set {
if (!autoReset && value) {
state.Start -= HandleNewAnimationAutoreset; // make sure there isn't a double-subscription.
state.Start += HandleNewAnimationAutoreset;
}
autoReset = value;
}
#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) {
return SkeletonRenderer.AddSpineComponent<SkeletonAnimation>(gameObject, skeletonDataAsset);
}
protected virtual void HandleNewAnimationAutoreset (Spine.AnimationState state, int trackIndex) {
if (!autoReset) return;
if (skeleton != null) skeleton.SetToSetupPose();
/// <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) {
return SkeletonRenderer.NewSpineGameObject<SkeletonAnimation>(skeletonDataAsset);
}
*/
#endregion
public override void Reset () {
@ -146,16 +133,24 @@ public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation {
state = new Spine.AnimationState(skeletonDataAsset.GetAnimationStateData());
/*
if (autoReset) {
state.Start += HandleNewAnimationAutoreset;
#if UNITY_EDITOR
if (!string.IsNullOrEmpty(_animationName)) {
if (Application.isPlaying) {
state.SetAnimation(0, _animationName, loop);
} else {
// Assume SkeletonAnimation is valid for skeletonData and skeleton. Checked above.
var animationObject = skeletonDataAsset.GetSkeletonData(false).FindAnimation(_animationName);
if (animationObject != null)
animationObject.Apply(skeleton, 0f, 0f, false, null);
}
Update(0);
}
*/
if (_animationName != null && _animationName.Length > 0) {
#else
if (!string.IsNullOrEmpty(_animationName)) {
state.SetAnimation(0, _animationName, loop);
Update(0);
}
#endif
}
public virtual void Update () {
@ -185,4 +180,5 @@ public class SkeletonAnimation : SkeletonRenderer, ISkeletonAnimation {
_UpdateComplete(this);
}
}
}

View File

@ -1,7 +1,7 @@
/*****************************************************************************
* Spine Extensions created by Mitch Thompson
* Spine Extensions by Mitch Thompson and John Dy
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
@ -11,6 +11,14 @@ using Spine;
public static class SkeletonExtensions {
const float ByteToFloat = 1f / 255f;
#region Colors
public static Color GetColor (this Skeleton s) { return new Color(s.r, s.g, s.b, s.a); }
public static Color GetColor (this RegionAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
public static Color GetColor (this MeshAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
public static Color GetColor (this SkinnedMeshAttachment a) { return new Color(a.r, a.g, a.b, a.a); }
public static void SetColor (this Skeleton skeleton, Color color) {
skeleton.A = color.a;
skeleton.R = color.r;
@ -19,10 +27,10 @@ public static class SkeletonExtensions {
}
public static void SetColor (this Skeleton skeleton, Color32 color) {
skeleton.A = color.a / 255f;
skeleton.R = color.r / 255f;
skeleton.G = color.g / 255f;
skeleton.B = color.b / 255f;
skeleton.A = color.a * ByteToFloat;
skeleton.R = color.r * ByteToFloat;
skeleton.G = color.g * ByteToFloat;
skeleton.B = color.b * ByteToFloat;
}
public static void SetColor (this Slot slot, Color color) {
@ -33,10 +41,10 @@ public static class SkeletonExtensions {
}
public static void SetColor (this Slot slot, Color32 color) {
slot.A = color.a / 255f;
slot.R = color.r / 255f;
slot.G = color.g / 255f;
slot.B = color.b / 255f;
slot.A = color.a * ByteToFloat;
slot.R = color.r * ByteToFloat;
slot.G = color.g * ByteToFloat;
slot.B = color.b * ByteToFloat;
}
public static void SetColor (this RegionAttachment attachment, Color color) {
@ -47,10 +55,10 @@ public static class SkeletonExtensions {
}
public static void SetColor (this RegionAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
attachment.A = color.a * ByteToFloat;
attachment.R = color.r * ByteToFloat;
attachment.G = color.g * ByteToFloat;
attachment.B = color.b * ByteToFloat;
}
public static void SetColor (this MeshAttachment attachment, Color color) {
@ -61,10 +69,10 @@ public static class SkeletonExtensions {
}
public static void SetColor (this MeshAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
attachment.A = color.a * ByteToFloat;
attachment.R = color.r * ByteToFloat;
attachment.G = color.g * ByteToFloat;
attachment.B = color.b * ByteToFloat;
}
public static void SetColor (this SkinnedMeshAttachment attachment, Color color) {
@ -75,12 +83,14 @@ public static class SkeletonExtensions {
}
public static void SetColor (this SkinnedMeshAttachment attachment, Color32 color) {
attachment.A = color.a / 255f;
attachment.R = color.r / 255f;
attachment.G = color.g / 255f;
attachment.B = color.b / 255f;
attachment.A = color.a * ByteToFloat;
attachment.R = color.r * ByteToFloat;
attachment.G = color.g * ByteToFloat;
attachment.B = color.b * ByteToFloat;
}
#endregion
#region Bone Position
public static void SetPosition (this Bone bone, Vector2 position) {
bone.X = position.x;
bone.Y = position.y;
@ -91,10 +101,36 @@ public static class SkeletonExtensions {
bone.Y = position.y;
}
public static Vector2 GetSkeletonSpacePosition (this Bone bone) {
// TODO: This changes in v3.0
return new Vector2(bone.worldX, bone.worldY);
}
public static Vector3 GetWorldPosition (this Bone bone, UnityEngine.Transform parentTransform) {
return parentTransform.TransformPoint(new Vector3(bone.worldX, bone.worldY));
}
#endregion
#region Posing
/// <summary>
/// Shortcut for posing a skeleton at a specific time. Time is in seconds. (frameNumber / 30f) will give you seconds.
/// If you need to do this often, you should get the Animation object yourself using skeleton.data.FindAnimation. and call Apply on that.</summary>
/// <param name = "skeleton">The skeleton to pose.</param>
/// <param name="animationName">The name of the animation to use.</param>
/// <param name = "time">The time of the pose within the animation.</param>
/// <param name = "loop">Wraps the time around if it is longer than the duration of the animation.</param>
public static void PoseWithAnimation (this Skeleton skeleton, string animationName, float time, bool loop) {
// Fail loud when skeleton.data is null.
Spine.Animation animation = skeleton.data.FindAnimation(animationName);
if (animation == null) return;
animation.Apply(skeleton, 0, time, loop, null);
}
#endregion
#region Unity Sprite To Attachments
public static Attachment AttachUnitySprite (this Skeleton skeleton, string slotName, Sprite sprite, string shaderName = "Spine/Skeleton") {
var att = sprite.ToRegionAttachment(shaderName);
skeleton.FindSlot(slotName).Attachment = att;
return att;
}
@ -117,4 +153,6 @@ public static class SkeletonExtensions {
loader = null;
return att;
}
#endregion
}

View File

@ -76,6 +76,26 @@ public class SkeletonRenderer : MonoBehaviour {
private readonly ExposedList<Submesh> submeshes = new ExposedList<Submesh>();
private SkeletonUtilitySubmeshRenderer[] submeshRenderers;
#region Runtime Instantiation
/// <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) where T : SkeletonRenderer {
var c = gameObject.AddComponent<T>();
if (skeletonDataAsset != null) {
c.skeletonDataAsset = skeletonDataAsset;
c.Reset(); // TODO: Method name will change.
}
return c;
}
public static T NewSpineGameObject<T> (SkeletonDataAsset skeletonDataAsset) where T : SkeletonRenderer {
return SkeletonRenderer.AddSpineComponent<T>(new GameObject("New Spine GameObject"), skeletonDataAsset);
}
#endregion
public virtual void Awake () {
Reset();
}

View File

@ -85,14 +85,14 @@ public class SpineAttachment : SpineAttributeBase {
/// Smart popup menu for Spine Attachments
/// </summary>
/// <param name="currentSkinOnly">Filters popup results to only include the current Skin. Only valid when a SkeletonRenderer is the data source.</param>
/// <param name="returnAttachmentPath">Returns a fully qualified path for an Attachment in the format "Skin/Slot/AttachmentName"</param>
/// <param name="returnAttachmentPath">Returns a fully qualified path for an Attachment in the format "Skin/Slot/AttachmentName". This path format is only used by the SpineAttachment helper methods like SpineAttachment.GetAttachment and .GetHierarchy. Do not use full path anywhere else in Spine's system.</param>
/// <param name="placeholdersOnly">Filters popup results to exclude attachments that are not children of Skin Placeholders</param>
/// <param name="slotField">If specified, a locally scoped field with the name supplied by in slotField will be used to limit the popup results to children of a named slot</param>
/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
/// </param>
public SpineAttachment(bool currentSkinOnly = true, bool returnAttachmentPath = false, bool placeholdersOnly = false, string slotField = "", string dataField = "") {
public SpineAttachment (bool currentSkinOnly = true, bool returnAttachmentPath = false, bool placeholdersOnly = false, string slotField = "", string dataField = "") {
this.currentSkinOnly = currentSkinOnly;
this.returnAttachmentPath = returnAttachmentPath;
this.placeholdersOnly = placeholdersOnly;
@ -100,11 +100,11 @@ public class SpineAttachment : SpineAttributeBase {
this.dataField = dataField;
}
public static Hierarchy GetHierarchy(string fullPath) {
return new Hierarchy(fullPath);
public static SpineAttachment.Hierarchy GetHierarchy (string fullPath) {
return new SpineAttachment.Hierarchy(fullPath);
}
public static Spine.Attachment GetAttachment(string attachmentPath, Spine.SkeletonData skeletonData) {
public static Spine.Attachment GetAttachment (string attachmentPath, Spine.SkeletonData skeletonData) {
var hierarchy = SpineAttachment.GetHierarchy(attachmentPath);
if (hierarchy.name == "")
return null;
@ -112,16 +112,18 @@ public class SpineAttachment : SpineAttributeBase {
return skeletonData.FindSkin(hierarchy.skin).GetAttachment(skeletonData.FindSlotIndex(hierarchy.slot), hierarchy.name);
}
public static Spine.Attachment GetAttachment(string attachmentPath, SkeletonDataAsset skeletonDataAsset) {
public static Spine.Attachment GetAttachment (string attachmentPath, SkeletonDataAsset skeletonDataAsset) {
return GetAttachment(attachmentPath, skeletonDataAsset.GetSkeletonData(true));
}
/// <summary>
/// A struct that represents 3 strings that help identify and locate an attachment in a skeleton.</summary>
public struct Hierarchy {
public string skin;
public string slot;
public string name;
public Hierarchy(string fullPath) {
public Hierarchy (string fullPath) {
string[] chunks = fullPath.Split(new char[]{'/'}, System.StringSplitOptions.RemoveEmptyEntries);
if (chunks.Length == 0) {
skin = "";
@ -157,15 +159,11 @@ public class SpineBone : SpineAttributeBase {
}
public static Spine.Bone GetBone(string boneName, SkeletonRenderer renderer) {
if (renderer.skeleton == null)
return null;
return renderer.skeleton.FindBone(boneName);
return renderer.skeleton == null ? null : renderer.skeleton.FindBone(boneName);
}
public static Spine.BoneData GetBoneData(string boneName, SkeletonDataAsset skeletonDataAsset) {
var data = skeletonDataAsset.GetSkeletonData(true);
return data.FindBone(boneName);
}
}