预修改适配对多轨道动画

This commit is contained in:
ww-rm
2025-03-27 09:56:21 +08:00
parent a372a89b5e
commit 0e6f47b23c
10 changed files with 163 additions and 200 deletions

View File

@@ -607,7 +607,7 @@ namespace SpineViewer.Controls
lock (SpineListView.Spines) lock (SpineListView.Spines)
{ {
foreach (var spine in SpineListView.Spines) foreach (var spine in SpineListView.Spines)
spine.CurrentAnimation = spine.CurrentAnimation; spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
} }
} }
} }
@@ -619,7 +619,7 @@ namespace SpineViewer.Controls
lock (SpineListView.Spines) lock (SpineListView.Spines)
{ {
foreach (var spine in SpineListView.Spines) foreach (var spine in SpineListView.Spines)
spine.CurrentAnimation = spine.CurrentAnimation; spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
} }
} }
IsUpdating = true; IsUpdating = true;

View File

@@ -23,9 +23,9 @@ namespace SpineViewer.Exporter.Implementations.Exporter
{ {
var args = (VideoExportArgs)ExportArgs; var args = (VideoExportArgs)ExportArgs;
// 独立导出时如果 args.Duration 小于 0 则使用自己的动画时长 // 独立导出时如果 args.Duration 小于 0 则使用 Track0 的动画时长
var duration = args.Duration; var duration = args.Duration;
if (duration < 0) duration = spine.CurrentAnimationDuration; if (duration < 0) duration = spine.GetAnimationDuration(spine.Track0Animation); // TODO: 也许可以使用所有轨道的最大值
float delta = 1f / args.FPS; float delta = 1f / args.FPS;
int total = Math.Max(1, (int)(duration * args.FPS)); // 至少导出 1 帧 int total = Math.Max(1, (int)(duration * args.FPS)); // 至少导出 1 帧
@@ -75,7 +75,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
public override void Export(Spine.Spine[] spines, BackgroundWorker? worker = null) public override void Export(Spine.Spine[] spines, BackgroundWorker? worker = null)
{ {
// 导出视频格式需要把模型时间都重置到 0 // 导出视频格式需要把模型时间都重置到 0
foreach (var spine in spines) spine.CurrentAnimation = spine.CurrentAnimation; foreach (var spine in spines) spine.Track0Animation = spine.Track0Animation; // TODO: 多轨道重置
base.Export(spines, worker); base.Export(spines, worker);
} }
} }

View File

