Skip to content

GameUI 序列帧选型指南 ​

WasiCore 中常见的 2D 序列帧有两条路线:

  • GameUI.Control.Primitive.Sprites:原生 GameUI 控件,自己推进帧并参与 UI 布局树。
  • GameCore.Animation.AnimatedImageSource + CanvasAnimated:脚本层动画数据源,在 Canvas 渲染循环中手动绘制。

两者都能播放 sprite sheet,但抽象层级不同。选择时先判断它是“一个 UI 控件”还是“游戏画面里由脚本驱动的绘制对象”。

快速决策 ​

场景推荐方案原因
加载光效、按钮高亮、头像边框、UI 面板上的循环小动画Sprites作为普通 UI 控件使用,能直接设置尺寸、对齐、透明度、层级和父子关系。
单张图片或文件夹序列,只需要按 FPS 循环播放Sprites不需要自建渲染循环,也不需要每帧执行 C# 绘制代码。
2D 角色、敌人、飞行物、平台游戏对象AnimatedImageSource + CanvasAnimated需要和游戏循环、物理、相机、批量对象位置一起绘制。
一张图集里有 idle/walk/attack/death 等多个命名动画AnimatedImageSource + CanvasAnimatedGameDataAnimatedImage.AnimationTags 能表达命名动画、循环方向、结束行为。
需要图层、命中框、挂点、逐帧偏移、调试框AnimatedImageSource + CanvasAnimatedAnimatedImageFrameLayer / LayerDefines / GetLayerBounds 更适合这类数据。
需要 UI inspector 中看到独立控件并用布局工具排查SpritesCanvas 直接绘制的内容通常不会出现在 GameUI 控件树里。
一屏有很多由相同循环驱动的 2D 对象通常用 CanvasAnimated可以在一个渲染循环中统一更新和绘制,避免创建大量 UI 控件。

Sprites:UI 控件型序列帧 ​

Sprites 适合“它本质上是一个 UI 元素”的动画。它继承自 Control,因此可以使用 GameUI 的布局、事件、可见性、透明度、缩放、父子关系和生命周期规则。

普通单图 sprite sheet 需要告诉控件如何切帧:

csharp
#if CLIENT
using GameUI.Control.Extensions;
using GameUI.Control.Primitive;

var pulse = new Sprites()
    .Image("image/effects/energy_pulse_sheet.png")
    .SpriteSheet(frameWidth: 128, frameHeight: 128, frameCount: 8, framesPerRow: 8)
    .SpriteFrames(startFrame: 0, endFrame: 7)
    .Size(128, 128)
    .Center()
    .AddToVisualTree();

pulse.FramePerSecond = 8;
pulse.IsLooping = true;
pulse.Play();
#endif

文件夹序列资源由 native 侧从子图列表推导帧区域,通常不需要 SpriteSheet(...):

csharp
#if CLIENT
var sparkle = new Sprites()
    .Image("image/effects/sparkle_sequence")
    .Size(96, 96)
    .AddToVisualTree();

sparkle.FramePerSecond = 12;
sparkle.IsLooping = true;
sparkle.Play();
#endif

Sprites 参数语义 ​

属性 / 方法普通图片文件文件夹序列资源
SpriteSize / FrameSize单帧像素尺寸,用来计算裁剪区域不参与切帧,使用子图自身区域
FrameCount总帧数播放上限,native 侧不会超过真实子图数量
RowFrameCount / FramesPerRow每行帧数;未配置时普通图片模式不会绘制忽略
StartFrame / EndFrame播放范围,0 起始同样表示子图下标范围
IntervalMillisecond / FramePerSecond控制播放速度控制播放速度
Play() / Stop()开始或停止推进帧开始或停止推进帧

如果传入的是单张 .png sprite sheet,只设置 .Image(...) 和 .Size(...) 不足以切帧,必须设置 FrameCount 和 RowFrameCount,推荐直接使用 .SpriteSheet(...)。

AnimatedImageSource:Canvas 绘制型序列帧 ​

AnimatedImageSource 适合“它是游戏画面中的一个动画对象”。动画数据放在 GameDataAnimatedImage 中,代码在 CanvasAnimated.OnAnimatedRender 里按当前时间绘制。

csharp
#if CLIENT
using GameCore.Animation;
using GameUI.Control.Primitive;
using GameUI.Control.Primitive.Struct;

private static CanvasAnimated? canvas;
private static AnimatedImageSource? hero;

private static void CreateScene()
{
    hero = new AnimatedImageSource(MyGameData.HeroAnimatedImage);
    hero.PlayAnimation("idle", canvasElapsedMilliseconds: 0);

    canvas = new CanvasAnimated();
    canvas.FullScreen();
    canvas.StartTiming();
    canvas.OnAnimatedRender += DrawFrame;
    canvas.AddToVisualTree();
}

private static void DrawFrame(object? sender, CanvasAnimatedEventArgs e)
{
    if (canvas == null || hero == null)
    {
        return;
    }

    canvas.ResetState();
    canvas.DrawAnimatedImage(hero, x: 320f, y: 480f);
}
#endif

AnimatedImageSource 的优势来自数据结构,而不是控件树:

  • AnimationTags:一张图集上定义多个命名动画。
  • LoopDirection / EndBehavior:控制往返、倒放、结束后隐藏或回退。
  • LayerDefines / FrameLayer:表达图层、命中框、武器层、特效层等。
  • GetLayerBounds(...):把动画数据用于碰撞、拾取、受击判断或调试绘制。
  • CanvasAnimatedEventArgs:用 DeltaTime 和总时间把动画、物理和相机逻辑放到同一个渲染循环里。

常见误用 ​

误用问题推荐修正
为一个按钮呼吸光效创建 CanvasAnimated需要额外渲染循环和事件清理,UI inspector 也看不到动画内容用 Sprites 作为按钮子控件或背景控件
用 Sprites 做角色 idle/walk/attack 状态机缺少命名动画、图层、命中框和游戏循环上下文用 AnimatedImageSource + CanvasAnimated
单图 sprite sheet 只写 .Image(...).Size(...)控件不知道每帧尺寸和每行帧数,可能只显示首帧或不绘制加 .SpriteSheet(width, height, frameCount, framesPerRow)
把图片路径写成 ui/image/foo.pngGameUI/Canvas 资源路径从 ui/ 下一级开始写 image/foo.png
不再使用的 CanvasAnimated 只 RemoveFromParent()渲染事件和底层资源可能继续占用取消事件订阅后调用 Destroy()

调试与验证 ​

  • Sprites 是 GameUI 控件,可以用 ui.snapshot、ui.find、ui.get_rect 检查控件树和布局。
  • AnimatedImageSource 绘制在 Canvas 里,通常不出现在控件树中;验证画面时优先用截图。
  • Canvas 动画需要调用 StartTiming() 才会触发 OnAnimatedRender。
  • 普通 UI 控件关闭后永久不用时调用 Destroy();CanvasAnimated 若手动订阅事件,先取消订阅再 Destroy()。

相关文档 ​