imp v34 & v35

This commit is contained in:
ww-rm
2025-08-18 01:23:31 +08:00
parent 50b1c66e27
commit b08b6752cd
41 changed files with 2434 additions and 127 deletions

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

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

View File

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

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

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

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

View File

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

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

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

View 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);
}
}

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

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

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

View File

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

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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));
}
}
}

View File

@@ -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;
}
}

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

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

View File

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

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

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

View 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);
}
}

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

View File

@@ -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" />

View File

@@ -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));

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 &lt;= 0 to use duration of previous animation minus any mix duration plus the negative delay.</param> /// <param name="delay">May be &lt;= 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 &lt;= 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();
}
}
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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>

View File

@@ -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 () {

View File

@@ -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]; }

View File

@@ -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.