重构并增加HitTest

This commit is contained in:
ww-rm
2025-10-01 16:35:51 +08:00
parent 1d8e2efdff
commit 30dee9978c
170 changed files with 748 additions and 564 deletions

View File

@@ -0,0 +1,25 @@
using Spine.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IAttachment
{
/// <summary>
/// 附件的唯一名字
/// </summary>
public string Name { get; }
/// <summary>
/// 计算世界顶点数组
/// </summary>
/// <param name="slot">装载的插槽</param>
/// <param name="worldVertices">顶点缓冲数组, 如果大小不足会重分配到合适的大小, 实际长度需要通过返回值获取</param>
/// <returns><paramref name="worldVertices"/> 的实际长度, 顶点数是长度除以 2</returns>
public int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IBoundingBoxAttachment : IAttachment
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IClippingAttachment : IAttachment
{
}
}

View File

@@ -0,0 +1,52 @@
using Spine.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IMeshAttachment : IAttachment
{
/// <summary>
/// R
/// </summary>
public float R { get; set; }
/// <summary>
/// G
/// </summary>
public float G { get; set; }
/// <summary>
/// B
/// </summary>
public float B { get; set; }
/// <summary>
/// A
/// </summary>
public float A { get; set; }
/// <summary>
/// 用于渲染的纹理对象
/// </summary>
public SFML.Graphics.Texture RendererObject { get; }
/// <summary>
/// 顶点纹理坐标, 每个坐标有 u 和 v 两个数, 有效长度和 <see cref="IAttachment.ComputeWorldVertices(ISlot, ref float[])"/> 返回值一致
/// </summary>
public float[] UVs { get; }
/// <summary>
/// 三角形索引顶点数组, 每 3 个为一组, 指向顶点的下标 (不是顶点坐标数组下标)
/// </summary>
public int[] Triangles { get; }
/// <summary>
/// 边缘顶点数组长度
/// </summary>
public int HullLength { get; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IPathAttachment : IAttachment
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IPointAttachment : IAttachment
{
}
}

View File

@@ -0,0 +1,52 @@
using Spine.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface IRegionAttachment : IAttachment
{
/// <summary>
/// 总是将 Region 附件矩形区域切分成两个这样的三角形
/// </summary>
private static readonly int[] _trangles = [0, 1, 2, 2, 3, 0];
/// <summary>
/// R
/// </summary>
public float R { get; set; }
/// <summary>
/// G
/// </summary>
public float G { get; set; }
/// <summary>
/// B
/// </summary>
public float B { get; set; }
/// <summary>
/// A
/// </summary>
public float A { get; set; }
/// <summary>
/// 用于渲染的纹理对象
/// </summary>
public SFML.Graphics.Texture RendererObject { get; }
/// <summary>
/// 顶点纹理坐标, 每个坐标有 u 和 v 两个数, 有效长度和 <see cref="IAttachment.ComputeWorldVertices(ISlot, ref float[])"/> 返回值一致
/// </summary>
public float[] UVs { get; }
/// <summary>
/// 三角形索引顶点数组, 每 3 个为一组, 指向顶点的下标 (不是顶点坐标数组下标)
/// </summary>
public int[] Triangles => _trangles;
}
}

View File

