imp v34 & v35
This commit is contained in:
23
Spine/Implementations/SpineWrappers/V34/Animation34.cs
Normal file
23
Spine/Implementations/SpineWrappers/V34/Animation34.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class Animation34(Animation innerObject) : IAnimation
|
||||||
|
{
|
||||||
|
private readonly Animation _o = innerObject;
|
||||||
|
|
||||||
|
public Animation InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public float Duration => _o.Duration;
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
180
Spine/Implementations/SpineWrappers/V34/AnimationState34.cs
Normal file
180
Spine/Implementations/SpineWrappers/V34/AnimationState34.cs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class AnimationState34(AnimationState innerObject, SpineObjectData34 data) : IAnimationState
|
||||||
|
{
|
||||||
|
private readonly AnimationState _o = innerObject;
|
||||||
|
private readonly SpineObjectData34 _data = data;
|
||||||
|
|
||||||
|
private readonly Dictionary<TrackEntry, TrackEntry34> _trackEntryPool = [];
|
||||||
|
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
|
||||||
|
|
||||||
|
public AnimationState InnerObject => _o;
|
||||||
|
|
||||||
|
#pragma warning disable CS0067
|
||||||
|
|
||||||
|
// NOTE: 3.4 以下没有这两个事件
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Interrupt;
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Dispose;
|
||||||
|
|
||||||
|
#pragma warning restore CS0067
|
||||||
|
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Start
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Start += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Start -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? End
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.End += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.End -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Complete
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Complete += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Complete -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
|
||||||
|
|
||||||
|
public void Update(float delta) => _o.Update(delta);
|
||||||
|
|
||||||
|
public void Apply(ISkeleton skeleton)
|
||||||
|
{
|
||||||
|
if (skeleton is Skeleton34 skel)
|
||||||
|
{
|
||||||
|
_o.Apply(skel.InnerObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
|
||||||
|
/// </summary>
|
||||||
|
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
|
||||||
|
{
|
||||||
|
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
|
||||||
|
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
|
||||||
|
|
||||||
|
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
|
||||||
|
|
||||||
|
public void ClearTrack(int index) => _o.ClearTrack(index);
|
||||||
|
|
||||||
|
public void ClearTracks() => _o.ClearTracks();
|
||||||
|
|
||||||
|
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
|
||||||
|
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
|
||||||
|
|
||||||
|
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
|
||||||
|
{
|
||||||
|
if (animation is Animation34 anime)
|
||||||
|
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
|
||||||
|
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
|
||||||
|
|
||||||
|
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
|
||||||
|
|
||||||
|
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
|
||||||
|
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
|
||||||
|
|
||||||
|
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
|
||||||
|
{
|
||||||
|
if (animation is Animation34 anime)
|
||||||
|
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
|
||||||
|
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
|
||||||
|
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34.Attachments
|
||||||
|
{
|
||||||
|
internal abstract class Attachment34(Attachment innerObject) : IAttachment
|
||||||
|
{
|
||||||
|
private readonly Attachment _o = innerObject;
|
||||||
|
|
||||||
|
public virtual Attachment InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class BoundingBoxAttachment34(BoundingBoxAttachment innerObject) :
|
||||||
|
Attachment34(innerObject),
|
||||||
|
IBoundingBoxAttachment
|
||||||
|
{
|
||||||
|
private readonly BoundingBoxAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override BoundingBoxAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot34 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class MeshAttachment34(MeshAttachment innerObject) :
|
||||||
|
Attachment34(innerObject),
|
||||||
|
IMeshAttachment
|
||||||
|
{
|
||||||
|
private readonly MeshAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override MeshAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot34 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
|
||||||
|
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
|
||||||
|
|
||||||
|
public float[] UVs => _o.UVs;
|
||||||
|
|
||||||
|
public int[] Triangles => _o.Triangles;
|
||||||
|
|
||||||
|
public int HullLength => _o.HullLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class PathAttachment34(PathAttachment innerObject) :
|
||||||
|
Attachment34(innerObject),
|
||||||
|
IPathAttachment
|
||||||
|
{
|
||||||
|
private readonly PathAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override PathAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot34 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class RegionAttachment34(RegionAttachment innerObject) :
|
||||||
|
Attachment34(innerObject),
|
||||||
|
IRegionAttachment
|
||||||
|
{
|
||||||
|
private readonly RegionAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override RegionAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot34 st)
|
||||||
|
{
|
||||||
|
if (worldVertices.Length < 8) worldVertices = new float[8];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices);
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
|
||||||
|
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
|
||||||
|
|
||||||
|
public float[] UVs => _o.UVs;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Spine/Implementations/SpineWrappers/V34/Bone34.cs
Normal file
33
Spine/Implementations/SpineWrappers/V34/Bone34.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class Bone34(Bone innerObject, Bone34? parent = null) : IBone
|
||||||
|
{
|
||||||
|
private readonly Bone _o = innerObject;
|
||||||
|
private readonly Bone34? _parent = parent;
|
||||||
|
|
||||||
|
public Bone InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Data.Name;
|
||||||
|
public int Index => _o.Data.Index;
|
||||||
|
|
||||||
|
public IBone? Parent => _parent;
|
||||||
|
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
|
||||||
|
public float Length => _o.Data.Length;
|
||||||
|
public float WorldX => _o.WorldX;
|
||||||
|
public float WorldY => _o.WorldY;
|
||||||
|
public float A => _o.A;
|
||||||
|
public float B => _o.B;
|
||||||
|
public float C => _o.C;
|
||||||
|
public float D => _o.D;
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Spine/Implementations/SpineWrappers/V34/Skeleton34.cs
Normal file
106
Spine/Implementations/SpineWrappers/V34/Skeleton34.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class Skeleton34 : ISkeleton
|
||||||
|
{
|
||||||
|
private readonly Skeleton _o;
|
||||||
|
private readonly SpineObjectData34 _data;
|
||||||
|
|
||||||
|
private readonly ImmutableArray<IBone> _bones;
|
||||||
|
private readonly FrozenDictionary<string, IBone> _bonesByName;
|
||||||
|
private readonly ImmutableArray<ISlot> _slots;
|
||||||
|
private readonly FrozenDictionary<string, ISlot> _slotsByName;
|
||||||
|
|
||||||
|
private Skin34? _skin;
|
||||||
|
|
||||||
|
public Skeleton34(Skeleton innerObject, SpineObjectData34 data)
|
||||||
|
{
|
||||||
|
_o = innerObject;
|
||||||
|
_data = data;
|
||||||
|
|
||||||
|
List<Bone34> bones = [];
|
||||||
|
Dictionary<string, IBone> bonesByName = [];
|
||||||
|
foreach (var b in _o.Bones)
|
||||||
|
{
|
||||||
|
var bone = new Bone34(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
|
||||||
|
bones.Add(bone);
|
||||||
|
bonesByName[bone.Name] = bone;
|
||||||
|
}
|
||||||
|
_bones = bones.Cast<IBone>().ToImmutableArray();
|
||||||
|
_bonesByName = bonesByName.ToFrozenDictionary();
|
||||||
|
|
||||||
|
List<Slot34> slots = [];
|
||||||
|
Dictionary<string, ISlot> slotsByName = [];
|
||||||
|
foreach (var s in _o.Slots)
|
||||||
|
{
|
||||||
|
var slot = new Slot34(s, _data, bones[s.Bone.Data.Index]);
|
||||||
|
slots.Add(slot);
|
||||||
|
slotsByName[slot.Name] = slot;
|
||||||
|
}
|
||||||
|
_slots = slots.Cast<ISlot>().ToImmutableArray();
|
||||||
|
_slotsByName = slotsByName.ToFrozenDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Skeleton InnerObject => _o;
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
public float X { get => _o.X; set => _o.X = value; }
|
||||||
|
public float Y { get => _o.Y; set => _o.Y = value; }
|
||||||
|
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
|
||||||
|
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
|
||||||
|
|
||||||
|
public ImmutableArray<IBone> Bones => _bones;
|
||||||
|
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
|
||||||
|
public ImmutableArray<ISlot> Slots => _slots;
|
||||||
|
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
|
||||||
|
|
||||||
|
public ISkin? Skin
|
||||||
|
{
|
||||||
|
get => _skin;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
_o.Skin = null;
|
||||||
|
_skin = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value is Skin34 sk)
|
||||||
|
{
|
||||||
|
_o.Skin = sk.InnerObject;
|
||||||
|
_skin = sk;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
|
||||||
|
public void UpdateCache() => _o.UpdateCache();
|
||||||
|
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
|
||||||
|
public void SetToSetupPose() => _o.SetToSetupPose();
|
||||||
|
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
|
||||||
|
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
|
||||||
|
public void Update(float delta) => _o.Update(delta);
|
||||||
|
|
||||||
|
public void GetBounds(out float x, out float y, out float w, out float h)
|
||||||
|
{
|
||||||
|
float[] _ = [];
|
||||||
|
_o.GetBounds(out x, out y, out w, out h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using Spine.Utils;
|
||||||
|
using SpineRuntime34;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class SkeletonClipping34 : ISkeletonClipping
|
||||||
|
{
|
||||||
|
public bool IsClipping => false;
|
||||||
|
|
||||||
|
public float[] ClippedVertices { get; private set; } = [];
|
||||||
|
|
||||||
|
public int ClippedVerticesLength { get; private set; } = 0;
|
||||||
|
|
||||||
|
public int[] ClippedTriangles { get; private set; } = [];
|
||||||
|
|
||||||
|
public int ClippedTrianglesLength { get; private set; } = 0;
|
||||||
|
|
||||||
|
public float[] ClippedUVs { get; private set; } = [];
|
||||||
|
|
||||||
|
public void ClipEnd(ISlot slot) { }
|
||||||
|
|
||||||
|
public void ClipEnd() { }
|
||||||
|
|
||||||
|
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment) { }
|
||||||
|
|
||||||
|
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
|
||||||
|
{
|
||||||
|
ClippedVertices = vertices.ToArray();
|
||||||
|
ClippedVerticesLength = verticesLength;
|
||||||
|
ClippedTriangles = triangles.ToArray();
|
||||||
|
ClippedTrianglesLength = trianglesLength;
|
||||||
|
ClippedUVs = uvs.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Spine/Implementations/SpineWrappers/V34/Skin34.cs
Normal file
45
Spine/Implementations/SpineWrappers/V34/Skin34.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class Skin34 : ISkin
|
||||||
|
{
|
||||||
|
private readonly Skin _o;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用指定名字创建空皮肤
|
||||||
|
/// </summary>
|
||||||
|
public Skin34(string name) => _o = new(name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包装已有皮肤对象
|
||||||
|
/// </summary>
|
||||||
|
public Skin34(Skin innerObject) => _o = innerObject;
|
||||||
|
|
||||||
|
public Skin InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public void AddSkin(ISkin skin)
|
||||||
|
{
|
||||||
|
if (skin is Skin34 sk)
|
||||||
|
{
|
||||||
|
// NOTE: 3.7 及以下不支持 AddSkin
|
||||||
|
foreach (var (k, v) in sk._o.Attachments)
|
||||||
|
_o.AddAttachment(k.slotIndex, k.name, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _o.Attachments.Clear();
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Spine/Implementations/SpineWrappers/V34/Slot34.cs
Normal file
78
Spine/Implementations/SpineWrappers/V34/Slot34.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.Utils;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class Slot34 : ISlot
|
||||||
|
{
|
||||||
|
private readonly Slot _o;
|
||||||
|
private readonly SpineObjectData34 _data;
|
||||||
|
|
||||||
|
private readonly Bone34 _bone;
|
||||||
|
private readonly SFML.Graphics.BlendMode _blendMode;
|
||||||
|
|
||||||
|
public Slot34(Slot innerObject, SpineObjectData34 data, Bone34 bone)
|
||||||
|
{
|
||||||
|
_o = innerObject;
|
||||||
|
_data = data;
|
||||||
|
|
||||||
|
_bone = bone;
|
||||||
|
_blendMode = _o.Data.BlendMode switch
|
||||||
|
{
|
||||||
|
BlendMode.normal => SFMLBlendMode.NormalPma,
|
||||||
|
BlendMode.additive => SFMLBlendMode.AdditivePma,
|
||||||
|
BlendMode.multiply => SFMLBlendMode.MultiplyPma,
|
||||||
|
BlendMode.screen => SFMLBlendMode.ScreenPma,
|
||||||
|
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slot InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Data.Name;
|
||||||
|
public int Index => _o.Data.Index;
|
||||||
|
public SFML.Graphics.BlendMode Blend => _blendMode;
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
public IBone Bone => _bone;
|
||||||
|
|
||||||
|
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_o.Attachment is Attachment att)
|
||||||
|
{
|
||||||
|
return _data.SlotAttachments[Name][att.Name];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
_o.Attachment = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value is Attachments.Attachment34 att)
|
||||||
|
{
|
||||||
|
_o.Attachment = att.InnerObject;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Spine/Implementations/SpineWrappers/V34/SpineObjectData34.cs
Normal file
136
Spine/Implementations/SpineWrappers/V34/SpineObjectData34.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.Utils;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime34;
|
||||||
|
using Spine.Implementations.SpineWrappers.V34.Attachments;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
[SpineImplementation(3, 4)]
|
||||||
|
internal sealed class SpineObjectData34 : SpineObjectData
|
||||||
|
{
|
||||||
|
private readonly Atlas _atlas;
|
||||||
|
private readonly SkeletonData _skeletonData;
|
||||||
|
private readonly AnimationStateData _animationStateData;
|
||||||
|
|
||||||
|
private readonly ImmutableArray<ISkin> _skins;
|
||||||
|
private readonly FrozenDictionary<string, ISkin> _skinsByName;
|
||||||
|
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
|
||||||
|
private readonly ImmutableArray<IAnimation> _animations;
|
||||||
|
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
|
||||||
|
|
||||||
|
public SpineObjectData34(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||||
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
|
{
|
||||||
|
// 加载 atlas
|
||||||
|
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||||
|
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Utf8Validator.IsUtf8(skelPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_atlas.Dispose();
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载动画数据
|
||||||
|
_animationStateData = new AnimationStateData(_skeletonData);
|
||||||
|
|
||||||
|
// 整理皮肤和附件
|
||||||
|
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||||
|
List<ISkin> skins = [];
|
||||||
|
Dictionary<string, ISkin> skinsByName = [];
|
||||||
|
foreach (var s in _skeletonData.Skins)
|
||||||
|
{
|
||||||
|
var skin = new Skin34(s);
|
||||||
|
skins.Add(skin);
|
||||||
|
skinsByName[s.Name] = skin;
|
||||||
|
foreach (var (k, att) in s.Attachments)
|
||||||
|
{
|
||||||
|
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
|
||||||
|
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||||
|
slotAttachments[slotName] = attachments = [];
|
||||||
|
|
||||||
|
attachments[att.Name] = att switch
|
||||||
|
{
|
||||||
|
RegionAttachment regionAtt => new RegionAttachment34(regionAtt),
|
||||||
|
MeshAttachment meshAtt => new MeshAttachment34(meshAtt),
|
||||||
|
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment34(bbAtt),
|
||||||
|
PathAttachment pathAtt => new PathAttachment34(pathAtt),
|
||||||
|
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
|
||||||
|
_skins = skins.ToImmutableArray();
|
||||||
|
_skinsByName = skinsByName.ToFrozenDictionary();
|
||||||
|
|
||||||
|
// 整理所有动画数据
|
||||||
|
List<IAnimation> animations = [];
|
||||||
|
Dictionary<string, IAnimation> animationsByName = [];
|
||||||
|
foreach (var a in _skeletonData.Animations)
|
||||||
|
{
|
||||||
|
var anime = new Animation34(a);
|
||||||
|
animations.Add(anime);
|
||||||
|
animationsByName[anime.Name] = anime;
|
||||||
|
}
|
||||||
|
_animations = animations.ToImmutableArray();
|
||||||
|
_animationsByName = animationsByName.ToFrozenDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string SkeletonVersion => _skeletonData.Version;
|
||||||
|
|
||||||
|
public override ImmutableArray<ISkin> Skins => _skins;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
|
||||||
|
|
||||||
|
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
|
||||||
|
|
||||||
|
public override ImmutableArray<IAnimation> Animations => _animations;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
|
||||||
|
|
||||||
|
protected override void DisposeAtlas() => _atlas.Dispose();
|
||||||
|
|
||||||
|
public override ISkeleton CreateSkeleton() => new Skeleton34(new(_skeletonData), this);
|
||||||
|
|
||||||
|
public override IAnimationState CreateAnimationState() => new AnimationState34(new(_animationStateData), this);
|
||||||
|
|
||||||
|
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping34();
|
||||||
|
|
||||||
|
public override ISkin CreateSkin(string name) => new Skin34(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
135
Spine/Implementations/SpineWrappers/V34/TrackEntry34.cs
Normal file
135
Spine/Implementations/SpineWrappers/V34/TrackEntry34.cs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime34;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V34
|
||||||
|
{
|
||||||
|
internal sealed class TrackEntry34(TrackEntry innerObject, AnimationState34 animationState, SpineObjectData34 data): ITrackEntry
|
||||||
|
{
|
||||||
|
private readonly TrackEntry _o = innerObject;
|
||||||
|
private readonly AnimationState34 _animationState = animationState;
|
||||||
|
private readonly SpineObjectData34 _data = data;
|
||||||
|
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
|
||||||
|
|
||||||
|
public TrackEntry InnerObject => _o;
|
||||||
|
|
||||||
|
#pragma warning disable CS0067
|
||||||
|
|
||||||
|
// 3.4 及以下没有这两个事件
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Interrupt;
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Dispose;
|
||||||
|
|
||||||
|
#pragma warning restore CS0067
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Start
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Start += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Start -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? End
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.End += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.End -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Complete
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Complete += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Complete -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TrackIndex { get => _o.TrackIndex; }
|
||||||
|
|
||||||
|
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
|
||||||
|
|
||||||
|
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
|
||||||
|
|
||||||
|
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
|
||||||
|
|
||||||
|
public float TrackTime { get => _o.Time; set => _o.Time = value; }
|
||||||
|
|
||||||
|
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
|
||||||
|
|
||||||
|
public float Alpha { get => _o.Mix; set => _o.Mix = value; }
|
||||||
|
|
||||||
|
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Spine/Implementations/SpineWrappers/V35/Animation35.cs
Normal file
23
Spine/Implementations/SpineWrappers/V35/Animation35.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class Animation35(Animation innerObject) : IAnimation
|
||||||
|
{
|
||||||
|
private readonly Animation _o = innerObject;
|
||||||
|
|
||||||
|
public Animation InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public float Duration => _o.Duration;
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
229
Spine/Implementations/SpineWrappers/V35/AnimationState35.cs
Normal file
229
Spine/Implementations/SpineWrappers/V35/AnimationState35.cs
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class AnimationState35(AnimationState innerObject, SpineObjectData35 data) : IAnimationState
|
||||||
|
{
|
||||||
|
private readonly AnimationState _o = innerObject;
|
||||||
|
private readonly SpineObjectData35 _data = data;
|
||||||
|
|
||||||
|
private readonly Dictionary<TrackEntry, TrackEntry35> _trackEntryPool = [];
|
||||||
|
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
|
||||||
|
|
||||||
|
public AnimationState InnerObject => _o;
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Start
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Start += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Start -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Interrupt
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Interrupt += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Interrupt -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? End
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.End += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.End -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Complete
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Complete += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Complete -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Dispose
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Dispose += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Dispose -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
|
||||||
|
|
||||||
|
public void Update(float delta) => _o.Update(delta);
|
||||||
|
|
||||||
|
public void Apply(ISkeleton skeleton)
|
||||||
|
{
|
||||||
|
if (skeleton is Skeleton35 skel)
|
||||||
|
{
|
||||||
|
_o.Apply(skel.InnerObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
|
||||||
|
/// </summary>
|
||||||
|
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
|
||||||
|
{
|
||||||
|
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
|
||||||
|
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
|
||||||
|
|
||||||
|
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
|
||||||
|
|
||||||
|
public void ClearTrack(int index) => _o.ClearTrack(index);
|
||||||
|
|
||||||
|
public void ClearTracks() => _o.ClearTracks();
|
||||||
|
|
||||||
|
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
|
||||||
|
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
|
||||||
|
|
||||||
|
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
|
||||||
|
{
|
||||||
|
if (animation is Animation35 anime)
|
||||||
|
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
|
||||||
|
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
|
||||||
|
|
||||||
|
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
|
||||||
|
|
||||||
|
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
|
||||||
|
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
|
||||||
|
|
||||||
|
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
|
||||||
|
{
|
||||||
|
if (animation is Animation35 anime)
|
||||||
|
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
|
||||||
|
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
|
||||||
|
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal abstract class Attachment35(Attachment innerObject) : IAttachment
|
||||||
|
{
|
||||||
|
private readonly Attachment _o = innerObject;
|
||||||
|
|
||||||
|
public virtual Attachment InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class BoundingBoxAttachment35(BoundingBoxAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IBoundingBoxAttachment
|
||||||
|
{
|
||||||
|
private readonly BoundingBoxAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override BoundingBoxAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class ClippingAttachment35(ClippingAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IClippingAttachment
|
||||||
|
{
|
||||||
|
private readonly ClippingAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override ClippingAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class MeshAttachment35(MeshAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IMeshAttachment
|
||||||
|
{
|
||||||
|
private readonly MeshAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override MeshAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
|
||||||
|
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
|
||||||
|
|
||||||
|
public float[] UVs => _o.UVs;
|
||||||
|
|
||||||
|
public int[] Triangles => _o.Triangles;
|
||||||
|
|
||||||
|
public int HullLength => _o.HullLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class PathAttachment35(PathAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IPathAttachment
|
||||||
|
{
|
||||||
|
private readonly PathAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override PathAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
var length = _o.WorldVerticesLength;
|
||||||
|
if (worldVertices.Length < length) worldVertices = new float[length];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class PointAttachment35(PointAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IPointAttachment
|
||||||
|
{
|
||||||
|
private readonly PointAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override PointAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
if (worldVertices.Length < 2) worldVertices = new float[2];
|
||||||
|
_o.ComputeWorldPosition(st.InnerObject.Bone, out worldVertices[0], out worldVertices[1]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35.Attachments
|
||||||
|
{
|
||||||
|
internal sealed class RegionAttachment35(RegionAttachment innerObject) :
|
||||||
|
Attachment35(innerObject),
|
||||||
|
IRegionAttachment
|
||||||
|
{
|
||||||
|
private readonly RegionAttachment _o = innerObject;
|
||||||
|
|
||||||
|
public override RegionAttachment InnerObject => _o;
|
||||||
|
|
||||||
|
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
if (worldVertices.Length < 8) worldVertices = new float[8];
|
||||||
|
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices, 0);
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
|
||||||
|
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
|
||||||
|
|
||||||
|
public float[] UVs => _o.UVs;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Spine/Implementations/SpineWrappers/V35/Bone35.cs
Normal file
33
Spine/Implementations/SpineWrappers/V35/Bone35.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class Bone35(Bone innerObject, Bone35? parent = null) : IBone
|
||||||
|
{
|
||||||
|
private readonly Bone _o = innerObject;
|
||||||
|
private readonly Bone35? _parent = parent;
|
||||||
|
|
||||||
|
public Bone InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Data.Name;
|
||||||
|
public int Index => _o.Data.Index;
|
||||||
|
|
||||||
|
public IBone? Parent => _parent;
|
||||||
|
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
|
||||||
|
public float Length => _o.Data.Length;
|
||||||
|
public float WorldX => _o.WorldX;
|
||||||
|
public float WorldY => _o.WorldY;
|
||||||
|
public float A => _o.A;
|
||||||
|
public float B => _o.B;
|
||||||
|
public float C => _o.C;
|
||||||
|
public float D => _o.D;
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Spine/Implementations/SpineWrappers/V35/Skeleton35.cs
Normal file
106
Spine/Implementations/SpineWrappers/V35/Skeleton35.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class Skeleton35 : ISkeleton
|
||||||
|
{
|
||||||
|
private readonly Skeleton _o;
|
||||||
|
private readonly SpineObjectData35 _data;
|
||||||
|
|
||||||
|
private readonly ImmutableArray<IBone> _bones;
|
||||||
|
private readonly FrozenDictionary<string, IBone> _bonesByName;
|
||||||
|
private readonly ImmutableArray<ISlot> _slots;
|
||||||
|
private readonly FrozenDictionary<string, ISlot> _slotsByName;
|
||||||
|
|
||||||
|
private Skin35? _skin;
|
||||||
|
|
||||||
|
public Skeleton35(Skeleton innerObject, SpineObjectData35 data)
|
||||||
|
{
|
||||||
|
_o = innerObject;
|
||||||
|
_data = data;
|
||||||
|
|
||||||
|
List<Bone35> bones = [];
|
||||||
|
Dictionary<string, IBone> bonesByName = [];
|
||||||
|
foreach (var b in _o.Bones)
|
||||||
|
{
|
||||||
|
var bone = new Bone35(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
|
||||||
|
bones.Add(bone);
|
||||||
|
bonesByName[bone.Name] = bone;
|
||||||
|
}
|
||||||
|
_bones = bones.Cast<IBone>().ToImmutableArray();
|
||||||
|
_bonesByName = bonesByName.ToFrozenDictionary();
|
||||||
|
|
||||||
|
List<Slot35> slots = [];
|
||||||
|
Dictionary<string, ISlot> slotsByName = [];
|
||||||
|
foreach (var s in _o.Slots)
|
||||||
|
{
|
||||||
|
var slot = new Slot35(s, _data, bones[s.Bone.Data.Index]);
|
||||||
|
slots.Add(slot);
|
||||||
|
slotsByName[slot.Name] = slot;
|
||||||
|
}
|
||||||
|
_slots = slots.Cast<ISlot>().ToImmutableArray();
|
||||||
|
_slotsByName = slotsByName.ToFrozenDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Skeleton InnerObject => _o;
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
public float X { get => _o.X; set => _o.X = value; }
|
||||||
|
public float Y { get => _o.Y; set => _o.Y = value; }
|
||||||
|
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
|
||||||
|
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
|
||||||
|
|
||||||
|
public ImmutableArray<IBone> Bones => _bones;
|
||||||
|
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
|
||||||
|
public ImmutableArray<ISlot> Slots => _slots;
|
||||||
|
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
|
||||||
|
|
||||||
|
public ISkin? Skin
|
||||||
|
{
|
||||||
|
get => _skin;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
_o.Skin = null;
|
||||||
|
_skin = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value is Skin35 sk)
|
||||||
|
{
|
||||||
|
_o.Skin = sk.InnerObject;
|
||||||
|
_skin = sk;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
|
||||||
|
public void UpdateCache() => _o.UpdateCache();
|
||||||
|
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
|
||||||
|
public void SetToSetupPose() => _o.SetToSetupPose();
|
||||||
|
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
|
||||||
|
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
|
||||||
|
public void Update(float delta) => _o.Update(delta);
|
||||||
|
|
||||||
|
public void GetBounds(out float x, out float y, out float w, out float h)
|
||||||
|
{
|
||||||
|
float[] _ = [];
|
||||||
|
_o.GetBounds(out x, out y, out w, out h, ref _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using Spine.Utils;
|
||||||
|
using SpineRuntime35;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class SkeletonClipping35 : ISkeletonClipping
|
||||||
|
{
|
||||||
|
private readonly SkeletonClipping _o = new();
|
||||||
|
|
||||||
|
public bool IsClipping => _o.IsClipping();
|
||||||
|
|
||||||
|
public float[] ClippedVertices => _o.ClippedVertices.Items;
|
||||||
|
|
||||||
|
public int ClippedVerticesLength => _o.ClippedVertices.Count;
|
||||||
|
|
||||||
|
public int[] ClippedTriangles => _o.ClippedTriangles.Items;
|
||||||
|
|
||||||
|
public int ClippedTrianglesLength => _o.ClippedTriangles.Count;
|
||||||
|
|
||||||
|
public float[] ClippedUVs => _o.ClippedUVs.Items;
|
||||||
|
|
||||||
|
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
|
||||||
|
=> _o.ClipTriangles(vertices, verticesLength, triangles, trianglesLength, uvs);
|
||||||
|
|
||||||
|
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st && clippingAttachment is Attachments.ClippingAttachment35 att)
|
||||||
|
{
|
||||||
|
_o.ClipStart(st.InnerObject, att.InnerObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {slot.GetType().Name} {clippingAttachment.GetType().Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClipEnd(ISlot slot)
|
||||||
|
{
|
||||||
|
if (slot is Slot35 st)
|
||||||
|
{
|
||||||
|
_o.ClipEnd(st.InnerObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClipEnd() => _o.ClipEnd();
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Spine/Implementations/SpineWrappers/V35/Skin35.cs
Normal file
45
Spine/Implementations/SpineWrappers/V35/Skin35.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class Skin35 : ISkin
|
||||||
|
{
|
||||||
|
private readonly Skin _o;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用指定名字创建空皮肤
|
||||||
|
/// </summary>
|
||||||
|
public Skin35(string name) => _o = new(name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包装已有皮肤对象
|
||||||
|
/// </summary>
|
||||||
|
public Skin35(Skin innerObject) => _o = innerObject;
|
||||||
|
|
||||||
|
public Skin InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Name;
|
||||||
|
|
||||||
|
public void AddSkin(ISkin skin)
|
||||||
|
{
|
||||||
|
if (skin is Skin35 sk)
|
||||||
|
{
|
||||||
|
// NOTE: 3.7 及以下不支持 AddSkin
|
||||||
|
foreach (var (k, v) in sk._o.Attachments)
|
||||||
|
_o.AddAttachment(k.slotIndex, k.name, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => _o.Attachments.Clear();
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
78
Spine/Implementations/SpineWrappers/V35/Slot35.cs
Normal file
78
Spine/Implementations/SpineWrappers/V35/Slot35.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.Utils;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class Slot35 : ISlot
|
||||||
|
{
|
||||||
|
private readonly Slot _o;
|
||||||
|
private readonly SpineObjectData35 _data;
|
||||||
|
|
||||||
|
private readonly Bone35 _bone;
|
||||||
|
private readonly SFML.Graphics.BlendMode _blendMode;
|
||||||
|
|
||||||
|
public Slot35(Slot innerObject, SpineObjectData35 data, Bone35 bone)
|
||||||
|
{
|
||||||
|
_o = innerObject;
|
||||||
|
_data = data;
|
||||||
|
|
||||||
|
_bone = bone;
|
||||||
|
_blendMode = _o.Data.BlendMode switch
|
||||||
|
{
|
||||||
|
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||||
|
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||||
|
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||||
|
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||||
|
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slot InnerObject => _o;
|
||||||
|
|
||||||
|
public string Name => _o.Data.Name;
|
||||||
|
public int Index => _o.Data.Index;
|
||||||
|
public SFML.Graphics.BlendMode Blend => _blendMode;
|
||||||
|
|
||||||
|
public float R { get => _o.R; set => _o.R = value; }
|
||||||
|
public float G { get => _o.G; set => _o.G = value; }
|
||||||
|
public float B { get => _o.B; set => _o.B = value; }
|
||||||
|
public float A { get => _o.A; set => _o.A = value; }
|
||||||
|
public IBone Bone => _bone;
|
||||||
|
|
||||||
|
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_o.Attachment is Attachment att)
|
||||||
|
{
|
||||||
|
return _data.SlotAttachments[Name][att.Name];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
_o.Attachment = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value is Attachments.Attachment35 att)
|
||||||
|
{
|
||||||
|
_o.Attachment = att.InnerObject;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Spine/Implementations/SpineWrappers/V35/SpineObjectData35.cs
Normal file
138
Spine/Implementations/SpineWrappers/V35/SpineObjectData35.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Frozen;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Spine.Utils;
|
||||||
|
using Spine.SpineWrappers;
|
||||||
|
using Spine.SpineWrappers.Attachments;
|
||||||
|
using SpineRuntime35;
|
||||||
|
using Spine.Implementations.SpineWrappers.V35.Attachments;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
[SpineImplementation(3, 5)]
|
||||||
|
internal sealed class SpineObjectData35 : SpineObjectData
|
||||||
|
{
|
||||||
|
private readonly Atlas _atlas;
|
||||||
|
private readonly SkeletonData _skeletonData;
|
||||||
|
private readonly AnimationStateData _animationStateData;
|
||||||
|
|
||||||
|
private readonly ImmutableArray<ISkin> _skins;
|
||||||
|
private readonly FrozenDictionary<string, ISkin> _skinsByName;
|
||||||
|
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
|
||||||
|
private readonly ImmutableArray<IAnimation> _animations;
|
||||||
|
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
|
||||||
|
|
||||||
|
public SpineObjectData35(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
|
||||||
|
: base(skelPath, atlasPath, textureLoader)
|
||||||
|
{
|
||||||
|
// 加载 atlas
|
||||||
|
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||||
|
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Utf8Validator.IsUtf8(skelPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_atlas.Dispose();
|
||||||
|
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载动画数据
|
||||||
|
_animationStateData = new AnimationStateData(_skeletonData);
|
||||||
|
|
||||||
|
// 整理皮肤和附件
|
||||||
|
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
|
||||||
|
List<ISkin> skins = [];
|
||||||
|
Dictionary<string, ISkin> skinsByName = [];
|
||||||
|
foreach (var s in _skeletonData.Skins)
|
||||||
|
{
|
||||||
|
var skin = new Skin35(s);
|
||||||
|
skins.Add(skin);
|
||||||
|
skinsByName[s.Name] = skin;
|
||||||
|
foreach (var (k, att) in s.Attachments)
|
||||||
|
{
|
||||||
|
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
|
||||||
|
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||||
|
slotAttachments[slotName] = attachments = [];
|
||||||
|
|
||||||
|
attachments[att.Name] = att switch
|
||||||
|
{
|
||||||
|
RegionAttachment regionAtt => new RegionAttachment35(regionAtt),
|
||||||
|
MeshAttachment meshAtt => new MeshAttachment35(meshAtt),
|
||||||
|
ClippingAttachment clipAtt => new ClippingAttachment35(clipAtt),
|
||||||
|
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment35(bbAtt),
|
||||||
|
PathAttachment pathAtt => new PathAttachment35(pathAtt),
|
||||||
|
PointAttachment ptAtt => new PointAttachment35(ptAtt),
|
||||||
|
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
|
||||||
|
_skins = skins.ToImmutableArray();
|
||||||
|
_skinsByName = skinsByName.ToFrozenDictionary();
|
||||||
|
|
||||||
|
// 整理所有动画数据
|
||||||
|
List<IAnimation> animations = [];
|
||||||
|
Dictionary<string, IAnimation> animationsByName = [];
|
||||||
|
foreach (var a in _skeletonData.Animations)
|
||||||
|
{
|
||||||
|
var anime = new Animation35(a);
|
||||||
|
animations.Add(anime);
|
||||||
|
animationsByName[anime.Name] = anime;
|
||||||
|
}
|
||||||
|
_animations = animations.ToImmutableArray();
|
||||||
|
_animationsByName = animationsByName.ToFrozenDictionary();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string SkeletonVersion => _skeletonData.Version;
|
||||||
|
|
||||||
|
public override ImmutableArray<ISkin> Skins => _skins;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
|
||||||
|
|
||||||
|
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
|
||||||
|
|
||||||
|
public override ImmutableArray<IAnimation> Animations => _animations;
|
||||||
|
|
||||||
|
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
|
||||||
|
|
||||||
|
protected override void DisposeAtlas() => _atlas.Dispose();
|
||||||
|
|
||||||
|
public override ISkeleton CreateSkeleton() => new Skeleton35(new(_skeletonData), this);
|
||||||
|
|
||||||
|
public override IAnimationState CreateAnimationState() => new AnimationState35(new(_animationStateData), this);
|
||||||
|
|
||||||
|
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping35();
|
||||||
|
|
||||||
|
public override ISkin CreateSkin(string name) => new Skin35(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
185
Spine/Implementations/SpineWrappers/V35/TrackEntry35.cs
Normal file
185
Spine/Implementations/SpineWrappers/V35/TrackEntry35.cs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
using Spine.SpineWrappers;
|
||||||
|
using SpineRuntime35;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Spine.Implementations.SpineWrappers.V35
|
||||||
|
{
|
||||||
|
internal sealed class TrackEntry35(TrackEntry innerObject, AnimationState35 animationState, SpineObjectData35 data): ITrackEntry
|
||||||
|
{
|
||||||
|
private readonly TrackEntry _o = innerObject;
|
||||||
|
private readonly AnimationState35 _animationState = animationState;
|
||||||
|
private readonly SpineObjectData35 _data = data;
|
||||||
|
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
|
||||||
|
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
|
||||||
|
|
||||||
|
public TrackEntry InnerObject => _o;
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Start
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Start += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Start -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Interrupt
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Interrupt += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Interrupt -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? End
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.End += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.End -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Complete
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Complete += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Complete -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event IAnimationState.TrackEntryDelegate? Dispose
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (!_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
|
||||||
|
_eventCount[value] = 0;
|
||||||
|
}
|
||||||
|
_o.Dispose += f;
|
||||||
|
_eventCount[value]++;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
if (value is null) return;
|
||||||
|
if (_eventMapping.TryGetValue(value, out var f))
|
||||||
|
{
|
||||||
|
_o.Dispose -= f;
|
||||||
|
_eventCount[value]--;
|
||||||
|
if (_eventCount[value] <= 0)
|
||||||
|
{
|
||||||
|
_eventMapping.Remove(value);
|
||||||
|
_eventCount.Remove(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TrackIndex { get => _o.TrackIndex; }
|
||||||
|
|
||||||
|
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
|
||||||
|
|
||||||
|
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
|
||||||
|
|
||||||
|
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
|
||||||
|
|
||||||
|
public float TrackTime { get => _o.TrackTime; set => _o.TrackTime = value; }
|
||||||
|
|
||||||
|
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
|
||||||
|
|
||||||
|
public float Alpha { get => _o.Alpha; set => _o.Alpha = value; }
|
||||||
|
|
||||||
|
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
|
||||||
|
|
||||||
|
public override string ToString() => _o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime21\SpineRuntime21.csproj" />
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime21\SpineRuntime21.csproj" />
|
||||||
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj" />
|
||||||
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime35\SpineRuntime35.csproj" />
|
||||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj" />
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime36\SpineRuntime36.csproj" />
|
||||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime37\SpineRuntime37.csproj" />
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime37\SpineRuntime37.csproj" />
|
||||||
<ProjectReference Include="..\SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj" />
|
<ProjectReference Include="..\SpineRuntimes\SpineRuntime38\SpineRuntime38.csproj" />
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace Spine
|
|||||||
public sealed class SpineVersion : IEquatable<SpineVersion>, IComparable<SpineVersion>
|
public sealed class SpineVersion : IEquatable<SpineVersion>, IComparable<SpineVersion>
|
||||||
{
|
{
|
||||||
public static readonly SpineVersion V21 = new(typeof(SpineRuntime21.Skeleton));
|
public static readonly SpineVersion V21 = new(typeof(SpineRuntime21.Skeleton));
|
||||||
|
public static readonly SpineVersion V34 = new(typeof(SpineRuntime34.Skeleton));
|
||||||
|
public static readonly SpineVersion V35 = new(typeof(SpineRuntime35.Skeleton));
|
||||||
public static readonly SpineVersion V36 = new(typeof(SpineRuntime36.Skeleton));
|
public static readonly SpineVersion V36 = new(typeof(SpineRuntime36.Skeleton));
|
||||||
public static readonly SpineVersion V37 = new(typeof(SpineRuntime37.Skeleton));
|
public static readonly SpineVersion V37 = new(typeof(SpineRuntime37.Skeleton));
|
||||||
public static readonly SpineVersion V38 = new(typeof(SpineRuntime38.Skeleton));
|
public static readonly SpineVersion V38 = new(typeof(SpineRuntime38.Skeleton));
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ namespace Spine.SpineWrappers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TextureLoader :
|
public class TextureLoader :
|
||||||
SpineRuntime21.TextureLoader,
|
SpineRuntime21.TextureLoader,
|
||||||
|
SpineRuntime34.TextureLoader,
|
||||||
|
SpineRuntime35.TextureLoader,
|
||||||
SpineRuntime36.TextureLoader,
|
SpineRuntime36.TextureLoader,
|
||||||
SpineRuntime37.TextureLoader,
|
SpineRuntime37.TextureLoader,
|
||||||
SpineRuntime38.TextureLoader,
|
SpineRuntime38.TextureLoader,
|
||||||
@@ -106,6 +108,76 @@ namespace Spine.SpineWrappers
|
|||||||
page.rendererObject = texture;
|
page.rendererObject = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void Load(SpineRuntime34.AtlasPage page, string path)
|
||||||
|
{
|
||||||
|
var texture = ReadTexture(path);
|
||||||
|
|
||||||
|
if (page.magFilter == SpineRuntime34.TextureFilter.Linear)
|
||||||
|
{
|
||||||
|
texture.Smooth = true;
|
||||||
|
}
|
||||||
|
if (page.uWrap == SpineRuntime34.TextureWrap.Repeat && page.vWrap == SpineRuntime34.TextureWrap.Repeat)
|
||||||
|
{
|
||||||
|
texture.Repeated = true;
|
||||||
|
}
|
||||||
|
switch (page.minFilter)
|
||||||
|
{
|
||||||
|
case SpineRuntime34.TextureFilter.Linear:
|
||||||
|
texture.Smooth = true;
|
||||||
|
break;
|
||||||
|
case SpineRuntime34.TextureFilter.MipMap:
|
||||||
|
case SpineRuntime34.TextureFilter.MipMapNearestNearest:
|
||||||
|
texture.GenerateMipmap();
|
||||||
|
break;
|
||||||
|
case SpineRuntime34.TextureFilter.MipMapLinearNearest:
|
||||||
|
case SpineRuntime34.TextureFilter.MipMapNearestLinear:
|
||||||
|
case SpineRuntime34.TextureFilter.MipMapLinearLinear:
|
||||||
|
texture.Smooth = true;
|
||||||
|
texture.GenerateMipmap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ForceNearest) texture.Smooth = false;
|
||||||
|
if (ForceMipmap) texture.GenerateMipmap();
|
||||||
|
|
||||||
|
page.rendererObject = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Load(SpineRuntime35.AtlasPage page, string path)
|
||||||
|
{
|
||||||
|
var texture = ReadTexture(path);
|
||||||
|
|
||||||
|
if (page.magFilter == SpineRuntime35.TextureFilter.Linear)
|
||||||
|
{
|
||||||
|
texture.Smooth = true;
|
||||||
|
}
|
||||||
|
if (page.uWrap == SpineRuntime35.TextureWrap.Repeat && page.vWrap == SpineRuntime35.TextureWrap.Repeat)
|
||||||
|
{
|
||||||
|
texture.Repeated = true;
|
||||||
|
}
|
||||||
|
switch (page.minFilter)
|
||||||
|
{
|
||||||
|
case SpineRuntime35.TextureFilter.Linear:
|
||||||
|
texture.Smooth = true;
|
||||||
|
break;
|
||||||
|
case SpineRuntime35.TextureFilter.MipMap:
|
||||||
|
case SpineRuntime35.TextureFilter.MipMapNearestNearest:
|
||||||
|
texture.GenerateMipmap();
|
||||||
|
break;
|
||||||
|
case SpineRuntime35.TextureFilter.MipMapLinearNearest:
|
||||||
|
case SpineRuntime35.TextureFilter.MipMapNearestLinear:
|
||||||
|
case SpineRuntime35.TextureFilter.MipMapLinearLinear:
|
||||||
|
texture.Smooth = true;
|
||||||
|
texture.GenerateMipmap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ForceNearest) texture.Smooth = false;
|
||||||
|
if (ForceMipmap) texture.GenerateMipmap();
|
||||||
|
|
||||||
|
page.rendererObject = texture;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Load(SpineRuntime36.AtlasPage page, string path)
|
public virtual void Load(SpineRuntime36.AtlasPage page, string path)
|
||||||
{
|
{
|
||||||
var texture = ReadTexture(path);
|
var texture = ReadTexture(path);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace SpineRuntime21 {
|
|||||||
static readonly Animation EmptyAnimation = new Animation("<empty>", new List<Timeline>(), 0);
|
static readonly Animation EmptyAnimation = new Animation("<empty>", new List<Timeline>(), 0);
|
||||||
private AnimationStateData data;
|
private AnimationStateData data;
|
||||||
|
|
||||||
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
||||||
private List<TrackEntry> tracks = new List<TrackEntry>();
|
private List<TrackEntry> tracks = new List<TrackEntry>();
|
||||||
private List<Event> events = new List<Event>();
|
private List<Event> events = new List<Event>();
|
||||||
private float timeScale = 1;
|
private float timeScale = 1;
|
||||||
|
|||||||
@@ -34,26 +34,25 @@ using System.Text;
|
|||||||
|
|
||||||
namespace SpineRuntime34 {
|
namespace SpineRuntime34 {
|
||||||
public class AnimationState {
|
public class AnimationState {
|
||||||
|
static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
|
||||||
private AnimationStateData data;
|
private AnimationStateData data;
|
||||||
private ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
private ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
||||||
private ExposedList<Event> events = new ExposedList<Event>();
|
private ExposedList<Event> events = new ExposedList<Event>();
|
||||||
private float timeScale = 1;
|
private float timeScale = 1;
|
||||||
|
|
||||||
public AnimationStateData Data { get { return data; } }
|
public AnimationStateData Data { get { return data; } }
|
||||||
|
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
||||||
|
|
||||||
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
|
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
|
||||||
public ExposedList<TrackEntry> Tracks { get { return tracks; } }
|
public ExposedList<TrackEntry> Tracks { get { return tracks; } }
|
||||||
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
|
public float TimeScale { get { return timeScale; } set { timeScale = value; } }
|
||||||
|
|
||||||
public delegate void StartEndDelegate (AnimationState state, int trackIndex);
|
public delegate void TrackEntryDelegate(TrackEntry trackEntry);
|
||||||
public event StartEndDelegate Start;
|
public event TrackEntryDelegate Start, End, Complete;
|
||||||
public event StartEndDelegate End;
|
|
||||||
|
|
||||||
public delegate void EventDelegate (AnimationState state, int trackIndex, Event e);
|
public delegate void EventDelegate (AnimationState state, int trackIndex, Event e);
|
||||||
public event EventDelegate Event;
|
public event EventDelegate Event;
|
||||||
|
|
||||||
public delegate void CompleteDelegate (AnimationState state, int trackIndex, int loopCount);
|
|
||||||
public event CompleteDelegate Complete;
|
|
||||||
|
|
||||||
public AnimationState (AnimationStateData data) {
|
public AnimationState (AnimationStateData data) {
|
||||||
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
|
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@@ -78,8 +77,8 @@ namespace SpineRuntime34 {
|
|||||||
// Check if completed the animation or a loop iteration.
|
// Check if completed the animation or a loop iteration.
|
||||||
if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) {
|
if (current.loop ? (current.lastTime % endTime > time % endTime) : (current.lastTime < endTime && time >= endTime)) {
|
||||||
int count = (int)(time / endTime);
|
int count = (int)(time / endTime);
|
||||||
current.OnComplete(this, i, count);
|
current.OnComplete();
|
||||||
if (Complete != null) Complete(this, i, count);
|
if (Complete != null) Complete(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackEntry next = current.next;
|
TrackEntry next = current.next;
|
||||||
@@ -149,10 +148,17 @@ namespace SpineRuntime34 {
|
|||||||
TrackEntry current = tracks.Items[trackIndex];
|
TrackEntry current = tracks.Items[trackIndex];
|
||||||
if (current == null) return;
|
if (current == null) return;
|
||||||
|
|
||||||
current.OnEnd(this, trackIndex);
|
current.OnEnd();
|
||||||
if (End != null) End(this, trackIndex);
|
if (End != null) End(current);
|
||||||
|
|
||||||
tracks.Items[trackIndex] = null;
|
tracks.Items[trackIndex] = null;
|
||||||
|
|
||||||
|
while (current is not null)
|
||||||
|
{
|
||||||
|
var tmp = current.next;
|
||||||
|
trackEntryPool.Free(current);
|
||||||
|
current = tmp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackEntry ExpandToIndex (int index) {
|
private TrackEntry ExpandToIndex (int index) {
|
||||||
@@ -168,8 +174,8 @@ namespace SpineRuntime34 {
|
|||||||
TrackEntry previous = current.previous;
|
TrackEntry previous = current.previous;
|
||||||
current.previous = null;
|
current.previous = null;
|
||||||
|
|
||||||
current.OnEnd(this, index);
|
current.OnEnd();
|
||||||
if (End != null) End(this, index);
|
if (End != null) End(current);
|
||||||
|
|
||||||
entry.mixDuration = data.GetMix(current.animation, entry.animation);
|
entry.mixDuration = data.GetMix(current.animation, entry.animation);
|
||||||
if (entry.mixDuration > 0) {
|
if (entry.mixDuration > 0) {
|
||||||
@@ -184,8 +190,15 @@ namespace SpineRuntime34 {
|
|||||||
|
|
||||||
tracks.Items[index] = entry;
|
tracks.Items[index] = entry;
|
||||||
|
|
||||||
entry.OnStart(this, index);
|
while (current is not null)
|
||||||
if (Start != null) Start(this, index);
|
{
|
||||||
|
var tmp = current.next;
|
||||||
|
trackEntryPool.Free(current);
|
||||||
|
current = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.OnStart();
|
||||||
|
if (Start != null) Start(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <seealso cref="SetAnimation(int, Animation, bool)" />
|
/// <seealso cref="SetAnimation(int, Animation, bool)" />
|
||||||
@@ -198,7 +211,8 @@ namespace SpineRuntime34 {
|
|||||||
/// <summary>Set the current animation. Any queued animations are cleared.</summary>
|
/// <summary>Set the current animation. Any queued animations are cleared.</summary>
|
||||||
public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
|
public TrackEntry SetAnimation (int trackIndex, Animation animation, bool loop) {
|
||||||
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
|
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
|
||||||
TrackEntry entry = new TrackEntry();
|
TrackEntry entry = trackEntryPool.Obtain();
|
||||||
|
entry.trackIndex = trackIndex;
|
||||||
entry.animation = animation;
|
entry.animation = animation;
|
||||||
entry.loop = loop;
|
entry.loop = loop;
|
||||||
entry.time = 0;
|
entry.time = 0;
|
||||||
@@ -218,7 +232,9 @@ namespace SpineRuntime34 {
|
|||||||
/// <param name="delay">May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
|
/// <param name="delay">May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param>
|
||||||
public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
|
public TrackEntry AddAnimation (int trackIndex, Animation animation, bool loop, float delay) {
|
||||||
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
|
if (animation == null) throw new ArgumentNullException("animation", "animation cannot be null.");
|
||||||
TrackEntry entry = new TrackEntry();
|
TrackEntry entry = trackEntryPool.Obtain();
|
||||||
|
entry.trackIndex = trackIndex;
|
||||||
|
entry.animation = animation;
|
||||||
entry.animation = animation;
|
entry.animation = animation;
|
||||||
entry.loop = loop;
|
entry.loop = loop;
|
||||||
entry.time = 0;
|
entry.time = 0;
|
||||||
@@ -243,6 +259,46 @@ namespace SpineRuntime34 {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
|
||||||
|
public TrackEntry SetEmptyAnimation(int trackIndex, float mixDuration)
|
||||||
|
{
|
||||||
|
TrackEntry entry = SetAnimation(trackIndex, AnimationState.EmptyAnimation, false);
|
||||||
|
entry.mixDuration = mixDuration;
|
||||||
|
entry.endTime = mixDuration;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
|
||||||
|
/// specified mix duration.</summary>
|
||||||
|
/// <returns>
|
||||||
|
/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after <see cref="AnimationState.Dispose"/>.
|
||||||
|
/// </returns>
|
||||||
|
/// <param name="trackIndex">Track number.</param>
|
||||||
|
/// <param name="mixDuration">Mix duration.</param>
|
||||||
|
/// <param name="delay">Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
|
||||||
|
/// duration of the previous track minus any mix duration plus the negative delay.</param>
|
||||||
|
public TrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
|
||||||
|
{
|
||||||
|
if (delay <= 0) delay -= mixDuration;
|
||||||
|
TrackEntry entry = AddAnimation(trackIndex, AnimationState.EmptyAnimation, false, delay);
|
||||||
|
entry.mixDuration = mixDuration;
|
||||||
|
entry.endTime = mixDuration;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.</summary>
|
||||||
|
public void SetEmptyAnimations(float mixDuration)
|
||||||
|
{
|
||||||
|
for (int i = 0, n = tracks.Count; i < n; i++)
|
||||||
|
{
|
||||||
|
TrackEntry current = tracks.Items[i];
|
||||||
|
if (current != null) SetEmptyAnimation(i, mixDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <returns>May be null.</returns>
|
/// <returns>May be null.</returns>
|
||||||
public TrackEntry GetCurrent (int trackIndex) {
|
public TrackEntry GetCurrent (int trackIndex) {
|
||||||
if (trackIndex >= tracks.Count) return null;
|
if (trackIndex >= tracks.Count) return null;
|
||||||
@@ -262,13 +318,15 @@ namespace SpineRuntime34 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TrackEntry {
|
public class TrackEntry : Pool<TrackEntry>.IPoolable {
|
||||||
internal TrackEntry next, previous;
|
internal TrackEntry next, previous;
|
||||||
|
internal int trackIndex;
|
||||||
internal Animation animation;
|
internal Animation animation;
|
||||||
internal bool loop;
|
internal bool loop;
|
||||||
internal float delay, time, lastTime = -1, endTime, timeScale = 1;
|
internal float delay, time, lastTime = -1, endTime, timeScale = 1;
|
||||||
internal float mixTime, mixDuration, mix = 1;
|
internal float mixTime, mixDuration, mix = 1;
|
||||||
|
|
||||||
|
public int TrackIndex { get { return trackIndex; } }
|
||||||
public Animation Animation { get { return animation; } }
|
public Animation Animation { get { return animation; } }
|
||||||
public float Delay { get { return delay; } set { delay = value; } }
|
public float Delay { get { return delay; } set { delay = value; } }
|
||||||
public float Time { get { return time; } set { time = value; } }
|
public float Time { get { return time; } set { time = value; } }
|
||||||
@@ -278,29 +336,111 @@ namespace SpineRuntime34 {
|
|||||||
public float Mix { get { return mix; } set { mix = value; } }
|
public float Mix { get { return mix; } set { mix = value; } }
|
||||||
public bool Loop { get { return loop; } set { loop = value; } }
|
public bool Loop { get { return loop; } set { loop = value; } }
|
||||||
|
|
||||||
public event AnimationState.StartEndDelegate Start;
|
/// <summary>
|
||||||
public event AnimationState.StartEndDelegate End;
|
/// Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
|
||||||
|
/// <see cref="AnimationStateData"/> based on the animation before this animation (if any).
|
||||||
|
///
|
||||||
|
/// The mix duration can be set manually rather than use the value from AnimationStateData.GetMix.
|
||||||
|
/// In that case, the mixDuration must be set before <see cref="AnimationState.Update(float)"/> is next called.
|
||||||
|
/// <para>
|
||||||
|
/// When using <seealso cref="AnimationState.AddAnimation(int, Animation, bool, float)"/> with a
|
||||||
|
/// <code>delay</code> less than or equal to 0, note the <seealso cref="Delay"/> is set using the mix duration from the <see cref=" AnimationStateData"/>
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public float MixDuration { get { return mixDuration; } set { mixDuration = value; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation queued to start after this animation, or null.</summary>
|
||||||
|
public TrackEntry Next { get { return next; } }
|
||||||
|
|
||||||
|
public event AnimationState.TrackEntryDelegate Start, End, Complete;
|
||||||
public event AnimationState.EventDelegate Event;
|
public event AnimationState.EventDelegate Event;
|
||||||
public event AnimationState.CompleteDelegate Complete;
|
|
||||||
|
|
||||||
internal void OnStart (AnimationState state, int index) {
|
// IPoolable.Reset()
|
||||||
if (Start != null) Start(state, index);
|
public void Reset()
|
||||||
|
{
|
||||||
|
next = null;
|
||||||
|
previous = null;
|
||||||
|
animation = null;
|
||||||
|
|
||||||
|
Start = null;
|
||||||
|
End = null;
|
||||||
|
Complete = null;
|
||||||
|
Event = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnEnd (AnimationState state, int index) {
|
internal void OnStart() { if (Start != null) Start(this); }
|
||||||
if (End != null) End(state, index);
|
internal void OnEnd() { if (End != null) End(this); }
|
||||||
}
|
internal void OnComplete() { if (Complete != null) Complete(this); }
|
||||||
|
|
||||||
internal void OnEvent (AnimationState state, int index, Event e) {
|
internal void OnEvent(AnimationState state, int index, Event e)
|
||||||
|
{
|
||||||
if (Event != null) Event(state, index, e);
|
if (Event != null) Event(state, index, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnComplete (AnimationState state, int index, int loopCount) {
|
|
||||||
if (Complete != null) Complete(state, index, loopCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
override public String ToString () {
|
override public String ToString () {
|
||||||
return animation == null ? "<none>" : animation.name;
|
return animation == null ? "<none>" : animation.name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Pool<T> where T : class, new()
|
||||||
|
{
|
||||||
|
public readonly int max;
|
||||||
|
readonly Stack<T> freeObjects;
|
||||||
|
|
||||||
|
public int Count { get { return freeObjects.Count; } }
|
||||||
|
public int Peak { get; private set; }
|
||||||
|
|
||||||
|
public Pool(int initialCapacity = 16, int max = int.MaxValue)
|
||||||
|
{
|
||||||
|
freeObjects = new Stack<T>(initialCapacity);
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Obtain()
|
||||||
|
{
|
||||||
|
return freeObjects.Count == 0 ? new T() : freeObjects.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(T obj)
|
||||||
|
{
|
||||||
|
if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null");
|
||||||
|
if (freeObjects.Count < max)
|
||||||
|
{
|
||||||
|
freeObjects.Push(obj);
|
||||||
|
Peak = Math.Max(Peak, freeObjects.Count);
|
||||||
|
}
|
||||||
|
Reset(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected void FreeAll (List<T> objects) {
|
||||||
|
// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null.");
|
||||||
|
// var freeObjects = this.freeObjects;
|
||||||
|
// int max = this.max;
|
||||||
|
// for (int i = 0; i < objects.Count; i++) {
|
||||||
|
// T obj = objects[i];
|
||||||
|
// if (obj == null) continue;
|
||||||
|
// if (freeObjects.Count < max) freeObjects.Push(obj);
|
||||||
|
// Reset(obj);
|
||||||
|
// }
|
||||||
|
// Peak = Math.Max(Peak, freeObjects.Count);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
freeObjects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Reset(T obj)
|
||||||
|
{
|
||||||
|
var poolable = obj as IPoolable;
|
||||||
|
if (poolable != null) poolable.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPoolable
|
||||||
|
{
|
||||||
|
void Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,22 +105,13 @@ namespace SpineRuntime34 {
|
|||||||
Bone parent = this.parent;
|
Bone parent = this.parent;
|
||||||
if (parent == null) { // Root bone.
|
if (parent == null) { // Root bone.
|
||||||
Skeleton skeleton = this.skeleton;
|
Skeleton skeleton = this.skeleton;
|
||||||
if (skeleton.flipX) {
|
float sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||||
x = -x;
|
a = la * sx;
|
||||||
la = -la;
|
b = lb * sx;
|
||||||
lb = -lb;
|
c = lc * sy;
|
||||||
}
|
d = ld * sy;
|
||||||
if (skeleton.flipY != yDown) {
|
worldX = x * sx;
|
||||||
y = -y;
|
worldY = y * sy;
|
||||||
lc = -lc;
|
|
||||||
ld = -ld;
|
|
||||||
}
|
|
||||||
a = la;
|
|
||||||
b = lb;
|
|
||||||
c = lc;
|
|
||||||
d = ld;
|
|
||||||
worldX = x;
|
|
||||||
worldY = y;
|
|
||||||
worldSignX = Math.Sign(scaleX);
|
worldSignX = Math.Sign(scaleX);
|
||||||
worldSignY = Math.Sign(scaleY);
|
worldSignY = Math.Sign(scaleY);
|
||||||
return;
|
return;
|
||||||
@@ -196,14 +187,10 @@ namespace SpineRuntime34 {
|
|||||||
c = lc;
|
c = lc;
|
||||||
d = ld;
|
d = ld;
|
||||||
}
|
}
|
||||||
if (skeleton.flipX) {
|
a *= skeleton.scaleX;
|
||||||
a = -a;
|
b *= skeleton.scaleX;
|
||||||
b = -b;
|
c *= skeleton.scaleY;
|
||||||
}
|
d *= skeleton.scaleY;
|
||||||
if (skeleton.flipY != yDown) {
|
|
||||||
c = -c;
|
|
||||||
d = -d;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace SpineRuntime34 {
|
|||||||
internal Skin skin;
|
internal Skin skin;
|
||||||
internal float r = 1, g = 1, b = 1, a = 1;
|
internal float r = 1, g = 1, b = 1, a = 1;
|
||||||
internal float time;
|
internal float time;
|
||||||
internal bool flipX, flipY;
|
internal float scaleX = 1, scaleY = 1;
|
||||||
internal float x, y;
|
internal float x, y;
|
||||||
|
|
||||||
public SkeletonData Data { get { return data; } }
|
public SkeletonData Data { get { return data; } }
|
||||||
@@ -63,8 +63,14 @@ namespace SpineRuntime34 {
|
|||||||
public float Time { get { return time; } set { time = value; } }
|
public float Time { get { return time; } set { time = value; } }
|
||||||
public float X { get { return x; } set { x = value; } }
|
public float X { get { return x; } set { x = value; } }
|
||||||
public float Y { get { return y; } set { y = value; } }
|
public float Y { get { return y; } set { y = value; } }
|
||||||
public bool FlipX { get { return flipX; } set { flipX = value; } }
|
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||||
public bool FlipY { get { return flipY; } set { flipY = value; } }
|
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||||
|
|
||||||
|
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
|
||||||
|
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
|
||||||
|
|
||||||
|
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
|
||||||
|
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
|
||||||
|
|
||||||
public Bone RootBone {
|
public Bone RootBone {
|
||||||
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
||||||
@@ -453,5 +459,50 @@ namespace SpineRuntime34 {
|
|||||||
public void Update (float delta) {
|
public void Update (float delta) {
|
||||||
time += delta;
|
time += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GetBounds(out float x, out float y, out float width, out float height)
|
||||||
|
{
|
||||||
|
float[] temp = new float[8];
|
||||||
|
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
|
||||||
|
for (int i = 0, n = drawOrder.Count; i < n; i++)
|
||||||
|
{
|
||||||
|
Slot slot = drawOrder.Items[i];
|
||||||
|
int verticesLength = 0;
|
||||||
|
float[] vertices = null;
|
||||||
|
Attachment attachment = slot.Attachment;
|
||||||
|
if (attachment is RegionAttachment regionAttachment)
|
||||||
|
{
|
||||||
|
verticesLength = 8;
|
||||||
|
vertices = temp;
|
||||||
|
if (vertices.Length < 8) vertices = temp = new float[8];
|
||||||
|
regionAttachment.ComputeWorldVertices(slot.Bone, temp);
|
||||||
|
}
|
||||||
|
else if (attachment is MeshAttachment meshAttachment)
|
||||||
|
{
|
||||||
|
|
||||||
|
MeshAttachment mesh = meshAttachment;
|
||||||
|
verticesLength = mesh.Vertices.Length;
|
||||||
|
vertices = temp;
|
||||||
|
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
|
||||||
|
mesh.ComputeWorldVertices(slot, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertices != null)
|
||||||
|
{
|
||||||
|
for (int ii = 0; ii < verticesLength; ii += 2)
|
||||||
|
{
|
||||||
|
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||||
|
minX = Math.Min(minX, vx);
|
||||||
|
minY = Math.Min(minY, vy);
|
||||||
|
maxX = Math.Max(maxX, vx);
|
||||||
|
maxY = Math.Max(maxY, vy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x = minX;
|
||||||
|
y = minY;
|
||||||
|
width = maxX - minX;
|
||||||
|
height = maxY - minY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace SpineRuntime35 {
|
|||||||
|
|
||||||
private float timeScale = 1;
|
private float timeScale = 1;
|
||||||
|
|
||||||
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
||||||
|
|
||||||
public AnimationStateData Data { get { return data; } }
|
public AnimationStateData Data { get { return data; } }
|
||||||
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
|
/// <summary>A list of tracks that have animations, which may contain nulls.</summary>
|
||||||
|
|||||||
@@ -152,37 +152,19 @@ namespace SpineRuntime35 {
|
|||||||
|
|
||||||
Bone parent = this.parent;
|
Bone parent = this.parent;
|
||||||
if (parent == null) { // Root bone.
|
if (parent == null) { // Root bone.
|
||||||
float rotationY = rotation + 90 + shearY;
|
float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||||
float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
|
a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
|
||||||
float lb = MathUtils.CosDeg(rotationY) * scaleY;
|
b = MathUtils.CosDeg(rotationY) * scaleY * sx;
|
||||||
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
|
c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy;
|
||||||
float ld = MathUtils.SinDeg(rotationY) * scaleY;
|
d = MathUtils.SinDeg(rotationY) * scaleY * sy;
|
||||||
if (skeleton.flipX) {
|
worldX = x * sx + skeleton.x;
|
||||||
x = -x;
|
worldY = y * sy + skeleton.y;
|
||||||
la = -la;
|
|
||||||
lb = -lb;
|
|
||||||
}
|
|
||||||
if (skeleton.flipY != yDown) {
|
|
||||||
y = -y;
|
|
||||||
lc = -lc;
|
|
||||||
ld = -ld;
|
|
||||||
}
|
|
||||||
a = la;
|
|
||||||
b = lb;
|
|
||||||
c = lc;
|
|
||||||
d = ld;
|
|
||||||
worldX = x + skeleton.x;
|
|
||||||
worldY = y + skeleton.y;
|
|
||||||
// worldSignX = Math.Sign(scaleX);
|
|
||||||
// worldSignY = Math.Sign(scaleY);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
|
||||||
worldX = pa * x + pb * y + parent.worldX;
|
worldX = pa * x + pb * y + parent.worldX;
|
||||||
worldY = pc * x + pd * y + parent.worldY;
|
worldY = pc * x + pd * y + parent.worldY;
|
||||||
// worldSignX = parent.worldSignX * Math.Sign(scaleX);
|
|
||||||
// worldSignY = parent.worldSignY * Math.Sign(scaleY);
|
|
||||||
|
|
||||||
switch (data.transformMode) {
|
switch (data.transformMode) {
|
||||||
case TransformMode.Normal: {
|
case TransformMode.Normal: {
|
||||||
@@ -232,13 +214,16 @@ namespace SpineRuntime35 {
|
|||||||
case TransformMode.NoScale:
|
case TransformMode.NoScale:
|
||||||
case TransformMode.NoScaleOrReflection: {
|
case TransformMode.NoScaleOrReflection: {
|
||||||
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
|
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
|
||||||
float za = pa * cos + pb * sin;
|
float za = (pa * cos + pb * sin) / skeleton.scaleX;
|
||||||
float zc = pc * cos + pd * sin;
|
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
|
||||||
float s = (float)Math.Sqrt(za * za + zc * zc);
|
float s = (float)Math.Sqrt(za * za + zc * zc);
|
||||||
if (s > 0.00001f) s = 1 / s;
|
if (s > 0.00001f) s = 1 / s;
|
||||||
za *= s;
|
za *= s;
|
||||||
zc *= s;
|
zc *= s;
|
||||||
s = (float)Math.Sqrt(za * za + zc * zc);
|
s = (float)Math.Sqrt(za * za + zc * zc);
|
||||||
|
if (data.transformMode == TransformMode.NoScale
|
||||||
|
&& (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
|
||||||
|
|
||||||
float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
|
float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
|
||||||
float zb = MathUtils.Cos(r) * s;
|
float zb = MathUtils.Cos(r) * s;
|
||||||
float zd = MathUtils.Sin(r) * s;
|
float zd = MathUtils.Sin(r) * s;
|
||||||
@@ -250,22 +235,14 @@ namespace SpineRuntime35 {
|
|||||||
b = za * lb + zb * ld;
|
b = za * lb + zb * ld;
|
||||||
c = zc * la + zd * lc;
|
c = zc * la + zd * lc;
|
||||||
d = zc * lb + zd * ld;
|
d = zc * lb + zd * ld;
|
||||||
if (data.transformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : skeleton.flipX != skeleton.flipY) {
|
break;
|
||||||
b = -b;
|
|
||||||
d = -d;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skeleton.flipX) {
|
a *= skeleton.scaleX;
|
||||||
a = -a;
|
b *= skeleton.scaleX;
|
||||||
b = -b;
|
c *= skeleton.scaleY;
|
||||||
}
|
d *= skeleton.scaleY;
|
||||||
if (skeleton.flipY != Bone.yDown) {
|
|
||||||
c = -c;
|
|
||||||
d = -d;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetToSetupPose () {
|
public void SetToSetupPose () {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace SpineRuntime35 {
|
|||||||
internal Skin skin;
|
internal Skin skin;
|
||||||
internal float r = 1, g = 1, b = 1, a = 1;
|
internal float r = 1, g = 1, b = 1, a = 1;
|
||||||
internal float time;
|
internal float time;
|
||||||
internal bool flipX, flipY;
|
internal float scaleX = 1, scaleY = 1;
|
||||||
internal float x, y;
|
internal float x, y;
|
||||||
|
|
||||||
public SkeletonData Data { get { return data; } }
|
public SkeletonData Data { get { return data; } }
|
||||||
@@ -64,8 +64,14 @@ namespace SpineRuntime35 {
|
|||||||
public float Time { get { return time; } set { time = value; } }
|
public float Time { get { return time; } set { time = value; } }
|
||||||
public float X { get { return x; } set { x = value; } }
|
public float X { get { return x; } set { x = value; } }
|
||||||
public float Y { get { return y; } set { y = value; } }
|
public float Y { get { return y; } set { y = value; } }
|
||||||
public bool FlipX { get { return flipX; } set { flipX = value; } }
|
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||||
public bool FlipY { get { return flipY; } set { flipY = value; } }
|
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||||
|
|
||||||
|
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
|
||||||
|
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
|
||||||
|
|
||||||
|
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
|
||||||
|
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
|
||||||
|
|
||||||
public Bone RootBone {
|
public Bone RootBone {
|
||||||
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace SpineRuntime36 {
|
|||||||
|
|
||||||
private AnimationStateData data;
|
private AnimationStateData data;
|
||||||
|
|
||||||
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
private readonly Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
|
||||||
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
|
||||||
private readonly ExposedList<Event> events = new ExposedList<Event>();
|
private readonly ExposedList<Event> events = new ExposedList<Event>();
|
||||||
private readonly EventQueue queue; // Initialized by constructor.
|
private readonly EventQueue queue; // Initialized by constructor.
|
||||||
|
|||||||
Reference in New Issue
Block a user