SeasonXNA 是让 XNA 形状的 2D 代码 — 精灵绘制、图像、MSDF 文本 — 跑在季节引擎渲染器之上的兼容层。本页是迁移指南:什么能带过来、 什么必须改,以及什么被刻意地没有提供。
如果你的 MonoGame 代码主要是 SpriteBatch.Draw、
DrawString、Texture2D、
SpriteFont 和 Content.Load 调用,
那么绘图代码的形状会保留下来。保不下来的是程序集本身:SeasonXNA 在
原版 Microsoft.Xna.Framework 命名空间下发布
14 个公开类型,它们没有一个来自 MonoGame。
Draw 有 7 组重载、DrawString 6 组、MeasureString 2 组,值类型保留你熟悉的成员。Game 类、没有 GraphicsDeviceManager、没有固定时间步 — 你的应用是一个普通的 Season BaseApp,SeasonXNA 插入它的 Draw2D 回调。ContentManager 和 .xnb 没有了;由 SeasonResources.LoadTexture 和 LoadFont 取代,且只允许预加载。
现有类库可以在源码级复用,但不能在二进制级复用,而且只限于受支持的
子集。一个引用 MonoGame.dll 的已编译类库无法
直接替换,SeasonXNA 也不能与 MonoGame 混用在同一进程内。迁移
意味着把你的代码重新针对 SeasonXNA 编译。
全部公开面是四个命名空间里的 14 个类型。这个数字是被机械校验的, 不是靠目测 — 见下面的验证一节。
| 命名空间 | 类型 |
|---|---|
Microsoft.Xna.Framework | Vector2、Rectangle、Color、Matrix |
Microsoft.Xna.Framework.Graphics | Texture2D、SpriteFont、SpriteBatch、SpriteEffects、SpriteSortMode、BlendState、SamplerState |
SeasonXNA.Hosting | DrawContext、FrameScope |
SeasonXNA.Interop | SeasonResources |
Texture2D 和 SpriteFont 没有公开构造函数。句柄只能来自 SeasonResources — LoadTexture、LoadFont、BorrowTexture、BorrowFont、TakeTextureOwnership — 所有权始终显式。SpriteBatch 只有一个构造函数,接受 DrawContext;Begin 只接受 sortMode、blendState、samplerState 和 transformMatrix。XNA 的 Effect、DepthStencilState 和 RasterizerState 参数不是「接受但忽略」的占位符:它们不存在。DrawContext 和 FrameScope 取代帧生命周期。每次 Draw2D 回调用 using 绑定一次,绝不把作用域带过 await、线程或帧。一个只画一张图和一行文本的宿主,完整形状如下:
// XNA-shaped names at the host boundary.
using SpriteBatch = Microsoft.Xna.Framework.Graphics.SpriteBatch;
using Texture2D = Microsoft.Xna.Framework.Graphics.Texture2D;
using SpriteFont = Microsoft.Xna.Framework.Graphics.SpriteFont;
using XnaColor = Microsoft.Xna.Framework.Color;
using XnaVector2 = Microsoft.Xna.Framework.Vector2;
using XnaRectangle = Microsoft.Xna.Framework.Rectangle;
public sealed class MyApp : BaseApp
{
private readonly DrawContext _drawing = new();
private readonly SpriteBatch _sprites;
private Texture2D? _texture;
private SpriteFont? _font;
public MyApp() => _sprites = new SpriteBatch(_drawing);
// Loading phase: preload everything once, before the first frame.
public override void Create()
{
base.Create();
_texture = SeasonResources.LoadTexture("Content/Textures/Logo.png");
_font = SeasonResources.LoadFont("Content/Fonts/GameFont.ttf", 24);
}
// Recording phase: this callback only records commands into the native canvas.
public override void Draw2D(Draw2D canvas)
{
if (_texture is null || _font is null) return;
using var frame = _drawing.Bind(canvas);
try
{
_sprites.Begin(); // LinearClamp by default; required for MSDF text
_sprites.Draw(_texture, new XnaRectangle(24, 24, 320, 180), XnaColor.White);
_sprites.DrawString(_font, "Hello from SeasonXNA", new XnaVector2(24, 224), XnaColor.White);
_sprites.End();
}
catch
{
_drawing.CancelPendingBatches();
throw;
}
}
}
用 dotnet add package SeasonXNA 安装;季节引擎
核心库作为包依赖自动还原。
MAUI 和引擎类型如 Color、
Rectangle 和 Vector2
与 XNA 版本并存。在宿主边界用显式别名,
using XnaColor = Microsoft.Xna.Framework.Color;,
而不是改动引擎级的全局 using。
推荐顺序就是兼容层所针对的顺序。代价高昂的错误都是顺序错误。
BaseApp 宿主,显式划分 Create、Update 和 Draw2D 边界。不要实现第二个 Game.Run。DrawContext 和一个 SpriteBatch,只在 Season 提供的 Draw2D 回调里绑定它们。Begin 和资源、字体包装上。不要用空 API 桩去骗过编译器 — 不支持的类型是刻意缺席的,好让每个受影响的调用点都被找到并替换。没有内容管线,也不会有模拟的版本。对一次移植来说,实际后果是:
.xnb。 内容构建器、编译后的 Content 产物和 ContentManager.Load<T> 不受支持,也永远不会被模拟。SeasonXNA 不读取 XNB 文件。.ttf / .otf)。文本在运行时用 MSDF 从字体排版;烘焙进 XNB 的精灵字体在这里不存在。SeasonResources.LoadTexture(path) 和 LoadFont(path, size)。SeasonXNA 绝不扫描目录、不维护内容清单、不在 Draw 期间加载。wwwroot、桌面内容目录)和资源表都是应用层的责任。preload 回调里用 Image2D.LoadAsync 和 Font.CreateAsync;同步路径只服务已经准备好的缓存条目。
资产的实操顺序:删掉 MGCB 构建步骤,保留原始 PNG 和 TTF 文件,
移植路径表,把每一处 Content.Load 调用替换成
预加载调用。
有一条设计规则写在代码里并处处成立:不受支持的能力要么在编译期 缺席,要么在运行时被拒绝。没有任何东西「接受参数然后悄悄什么都不做」。
GraphicsDevice、GraphicsDeviceManager、ContentManager 以及其余非核心 XNA 表面 — 没有发布。LinearClamp 和 PointClamp 之外的采样器,以及 AlphaBlend 标记之外的任何 BlendState — 它是 D01-A tint 适配的标记,不是可变的 GPU 混合状态。| 输入 | 结果 |
|---|---|
任何非 Deferred 的 SpriteSortMode | NotSupportedException |
在 PointClamp 批次里调用 DrawString | NotSupportedException — MSDF 文本要求 LinearClamp |
| R、G 或 B 大于 A 的 tint | NotSupportedException — 见下面的 tint 规则 |
带透视或 Z 耦合的 Matrix | NotSupportedException — 只支持平面仿射 |
[0, 1] 之外的 layerDepth | ArgumentOutOfRangeException — Deferred 不按它排序 |
| 非有限(NaN/Infinity)的位置、原点、缩放、旋转、深度或矩阵 | ArgumentOutOfRangeException |
| 负尺寸的目标矩形 | ArgumentOutOfRangeException — 镜像用缩放和效果位完成 |
| 尺寸为零/负数或越界的源矩形 | ArgumentOutOfRangeException |
未知的 SpriteEffects 位 | ArgumentOutOfRangeException |
未 Begin 就 Draw 或 End;未 End 就再次 Begin | InvalidOperationException |
缺字形且未设 DefaultCharacter;控制字符;无效 UTF-16 | ArgumentException |
Dispose 之后的任何使用 | ObjectDisposedException |
刻意不同的语义被当作已记录的差异,而不是 bug:混合输出不是完整的 XNA
AlphaBlend 目标 Alpha 累积;文本没有字距调整、
塑形、双向文本或自动换行;采样只有 Clamp 模式,带半像素内缩。
SeasonXNA 接入 Season 的非预乘(straight)Alpha 管线,由此有两条规则。 纹理必须是非预乘 Alpha:SeasonXNA 绝不检测、反预乘或修复预乘图像。 如果旧项目在加载时做了预乘,删掉那一步。
进入绘制的 tint 必须是有界预乘值:
R <= A, G <= A, B <= A (reject otherwise)
A = 0 -> only RGBA all-zero is accepted (straight transparent black)
A > 0 -> converted to (R/A, G/A, B/A, A/255)
Color 本身仍然是普通 RGBA 值,可以表示任何东西,
包括 RGB 大于 A。但 new Color(255, 0, 0, 128)
不是合法 tint;半透明的红色请写 Color.Red * 0.5f
或 Color.FromNonPremultiplied(255, 0, 0, 128)。
两种写法可能因字节量化而有差异 — 意图永远不会被猜测。
Color * float 缩放全部四个通道(XNA 行为),
没有被改成只缩放 alpha。
这一层是作为有界契约来验证的,而不是「游戏能编译」。验证工具套件放在 开发工作区里,不随包发布;下面的数字是它的记录结果。
| 检查 | 记录结果 |
|---|---|
| 公开类型与程序集表面 | 9 组、120,742 条断言:恰好 14 个公开类型、Draw 7 / DrawString 6 / MeasureString 2 组重载、唯一的 SpriteBatch 构造函数、没有公开的 Texture2D 或 SpriteFont 构造函数、发布程序集中没有 MonoGame 引用。 |
| CPU 契约 | 12 组、累计 655 条断言:绑定、生命周期、所有权、缓存、异常恢复、绘制顺序、共享的跨平台场景和异步加载契约。 |
| Windows GPU 验收 | 真实截图 54 项 RGB 检查、四组中文文本(逐通道容差 4/255,实测最大差值 0)、窗口缩放和五条生命周期路径。 |
| Linux 在验证层下 | WSL 中共享 Vulkan 后端配软件 llvmpipe 驱动并开启 Khronos 验证:多页字形图集(1,800 个 CJK 字形、跨 2 页)、瞬态资源释放、生命周期与空租约检查。 |
| Web(WebGPU) | 浏览器中 54 项 RGB 检查和四组文本对比(最大差值 0),以及设备丢失和资源计数检查。大 CJK 字库压力路径尚未通过。 |
平台状态,用与本站其余部分相同的诚实度陈述:
| 平台 | 后端 | 状态 |
|---|---|---|
| Windows | Direct3D 12 | 参照平台:CPU 契约、GPU 图像/文本/动画、缩放和全部生命周期路径通过。 |
| Linux | Vulkan | 在 WSL 中用软件 llvmpipe 驱动并在验证开启下通过;不是硬件 GPU 性能主张。 |
| Android | Vulkan | 宿主生命周期已接线并可编译;设备上验证推迟。 |
| iOS / Mac Catalyst | Metal | 托管编译和 CPU 契约通过;尚无原生链接、MSL 编译或 GPU 证据。 |
| Web | WebGPU | 核心图像、文本和生命周期路径在浏览器中通过;大 CJK 字库压力路径尚未通过。 |
有一条边界值得重复:基于截图的检查验证的是可见输出的 RGB。 与 MonoGame 的原目标 Alpha 等价性没有被主张。
SeasonXNA 不是 MonoGame 的替代品,它也不试图成为。它是一个有界的 兼容层,面向 2D 精灵、图像和文本调用点 — 服务于想在新项目或 活跃开发的项目里,用熟悉的 XNA 形状绘图调用、同时获得 Season 渲染器、应用外壳及其他层次的人。
如果目标是忠实移植一款已发布的 XNA 作品,FNA 正是为此而生的工具。 如果目标是带内容管线、效果和渲染目标的完整 XNA 形状框架,答案仍然是 MonoGame。这里提供的是一个窄小、经过验证的桥 — 而窄小正是 它的意义。