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(); + } } }