@@ -0,0 +1,52 @@
using Spine.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces.Attachments
{
public interface ISkinnedMeshAttachment : IAttachment
{
/// <summary>
/// R
/// </summary>
public float R { get; set; }
/// <summary>
/// G
/// </summary>
public float G { get; set; }
/// <summary>
/// B
/// </summary>
public float B { get; set; }
/// <summary>
/// A
/// </summary>
public float A { get; set; }
/// <summary>
/// 用于渲染的纹理对象
/// </summary>
public SFML.Graphics.Texture RendererObject { get; }
/// <summary>
/// 顶点纹理坐标, 每个坐标有 u 和 v 两个数, 有效长度和 <see cref="IAttachment.ComputeWorldVertices(ISlot, ref float[])"/> 返回值一致
/// </summary>
public float[] UVs { get; }
/// <summary>
/// 三角形索引顶点数组, 每 3 个为一组, 指向顶点的下标 (不是顶点坐标数组下标)
/// </summary>
public int[] Triangles { get; }
/// <summary>
/// 边缘顶点数组长度
/// </summary>
public int HullLength { get; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface IAnimation
{
/// <summary>
/// 动画名称
/// </summary>
public string Name { get; }
/// <summary>
/// 动画时长
/// </summary>
public float Duration { get; }
}
}

View File

@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface IAnimationState
{
/// <summary>
/// 事件方法签名
/// </summary>
/// <param name="trackEntry"></param>
public delegate void TrackEntryDelegate(ITrackEntry trackEntry);
/// <summary>
/// Start 事件
/// </summary>
public event TrackEntryDelegate? Start;
/// <summary>
/// Interrupt 事件
/// </summary>
public event TrackEntryDelegate? Interrupt;
/// <summary>
/// End 事件
/// </summary>
public event TrackEntryDelegate? End;
/// <summary>
/// Complete 事件
/// </summary>
public event TrackEntryDelegate? Complete;
/// <summary>
/// Dispose 事件
/// </summary>
public event TrackEntryDelegate? Dispose;
/// <summary>
/// 速度因子
/// </summary>
public float TimeScale { get; set; }
/// <summary>
/// 遍历所有的轨道, 可能存在 null
/// </summary>
public IEnumerable<ITrackEntry?> IterTracks();
/// <summary>
/// 更新时间
/// </summary>
public void Update(float delta);
/// <summary>
/// 将动画应用到骨骼上
/// </summary>
public void Apply(ISkeleton skeleton);
/// <summary>
/// 获取指定轨道当前播放条目, 可能返回 null
/// </summary>
public ITrackEntry? GetCurrent(int index);
/// <summary>
/// 清除指定轨道
/// </summary>
public void ClearTrack(int index);
/// <summary>
/// 清除所有轨道
/// </summary>
public void ClearTracks();
/// <summary>
/// 使用动画名设置轨道动画
/// </summary>
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop);
/// <summary>
/// 使用动画对象设置轨道动画
/// </summary>
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop);
/// <summary>
/// 设置轨道空动画
/// </summary>
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration);
/// <summary>
/// 设置所有轨道空动画
/// </summary>
public void SetEmptyAnimations(float mixDuration);
/// <summary>
/// 在指定轨道后添加动画
/// </summary>
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay);
/// <summary>
/// 在指定轨道后添加动画
/// </summary>
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay);
/// <summary>
/// 在指定轨道后添加空动画
/// </summary>
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay);
}
}

