2016-10-16 00:19:26 +02:00

295 lines
10 KiB
C#

/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes 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 SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
* SkeletonAnimator created by Mitch Thompson
* Full irrevocable rights and permissions granted to Esoteric Software
*****************************************************************************/
//#define USE_SPINE_EVENTS // Uncomment this define to use C# events to handle Spine events. (Does not disable Unity AnimationClip Events)
using UnityEngine;
using System.Collections.Generic;
namespace Spine.Unity {
[RequireComponent(typeof(Animator))]
public class SkeletonAnimator : SkeletonRenderer, ISkeletonAnimation {
public enum MixMode { AlwaysMix, MixNext, SpineStyle }
public MixMode[] layerMixModes = new MixMode[0];
/// <summary>
/// Occurs after the animations are applied and before world space values are resolved.
/// Use this callback when you want to set bone local values.
/// </summary>
public event UpdateBonesDelegate UpdateLocal {
add { _UpdateLocal += value; }
remove { _UpdateLocal -= value; }
}
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Using this callback will cause the world space values to be solved an extra time.
/// Use this callback if want to use bone world space values, and also set bone local values.
/// </summary>
public event UpdateBonesDelegate UpdateWorld {
add { _UpdateWorld += value; }
remove { _UpdateWorld -= value; }
}
/// <summary>
/// Occurs after the Skeleton's bone world space values are resolved (including all constraints).
/// Use this callback if you want to use bone world space values, but don't intend to modify bone local values.
/// This callback can also be used when setting world position and the bone matrix.
/// </summary>
public event UpdateBonesDelegate UpdateComplete {
add { _UpdateComplete += value; }
remove { _UpdateComplete -= value; }
}
protected event UpdateBonesDelegate _UpdateLocal;
protected event UpdateBonesDelegate _UpdateWorld;
protected event UpdateBonesDelegate _UpdateComplete;
readonly Dictionary<int, Spine.Animation> animationTable = new Dictionary<int, Spine.Animation>();
readonly Dictionary<AnimationClip, int> clipNameHashCodeTable = new Dictionary<AnimationClip, int>();
Animator animator;
float lastTime;
#if USE_SPINE_EVENTS
public delegate void SkeletonAnimatorEventDelegate (Spine.Event firedEvent, float weight);
public event SkeletonAnimatorEventDelegate AnimationEvent;
public readonly ExposedList<Spine.Event> events = new ExposedList<Spine.Event>();
#else
public readonly ExposedList<Spine.Event> events = null;
#endif
public override void Initialize (bool overwrite) {
if (valid && !overwrite)
return;
base.Initialize(overwrite);
if (!valid)
return;
animationTable.Clear();
clipNameHashCodeTable.Clear();
var data = skeletonDataAsset.GetSkeletonData(true);
foreach (var a in data.Animations) {
animationTable.Add(a.Name.GetHashCode(), a);
}
animator = GetComponent<Animator>();
lastTime = Time.time;
}
void Update () {
if (!valid)
return;
if (layerMixModes.Length != animator.layerCount) {
System.Array.Resize<MixMode>(ref layerMixModes, animator.layerCount);
}
float deltaTime = Time.time - lastTime;
skeleton.Update(Time.deltaTime);
//apply
int layerCount = animator.layerCount;
for (int i = 0; i < layerCount; i++) {
float layerWeight = animator.GetLayerWeight(i);
if (i == 0)
layerWeight = 1;
var stateInfo = animator.GetCurrentAnimatorStateInfo(i);
var nextStateInfo = animator.GetNextAnimatorStateInfo(i);
#if UNITY_5
var clipInfo = animator.GetCurrentAnimatorClipInfo(i);
var nextClipInfo = animator.GetNextAnimatorClipInfo(i);
#else
var clipInfo = animator.GetCurrentAnimationClipState(i);
var nextClipInfo = animator.GetNextAnimationClipState(i);
#endif
MixMode mode = layerMixModes[i];
if (mode == MixMode.AlwaysMix) {
//always use Mix instead of Applying the first non-zero weighted clip
for (int c = 0; c < clipInfo.Length; c++) {
var info = clipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = stateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
}
#if UNITY_5
if (nextStateInfo.fullPathHash != 0) {
#else
if (nextStateInfo.nameHash != 0) {
#endif
for (int c = 0; c < nextClipInfo.Length; c++) {
var info = nextClipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = nextStateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
}
}
} else if (mode >= MixMode.MixNext) {
//apply first non-zero weighted clip
int c = 0;
for (; c < clipInfo.Length; c++) {
var info = clipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = stateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
break;
}
//mix the rest
for (; c < clipInfo.Length; c++) {
var info = clipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = stateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, stateInfo.loop, events, weight);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
}
c = 0;
#if UNITY_5
if (nextStateInfo.fullPathHash != 0) {
#else
if (nextStateInfo.nameHash != 0) {
#endif
//apply next clip directly instead of mixing (ie: no crossfade, ignores mecanim transition weights)
if (mode == MixMode.SpineStyle) {
for (; c < nextClipInfo.Length; c++) {
var info = nextClipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = nextStateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Apply(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
break;
}
}
//mix the rest
for (; c < nextClipInfo.Length; c++) {
var info = nextClipInfo[c];
float weight = info.weight * layerWeight;
if (weight == 0)
continue;
float time = nextStateInfo.normalizedTime * info.clip.length;
animationTable[GetAnimationClipNameHashCode(info.clip)].Mix(skeleton, Mathf.Max(0, time - deltaTime), time, nextStateInfo.loop, events, weight);
#if USE_SPINE_EVENTS
FireEvents(events, weight, this.AnimationEvent);
#endif
}
}
}
}
if (_UpdateLocal != null)
_UpdateLocal(this);
skeleton.UpdateWorldTransform();
if (_UpdateWorld != null) {
_UpdateWorld(this);
skeleton.UpdateWorldTransform();
}
if (_UpdateComplete != null) {
_UpdateComplete(this);
}
lastTime = Time.time;
}
private int GetAnimationClipNameHashCode (AnimationClip clip) {
int clipNameHashCode;
if (!clipNameHashCodeTable.TryGetValue(clip, out clipNameHashCode)) {
clipNameHashCode = clip.name.GetHashCode();
clipNameHashCodeTable.Add(clip, clipNameHashCode);
}
return clipNameHashCode;
}
#if USE_SPINE_EVENTS
static void FireEvents (ExposedList<Spine.Event> eventList, float weight, SkeletonAnimatorEventDelegate callback) {
int eventsCount = eventList.Count;
if (eventsCount > 0) {
var eventListItems = eventList.Items;
for (int i = 0; i < eventsCount; i++) {
if (callback != null)
callback(eventListItems[i], weight);
}
eventList.Clear(false);
}
}
#endif
}
}