@@ -82,10 +82,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -113,8 +109,8 @@ namespace SpineViewer.Spine.Implementations.Spine
var position = Position; var position = Position;
var flipX = FlipX; var flipX = FlipX;
var flipY = FlipY; var flipY = FlipY;
var animation = CurrentAnimation; var animation = Track0Animation; // TODO: 适配多轨道
var skin = CurrentSkin; var skin = Skin;
var val = Math.Max(value, SCALE_MIN); var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null) if (skeletonBinary is not null)
@@ -137,8 +133,8 @@ namespace SpineViewer.Spine.Implementations.Spine
Position = position; Position = position;
FlipX = flipX; FlipX = flipX;
FlipY = flipY; FlipY = flipY;
CurrentAnimation = animation; Track0Animation = animation; // TODO: 适配多轨道
CurrentSkin = skin; Skin = skin;
} }
} }
@@ -165,7 +161,19 @@ namespace SpineViewer.Spine.Implementations.Spine
set { skeleton.FlipY = value; Update(0); } set { skeleton.FlipY = value; Update(0); }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -178,18 +186,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -81,10 +81,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -112,8 +108,8 @@ namespace SpineViewer.Spine.Implementations.Spine
var position = Position; var position = Position;
var flipX = FlipX; var flipX = FlipX;
var flipY = FlipY; var flipY = FlipY;
var animation = CurrentAnimation; var animation = Track0Animation; // TODO: 适配多轨道
var skin = CurrentSkin; var skin = Skin;
var val = Math.Max(value, SCALE_MIN); var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null) if (skeletonBinary is not null)
@@ -136,8 +132,8 @@ namespace SpineViewer.Spine.Implementations.Spine
Position = position; Position = position;
FlipX = flipX; FlipX = flipX;
FlipY = flipY; FlipY = flipY;
CurrentAnimation = animation; Track0Animation = animation; // TODO: 适配多轨道
CurrentSkin = skin; Skin = skin;
} }
} }
@@ -164,7 +160,19 @@ namespace SpineViewer.Spine.Implementations.Spine
set { skeleton.FlipY = value; Update(0); } set { skeleton.FlipY = value; Update(0); }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -177,18 +185,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -79,10 +79,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -137,7 +133,19 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -150,18 +158,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -85,10 +85,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -143,7 +139,19 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -156,18 +164,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -81,10 +81,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -139,7 +135,19 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -152,18 +160,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -81,10 +81,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -139,7 +135,19 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -152,18 +160,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -81,10 +81,6 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations) foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name); animationNames.Add(anime.Name);
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -139,7 +135,19 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentAnimation public override string Skin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override string Track0Animation
{ {
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION; get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
set set
@@ -152,18 +160,6 @@ namespace SpineViewer.Spine.Implementations.Spine
} }
} }
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetSlotsToSetupPose();
Update(0);
}
}
public override RectangleF Bounds public override RectangleF Bounds
{ {
get get

View File

@@ -182,7 +182,38 @@ namespace SpineViewer.Spine
{ {
throw new NotImplementedException($"Not implemented version: {version}"); throw new NotImplementedException($"Not implemented version: {version}");
} }
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
var spine = (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
// 统一初始化
spine.initBounds = spine.Bounds;
// XXX: tex 没办法在这里主动 Dispose
// 批量添加在获取预览图的时候极大概率会和预览线程死锁
// 虽然两边不会同时调用 Draw, 但是死锁似乎和 Draw 函数有关
// 除此之外, 似乎还和 tex 的 Dispose 有关
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
tex.SetView(spine.InitBounds.GetView(PREVIEW_WIDTH, PREVIEW_HEIGHT));
tex.Clear(SFML.Graphics.Color.Transparent);
tex.Draw(spine);
tex.Display();
using (var img = tex.Texture.CopyToImage())
{
img.SaveToMemory(out var imgBuffer, "bmp");
using (var stream = new MemoryStream(imgBuffer))
{
// 必须重复构造一个副本才能摆脱对流的依赖, 否则之后使用会报错
spine.preview = new Bitmap(new Bitmap(stream));
}
}
// 取最后一个作为初始, 尽可能去显示非默认的内容
spine.Skin = spine.SkinNames.Last();
spine.Track0Animation = spine.AnimationNames.Last();
return spine;
} }
/// <summary> /// <summary>
@@ -300,26 +331,6 @@ namespace SpineViewer.Spine
#region | [3] #region | [3]
/// <summary>
/// 包含的所有动画名称
/// </summary>
[Browsable(false)]
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
protected List<string> animationNames = [EMPTY_ANIMATION];
/// <summary>
/// 当前动画名称, 如果设置的动画不存在则忽略
/// </summary>
[TypeConverter(typeof(AnimationConverter))]
[Category("[3] "), DisplayName("")]
public abstract string CurrentAnimation { get; set; }
/// <summary>
/// 当前动画时长
/// </summary>
[Category("[3] "), DisplayName("")]
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
/// <summary> /// <summary>
/// 包含的所有皮肤名称 /// 包含的所有皮肤名称
/// </summary> /// </summary>
@@ -328,11 +339,31 @@ namespace SpineViewer.Spine
protected List<string> skinNames = []; protected List<string> skinNames = [];
/// <summary> /// <summary>
/// 当前皮肤名称, 如果设置的皮肤不存在则忽略 /// 使用的皮肤名称, 如果设置的皮肤不存在则忽略
/// </summary> /// </summary>
[TypeConverter(typeof(SkinConverter))] [TypeConverter(typeof(SkinConverter))]
[Category("[3] "), DisplayName("")] [Category("[3] "), DisplayName("")]
public abstract string CurrentSkin { get; set; } public abstract string Skin { get; set; }
/// <summary>
/// 包含的所有动画名称
/// </summary>
[Browsable(false)]
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
protected List<string> animationNames = [EMPTY_ANIMATION];
/// <summary>
/// 默认轨道动画名称, 如果设置的动画不存在则忽略
/// </summary>
[TypeConverter(typeof(AnimationConverter))]
[Category("[3] "), DisplayName("")]
public abstract string Track0Animation { get; set; }
/// <summary>
/// 默认轨道动画时长
/// </summary>
[Category("[3] "), DisplayName("")]
public float Track0AnimationDuration { get => GetAnimationDuration(Track0Animation); } // TODO: 动画时长变成伪属性在面板显示
#endregion #endregion
@@ -395,55 +426,15 @@ namespace SpineViewer.Spine
/// 初始状态下的骨骼包围盒 /// 初始状态下的骨骼包围盒
/// </summary> /// </summary>
[Browsable(false)] [Browsable(false)]
public RectangleF InitBounds public RectangleF InitBounds { get => initBounds; }
{ private RectangleF initBounds;
get
{
if (initBounds is null)
{
var tmp = CurrentAnimation;
CurrentAnimation = EMPTY_ANIMATION;
initBounds = Bounds;
CurrentAnimation = tmp;
}
return (RectangleF)initBounds;
}
}
private RectangleF? initBounds = null;
/// <summary> /// <summary>
/// 骨骼预览图 /// 骨骼预览图
/// </summary> /// </summary>
[Browsable(false)] [Browsable(false)]
public Image Preview public Image Preview { get => preview; }
{ private Image preview;
get
{
if (preview is null)
{
// XXX: tex 没办法在这里主动 Dispose
// 批量添加在获取预览图的时候极大概率会和预览线程死锁
// 虽然两边不会同时调用 Draw, 但是死锁似乎和 Draw 函数有关
// 除此之外, 似乎还和 tex 的 Dispose 有关
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
tex.SetView(InitBounds.GetView(PREVIEW_WIDTH, PREVIEW_HEIGHT));
tex.Clear(SFML.Graphics.Color.Transparent);
var tmp = CurrentAnimation;
CurrentAnimation = EMPTY_ANIMATION;
tex.Draw(this);
CurrentAnimation = tmp;
tex.Display();
using var img = tex.Texture.CopyToImage();
img.SaveToMemory(out var imgBuffer, "bmp");
using var stream = new MemoryStream(imgBuffer);
preview = new Bitmap(new Bitmap(stream)); // 必须重复构造一个副本才能摆脱对流的依赖, 否则之后使用会报错
}
return preview;
}
}
private Image preview = null;
/// <summary> /// <summary>
/// 获取动画时长, 如果动画不存在则返回 0 /// 获取动画时长, 如果动画不存在则返回 0