66
Spine/Interfaces/IBone.cs Normal file
View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface IBone
{
/// <summary>
/// 骨骼唯一名字
/// </summary>
public string Name { get; }
/// <summary>
/// 骨骼索引
/// </summary>
public int Index { get; }
/// <summary>
/// 是否激活
/// </summary>
public bool Active { get; }
/// <summary>
/// 父骨骼
/// </summary>
public IBone? Parent { get; }
/// <summary>
/// 骨骼长度
/// </summary>
public float Length { get; }
/// <summary>
/// 世界坐标 X
/// </summary>
public float WorldX { get; }
/// <summary>
/// 世界坐标 Y
/// </summary>
public float WorldY { get; }
/// <summary>
/// <c>Cos(theta)</c>
/// </summary>
public float A { get; }
/// <summary>
/// <c>-Sin(theta)</c>
/// </summary>
public float B { get; }
/// <summary>
/// <c>Sin(theta)</c>
/// </summary>
public float C { get; }
/// <summary>
/// <c>Cos(theta)</c>
/// </summary>
public float D { get; }
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface ISkeleton
{
/// <summary>
/// 物理约束
/// </summary>
public enum Physics { None, Reset, Update, Pose }
/// <summary>
/// R
/// </summary>
public float R { get; set; }
/// <summary>
/// G
/// </summary>
public float G { get; set; }
/// <summary>
/// B
/// </summary>
public float B { get; set; }
/// <summary>
/// A
/// </summary>
public float A { get; set; }
/// <summary>
/// 横坐标
/// </summary>
public float X { get; set; }
/// <summary>
/// 纵坐标
/// </summary>
public float Y { get; set; }
/// <summary>
/// 水平缩放, 负数时会翻转
/// </summary>
public float ScaleX { get; set; }
/// <summary>
/// 垂直缩放, 负数时会翻转
/// </summary>
public float ScaleY { get; set; }
/// <summary>
/// 所有骨骼按顺序
/// </summary>
public ImmutableArray<IBone> Bones { get; }
/// <summary>
/// 所有骨骼按名称
/// </summary>
public FrozenDictionary<string, IBone> BonesByName { get; }
/// <summary>
/// 所有插槽按顺序
/// </summary>
public ImmutableArray<ISlot> Slots { get; }
/// <summary>
/// 所有插槽按名称
/// </summary>
public FrozenDictionary<string, ISlot> SlotsByName { get; }
/// <summary>
/// 皮肤, 可以设置 null 值清除皮肤
/// </summary>
public ISkin? Skin { get; set; }
/// <summary>
/// 遍历渲染时槽顺序
/// </summary>
public IEnumerable<ISlot> IterDrawOrder();
/// <summary>
/// 更新附件约束等的缓存
/// </summary>
public void UpdateCache();
/// <summary>
/// 更新变换
/// </summary>
public void UpdateWorldTransform(Physics physics);
/// <summary>
/// 重置所有参数至初始值
/// </summary>
public void SetToSetupPose();
/// <summary>
/// 重置所有骨骼参数至初始值
/// </summary>
public void SetBonesToSetupPose();
/// <summary>
/// 重置所有插槽参数至初始值
/// </summary>
public void SetSlotsToSetupPose();
/// <summary>
/// 更新时间
/// </summary>
public void Update(float delta);
}
}

View File

@@ -0,0 +1,62 @@
using Spine.Interfaces.Attachments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface ISkeletonClipping
{
/// <summary>
/// 是否正处于裁剪
/// </summary>
public bool IsClipping { get; }
/// <summary>
/// 裁剪后的顶点数组
/// </summary>
public float[] ClippedVertices { get; }
/// <summary>
/// 裁剪后的顶点数组 <see cref="ClippedVertices"/> 长度
/// </summary>
public int ClippedVerticesLength { get; }
/// <summary>
/// 裁剪后的三角形索引数组
/// </summary>
public int[] ClippedTriangles { get; }
/// <summary>
/// 裁剪后的三角形索引数组 <see cref="ClippedTriangles"/> 长度
/// </summary>
public int ClippedTrianglesLength { get; }
/// <summary>
/// 裁剪后的 UV 数组
/// </summary>
public float[] ClippedUVs { get; }
/// <summary>
/// 进行裁剪, 裁剪后的结果通过属性获取
/// </summary>
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs);
/// <summary>
/// 开始裁剪
/// </summary>
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment);
/// <summary>
/// 判断输入插槽是否需要结束裁剪并结束裁剪
/// </summary>
public void ClipEnd(ISlot slot);
/// <summary>
/// 结束裁剪
/// </summary>
public void ClipEnd();
}
}

26
Spine/Interfaces/ISkin.cs Normal file
View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface ISkin
{
/// <summary>
/// 皮肤名字
/// </summary>
public string Name { get; }
/// <summary>
/// 添加其他皮肤
/// </summary>
public void AddSkin(ISkin skin);
/// <summary>
/// 清空皮肤内容
/// </summary>
public void Clear();
}
}

63
Spine/Interfaces/ISlot.cs Normal file
View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces.Attachments;
namespace Spine.Interfaces
{
public interface ISlot
{
/// <summary>
/// 插槽唯一名字
/// </summary>
public string Name { get; }
/// <summary>
/// 插槽唯一索引
/// </summary>
public int Index { get; }
/// <summary>
/// 混合模式
/// </summary>
public SFML.Graphics.BlendMode Blend { get; }
/// <summary>
/// R
/// </summary>
public float R { get; set; }
/// <summary>
/// G
/// </summary>
public float G { get; set; }
/// <summary>
/// B
/// </summary>
public float B { get; set; }
/// <summary>
/// A
/// </summary>
public float A { get; set; }
/// <summary>
/// 所在骨骼
/// </summary>
public IBone Bone { get; }
/// <summary>
/// 使用的附件, 可以设置为 null 清空附件
/// </summary>
public IAttachment? Attachment { get; set; }
/// <summary>
/// 是否已禁用渲染该插槽
/// </summary>
public bool Disabled { get; set; }
}
}

