diff --git a/SpineViewer/Spine/Implementations/Spine/Spine21.cs b/SpineViewer/Spine/Implementations/Spine/Spine21.cs
index 24e00fa..df386f1 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine21.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine21.cs
@@ -109,7 +109,7 @@ namespace SpineViewer.Spine.Implementations.Spine
var pos = position;
var fX = flipX;
var fY = flipY;
- var animation = track0Animation; // TODO: 适配多轨道
+ var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
var sk = skin;
var val = Math.Max(value, SCALE_MIN);
@@ -133,7 +133,7 @@ namespace SpineViewer.Spine.Implementations.Spine
position = pos;
flipX = fX;
flipY = fY;
- track0Animation = animation; // TODO: 适配多轨道
+ for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
skin = sk;
}
}
@@ -171,18 +171,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -233,8 +237,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine36.cs b/SpineViewer/Spine/Implementations/Spine/Spine36.cs
index e21c551..814b04b 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine36.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine36.cs
@@ -108,7 +108,7 @@ namespace SpineViewer.Spine.Implementations.Spine
var pos = position;
var fX = flipX;
var fY = flipY;
- var animation = track0Animation; // TODO: 适配多轨道
+ var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
var sk = skin;
var val = Math.Max(value, SCALE_MIN);
@@ -132,7 +132,7 @@ namespace SpineViewer.Spine.Implementations.Spine
position = pos;
flipX = fX;
flipY = fY;
- track0Animation = animation; // TODO: 适配多轨道
+ for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
skin = sk;
}
}
@@ -170,18 +170,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -192,8 +196,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine37.cs b/SpineViewer/Spine/Implementations/Spine/Spine37.cs
index 38ea611..0342880 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine37.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine37.cs
@@ -140,18 +140,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -162,8 +166,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine38.cs b/SpineViewer/Spine/Implementations/Spine/Spine38.cs
index 565a6c8..6adc06b 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine38.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine38.cs
@@ -146,18 +146,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -168,8 +172,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine40.cs b/SpineViewer/Spine/Implementations/Spine/Spine40.cs
index 434fe15..c6d7ca8 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine40.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine40.cs
@@ -142,18 +142,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -164,8 +168,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine41.cs b/SpineViewer/Spine/Implementations/Spine/Spine41.cs
index c2bfc17..0d21c75 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine41.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine41.cs
@@ -142,18 +142,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -164,8 +168,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Implementations/Spine/Spine42.cs b/SpineViewer/Spine/Implementations/Spine/Spine42.cs
index aa98047..046baa3 100644
--- a/SpineViewer/Spine/Implementations/Spine/Spine42.cs
+++ b/SpineViewer/Spine/Implementations/Spine/Spine42.cs
@@ -142,18 +142,22 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- protected override string track0Animation
+ protected override int[] trackIndices => animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null).ToArray();
+
+ protected override string getAnimation(int track) => animationState.GetCurrent(track)?.Animation.Name ?? EMPTY_ANIMATION;
+
+ protected override void setAnimation(int track, string name)
{
- get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
- set
- {
- if (value == EMPTY_ANIMATION)
- animationState.SetAnimation(0, EmptyAnimation, false);
- else if (animationNames.Contains(value))
- animationState.SetAnimation(0, value, true);
- }
+ if (name == EMPTY_ANIMATION)
+ animationState.SetAnimation(track, EmptyAnimation, false);
+ else if (animationNames.Contains(name))
+ animationState.SetAnimation(track, name, true);
}
+ protected override void clearTrack(int i) => animationState.ClearTrack(i);
+
+ public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
+
protected override RectangleF bounds
{
get
@@ -164,8 +168,6 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
- public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
-
protected override void update(float delta)
{
animationState.Update(delta);
diff --git a/SpineViewer/Spine/Spine.cs b/SpineViewer/Spine/Spine.cs
index 4a30fc5..962b5b3 100644
--- a/SpineViewer/Spine/Spine.cs
+++ b/SpineViewer/Spine/Spine.cs
@@ -15,6 +15,8 @@ using System.Globalization;
using System.Text.Json.Nodes;
using System.Collections.Immutable;
using SpineViewer.Exporter;
+using System.ComponentModel.Design;
+using System.Drawing.Design;
namespace SpineViewer.Spine
{
@@ -77,6 +79,7 @@ namespace SpineViewer.Spine
{
SkinNames = skinNames.AsReadOnly();
AnimationNames = animationNames.AsReadOnly();
+ AnimationTracks = new(this);
// 必须 Update 一次否则包围盒还没有值
update(0);
@@ -105,7 +108,7 @@ namespace SpineViewer.Spine
// 取最后一个作为初始, 尽可能去显示非默认的内容
skin = SkinNames.Last();
- track0Animation = AnimationNames.Last();
+ setAnimation(0, AnimationNames.Last());
return this;
}
@@ -199,7 +202,7 @@ namespace SpineViewer.Spine
///
[TypeConverter(typeof(PointFConverter))]
[Category("[2] 变换"), DisplayName("位置")]
- public PointF Position
+ public PointF Position
{
get { lock (_lock) return position; }
set { lock (_lock) { position = value; update(0); } }
@@ -262,20 +265,46 @@ namespace SpineViewer.Spine
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
///
[TypeConverter(typeof(AnimationConverter))]
- [Category("[3] 动画"), DisplayName("默认轨道动画")]
+ [Category("[3] 动画"), DisplayName("轨道 0 动画")]
public string Track0Animation
{
- get { lock (_lock) return track0Animation; }
- set { lock (_lock) { track0Animation = value; update(0); } }
+ get { lock (_lock) return getAnimation(0); }
+ set { lock (_lock) { setAnimation(0, value); update(0); } }
}
- protected abstract string track0Animation { get; set; }
///
/// 默认轨道动画时长
///
- [Category("[3] 动画"), DisplayName("默认轨道动画时长")]
+ [Category("[3] 动画"), DisplayName("轨道 0 动画时长")]
public float Track0AnimationDuration { get => GetAnimationDuration(Track0Animation); } // TODO: 动画时长变成伪属性在面板显示
+ ///
+ /// 默认轨道动画时长
+ ///
+ [Editor(typeof(CollectionEditor), typeof(UITypeEditor))]
+ [Category("[3] 动画"), DisplayName("多轨道动画")]
+ public AnimationTrackDict AnimationTracks { get; private set; }
+
+ ///
+ /// 获取所有非 null 的轨道索引
+ ///
+ protected abstract int[] trackIndices { get; }
+
+ ///
+ /// 获取指定轨道的当前动画, 如果没有, 应当返回空动画名称
+ ///
+ protected abstract string getAnimation(int track);
+
+ ///
+ /// 设置某个轨道动画
+ ///
+ protected abstract void setAnimation(int track, string name);
+
+ ///
+ /// 清除某个轨道, 与设置空动画不同, 是彻底删除轨道内的东西
+ ///
+ protected abstract void clearTrack(int i);
+
#endregion
#region 属性 | [4] 调试
@@ -284,8 +313,8 @@ namespace SpineViewer.Spine
/// 显示调试
///
[Browsable(false)]
- public bool IsDebug
- {
+ public bool IsDebug
+ {
get { lock (_lock) return isDebug; }
set { lock (_lock) isDebug = value; }
}
@@ -395,5 +424,129 @@ namespace SpineViewer.Spine
protected abstract void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
#endregion
+
+ ///
+ /// 多轨动画管理集合
+ ///
+ ///
+ public class AnimationTrackDict(Spine spine) : IDictionary
+ {
+ private readonly Spine sp = spine;
+
+ public string this[int key]
+ {
+ get
+ {
+ lock (sp._lock)
+ {
+ if (!sp.trackIndices.Contains(key))
+ throw new KeyNotFoundException($"Track {key} not found.");
+ return sp.getAnimation(key);
+ }
+ }
+ set
+ {
+ lock (sp._lock) sp.setAnimation(key, value);
+ }
+ }
+
+ public ICollection Keys
+ {
+ get { lock (sp._lock) return sp.trackIndices; }
+ }
+
+ public ICollection Values
+ {
+ get { lock (sp._lock) return sp.trackIndices.Select(sp.getAnimation).ToArray(); }
+ }
+
+ public int Count
+ {
+ get { lock (sp._lock) return sp.trackIndices.Length; }
+ }
+
+ public bool IsReadOnly => false;
+
+ public void Add(int key, string value)
+ {
+ lock (sp._lock) sp.setAnimation(key, value);
+ }
+
+ public void Add(KeyValuePair item)
+ {
+ lock (sp._lock) sp.setAnimation(item.Key, item.Value);
+ }
+
+ public void Clear()
+ {
+ lock (sp._lock) foreach (var i in sp.trackIndices) sp.setAnimation(i, EMPTY_ANIMATION);
+ }
+
+ public bool Contains(KeyValuePair item)
+ {
+ lock (sp._lock) return sp.trackIndices.Contains(item.Key) && sp.getAnimation(item.Key) == item.Value;
+ }
+
+ public bool ContainsKey(int key)
+ {
+ lock (sp._lock) return sp.trackIndices.Contains(key);
+ }
+
+ public void CopyTo(KeyValuePair[] array, int arrayIndex)
+ {
+ lock (sp._lock) foreach (var i in sp.trackIndices) array[arrayIndex++] = new KeyValuePair(i, sp.getAnimation(i));
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ List> cache;
+ lock (sp._lock)
+ {
+ cache = sp.trackIndices.Select(i => new KeyValuePair(i, sp.getAnimation(i))).ToList();
+ }
+ foreach (var item in cache)
+ {
+ yield return item;
+ }
+ }
+
+ public bool Remove(int key)
+ {
+ lock (sp._lock)
+ {
+ sp.setAnimation(key, EMPTY_ANIMATION);
+ sp.clearTrack(key);
+ }
+ return true;
+ }
+
+ public bool Remove(KeyValuePair item)
+ {
+ lock (sp._lock)
+ {
+ sp.setAnimation(item.Key, EMPTY_ANIMATION);
+ sp.clearTrack(item.Key);
+ }
+ return true;
+ }
+
+ public bool TryGetValue(int key, [MaybeNullWhen(false)] out string value)
+ {
+ value = null;
+ lock (sp._lock)
+ {
+ if (!sp.trackIndices.Contains(key)) return false;
+ value = sp.getAnimation(key);
+ return true;
+ }
+ }
+
+ public override string ToString()
+ {
+ lock (sp._lock) return string.Join(", ", sp.trackIndices.Select(sp.getAnimation));
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
}
}