View File

@@ -0,0 +1,52 @@
using Spine.Interfaces.Attachments;
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
/// <summary>
/// 对 SkeletonData 和 AnimationStateData 的访问封装
/// </summary>
public interface ISpineObjectData
{
/// <summary>
/// skel 文件版本
/// </summary>
public string SkeletonVersion { get; }
/// <summary>
/// 所有皮肤
/// </summary>
public ImmutableArray<ISkin> Skins { get; }
/// <summary>
/// 所有皮肤按名称
/// </summary>
public FrozenDictionary<string, ISkin> SkinsByName { get; }
/// <summary>
/// 所有皮肤中所有插槽的可用附件集合, 并不保证所有插槽均在此处有键
/// </summary>
public FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments { get; }
/// <summary>
/// 所有动画
/// </summary>
public ImmutableArray<IAnimation> Animations { get; }
/// <summary>
/// 所有动画按名称
/// </summary>
public FrozenDictionary<string, IAnimation> AnimationsByName { get; }
/// <summary>
/// 默认的动画过渡时长
/// </summary>
public float DefaultMix { get; set; }
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public interface ITrackEntry
{
/// <summary>
/// Start 事件
/// </summary>
public event IAnimationState.TrackEntryDelegate? Start;
/// <summary>
/// Interrupt 事件
/// </summary>
public event IAnimationState.TrackEntryDelegate? Interrupt;
/// <summary>
/// End 事件
/// </summary>
public event IAnimationState.TrackEntryDelegate? End;
/// <summary>
/// Complete 事件
/// </summary>
public event IAnimationState.TrackEntryDelegate? Complete;
/// <summary>
/// Dispose 事件
/// </summary>
public event IAnimationState.TrackEntryDelegate? Dispose;
/// <summary>
/// 所在轨道序号
/// </summary>
public int TrackIndex { get; }
/// <summary>
/// 播放的动画
/// </summary>
public IAnimation Animation { get; }
/// <summary>
/// 下一个条目, 形成播放链表
/// </summary>
public ITrackEntry? Next { get; }
/// <summary>
/// 动画是否循环
/// </summary>
public bool Loop { get; set; }
/// <summary>
/// 轨道当前时间
/// </summary>
public float TrackTime { get; set; }
/// <summary>
/// 速度因子
/// </summary>
public float TimeScale { get; set; }
/// <summary>
/// 多轨道的 Alpha 混合
/// </summary>
public float Alpha { get; set; }
/// <summary>
/// 过渡到下一个条目的时长
/// </summary>
public float MixDuration { get; set; }
}
}

View File

@@ -0,0 +1,122 @@
using Spine.Interfaces.Attachments;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Interfaces
{
public static class SpineExtension
{
/// <summary>
/// 获取当前状态包围盒
/// </summary>
public static void GetBounds(this ISlot self, out float x, out float y, out float w, out float h)
{
float[] vertices = new float[8];
int verticesLength = 0;
var attachment = self.Attachment;
switch (attachment)
{
case IRegionAttachment:
case IMeshAttachment:
verticesLength = attachment.ComputeWorldVertices(self, ref vertices);
break;
default:
break;
}
if (verticesLength > 0)
{
float minX = int.MaxValue;
float minY = int.MaxValue;
float maxX = int.MinValue;
float maxY = int.MinValue;
for (int ii = 0; ii + 1 < verticesLength; ii += 2)
{
float vx = vertices[ii];
float 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;
w = maxX - minX;
h = maxY - minY;
}
else
{
x = self.Bone.WorldX;
y = self.Bone.WorldY;
w = 0;
h = 0;
}
}
/// <summary>
/// 命中测试, 当插槽全透明或者处于禁用或者骨骼处于未激活则无法命中
/// </summary>
public static bool HitTest(this ISlot self, float x, float y)
{
if (self.A <= 0 || !self.Bone.Active || self.Disabled)
return false;
self.GetBounds(out var bx, out var by, out var bw, out var bh);
return x >= bx && x <= (bx + bw) && y >= by && y <= (by + bh);
}
/// <summary>
/// 获取当前状态包围盒
/// </summary>
public static void GetBounds(this ISkeleton self, out float x, out float y, out float w, out float h)
{
float minX = int.MaxValue;
float minY = int.MaxValue;
float maxX = int.MinValue;
float maxY = int.MinValue;
foreach (var slot in self.IterDrawOrder())
{
if (slot.A <= 0 || !slot.Bone.Active || slot.Disabled)
continue;
float[] vertices = new float[8];
int verticesLength = 0;
var attachment = slot.Attachment;
switch (attachment)
{
case IRegionAttachment:
case IMeshAttachment:
verticesLength = attachment.ComputeWorldVertices(slot, ref vertices);
break;
default:
break;
}
for (int ii = 0; ii + 1 < verticesLength; ii += 2)
{
float vx = vertices[ii];
float 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;
w = maxX - minX;
h = maxY - minY;
}
/// <summary>
/// 逐插槽的命中测试, 不会计算处于禁用或者骨骼未激活的插槽, 比整体包围盒稍微精确一些
/// </summary>
public static bool HitTest(this ISkeleton self, float x, float y)
{
return self.IterDrawOrder().Any(st => st.HitTest(x, y));
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NLog;
using Spine.Implementations;
using Spine.Interfaces.Attachments;
namespace Spine.Interfaces
{
/// <summary>
/// 应当继承该类实现多版本, 子类需要提供签名为 <c><see cref="new(string, string)"/></c> 的构造函数
/// </summary>
public abstract class SpineObjectData :
Utils.ImplementationResolver<SpineObjectData, Utils.SpineImplementationAttribute, string>,
ISpineObjectData,
IDisposable
{
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// 构建版本对象
/// </summary>
public static SpineObjectData New(SpineVersion version, string skelPath, string atlasPath, TextureLoader textureLoader)
=> CreateInstance(version.Tag, skelPath, atlasPath, textureLoader);
/// <summary>
/// 构造函数, 继承的子类应当实现一个相同签名的构造函数
/// </summary>
public SpineObjectData(string skelPath, string atlasPath, TextureLoader textureLoader) { }
public abstract string SkeletonVersion { get; }
public abstract ImmutableArray<ISkin> Skins { get; }
public abstract FrozenDictionary<string, ISkin> SkinsByName { get; }
public abstract FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments { get; }
public abstract ImmutableArray<IAnimation> Animations { get; }
public abstract FrozenDictionary<string, IAnimation> AnimationsByName { get; }
public abstract float DefaultMix { get; set; }
/// <summary>
/// 释放纹理资源
/// </summary>
protected abstract void DisposeAtlas();
/// <summary>
/// 创建 skeleton
/// </summary>
/// <returns></returns>
public abstract ISkeleton CreateSkeleton();
/// <summary>
/// 创建 animationState
/// </summary>
public abstract IAnimationState CreateAnimationState();
/// <summary>
/// 创建 skeletonClipping
/// </summary>
public abstract ISkeletonClipping CreateSkeletonClipping();
/// <summary>
/// 创建空皮肤
/// </summary>
public abstract ISkin CreateSkin(string name);
#region IDispose
/// <summary>
/// 引用计数, 初始为 1, 每调用一次 Dispose 会减 1, 由于可能被多个实例同时引用, 因此直到小于等于 0 才会真正释放资源
/// </summary>
private int _refCount = 1;
/// <summary>
/// 增加引用计数, 当使用同一份数据创建实例副本时, 调用方负责使用该方法增加引用计数, 并且使用 Dispose 或者 Finalize 减少引用计数, 初始为 1
/// </summary>
public void IncRef() => _refCount++;
protected virtual void Dispose(bool disposing)
{
if (_refCount <= 0) return;
_refCount--;
if (_refCount <= 0)
{
if (disposing)
{
DisposeAtlas();
}
}
}
~SpineObjectData()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
if (_refCount <= 0)
{
GC.SuppressFinalize(this);
}
}
#endregion
}
}