MIT 许可证 NuGet

首页 / 与其他引擎的对比 / 从 MonoGame 迁移

从 MonoGame 迁移

SeasonXNA 是让 XNA 形状的 2D 代码 — 精灵绘制、图像、MSDF 文本 — 跑在季节引擎渲染器之上的兼容层。本页是迁移指南:什么能带过来、 什么必须改,以及什么被刻意地没有提供。

先说结论

如果你的 MonoGame 代码主要是 SpriteBatch.Draw、 DrawString、Texture2D、 SpriteFont 和 Content.Load 调用, 那么绘图代码的形状会保留下来。保不下来的是程序集本身:SeasonXNA 在 原版 Microsoft.Xna.Framework 命名空间下发布 14 个公开类型,它们没有一个来自 MonoGame。

  • 绘图调用点保持 XNA 形状: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.FrameworkVector2、Rectangle、Color、Matrix
Microsoft.Xna.Framework.GraphicsTexture2D、SpriteFont、SpriteBatch、SpriteEffects、SpriteSortMode、BlendState、SamplerState
SeasonXNA.HostingDrawContext、FrameScope
SeasonXNA.InteropSeasonResources
  • 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。

迁移顺序

推荐顺序就是兼容层所针对的顺序。代价高昂的错误都是顺序错误。

  1. 把旧的 MonoGame 版本保持为只读,并保留一份它真实视觉与交互的参照构建用于对比。从一个页面或一个小场景开始。
  2. 创建一个 Season BaseApp 宿主,显式划分 Create、Update 和 Draw2D 边界。不要实现第二个 Game.Run。
  3. 在加载阶段通过 SeasonXNA 创建图像和字体,并保留你自己的资源表、键和路径解析。
  4. 给宿主一个 DrawContext 和一个 SpriteBatch,只在 Season 提供的 Draw2D 回调里绑定它们。
  5. 保留受支持的 XNA 绘图调用点;把改动集中在构造函数、Begin 和资源、字体包装上。不要用空 API 桩去骗过编译器 — 不支持的类型是刻意缺席的,好让每个受影响的调用点都被找到并替换。
  6. 用 Season 原生服务替换非绘图的平台功能 — 输入、音频、截图、像素处理。移除 MonoGame 不是删掉业务代码。
  7. 逐页对比:画面、输入、存档和玩法。通过 SeasonXNA 自己的验收并不意味着游戏已经迁移完成。

内容管线没有了

没有内容管线,也不会有模拟的版本。对一次移植来说,实际后果是:

  • 没有 MGCB,没有 .xnb。 内容构建器、编译后的 Content 产物和 ContentManager.Load<T> 不受支持,也永远不会被模拟。SeasonXNA 不读取 XNB 文件。
  • 资产保持源格式。 图像是非预乘 Alpha 的 PNG 或 JPEG;字体是 TrueType 或 OpenType(.ttf / .otf)。文本在运行时用 MSDF 从字体排版;烘焙进 XNB 的精灵字体在这里不存在。
  • 加载显式且只允许预加载。 在加载阶段调用 SeasonResources.LoadTexture(path) 和 LoadFont(path, size)。SeasonXNA 绝不扫描目录、不维护内容清单、不在 Draw 期间加载。
  • 路径属于你的应用。 部署布局(Android assets、Web wwwroot、桌面内容目录)和资源表都是应用层的责任。
  • Web 目标是异步加载。 在帧循环开始前,在宿主的 preload 回调里用 Image2D.LoadAsync 和 Font.CreateAsync;同步路径只服务已经准备好的缓存条目。

资产的实操顺序:删掉 MGCB 构建步骤,保留原始 PNG 和 TTF 文件, 移植路径表,把每一处 Content.Load 调用替换成 预加载调用。

不受支持的东西

有一条设计规则写在代码里并处处成立:不受支持的能力要么在编译期 缺席,要么在运行时被拒绝。没有任何东西「接受参数然后悄悄什么都不做」。

编译期就不存在

  • 窗口、游戏循环、输入和音频 — 它们留在 Season 的原生服务和平台宿主里。
  • GraphicsDevice、GraphicsDeviceManager、ContentManager 以及其余非核心 XNA 表面 — 没有发布。
  • 效果与着色器、渲染目标、截图和像素回读 — 是 Season 的原生 API,不是 XNA 形状的。
  • LinearClamp 和 PointClamp 之外的采样器,以及 AlphaBlend 标记之外的任何 BlendState — 它是 D01-A tint 适配的标记,不是可变的 GPU 混合状态。

运行时被拒绝

输入结果
任何非 Deferred 的 SpriteSortModeNotSupportedException
在 PointClamp 批次里调用 DrawStringNotSupportedException — MSDF 文本要求 LinearClamp
R、G 或 B 大于 A 的 tintNotSupportedException — 见下面的 tint 规则
带透视或 Z 耦合的 MatrixNotSupportedException — 只支持平面仿射
[0, 1] 之外的 layerDepthArgumentOutOfRangeException — Deferred 不按它排序
非有限(NaN/Infinity)的位置、原点、缩放、旋转、深度或矩阵ArgumentOutOfRangeException
负尺寸的目标矩形ArgumentOutOfRangeException — 镜像用缩放和效果位完成
尺寸为零/负数或越界的源矩形ArgumentOutOfRangeException
未知的 SpriteEffects 位ArgumentOutOfRangeException
未 Begin 就 Draw 或 End;未 End 就再次 BeginInvalidOperationException
缺字形且未设 DefaultCharacter;控制字符;无效 UTF-16ArgumentException
Dispose 之后的任何使用ObjectDisposedException

刻意不同的语义被当作已记录的差异,而不是 bug:混合输出不是完整的 XNA AlphaBlend 目标 Alpha 累积;文本没有字距调整、 塑形、双向文本或自动换行;采样只有 Clamp 模式,带半像素内缩。

非预乘 Alpha 与 tint 规则

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 字库压力路径尚未通过。

平台状态,用与本站其余部分相同的诚实度陈述:

平台后端状态
WindowsDirect3D 12参照平台:CPU 契约、GPU 图像/文本/动画、缩放和全部生命周期路径通过。
LinuxVulkan在 WSL 中用软件 llvmpipe 驱动并在验证开启下通过;不是硬件 GPU 性能主张。
AndroidVulkan宿主生命周期已接线并可编译;设备上验证推迟。
iOS / Mac CatalystMetal托管编译和 CPU 契约通过;尚无原生链接、MSL 编译或 GPU 证据。
WebWebGPU核心图像、文本和生命周期路径在浏览器中通过;大 CJK 字库压力路径尚未通过。

有一条边界值得重复:基于截图的检查验证的是可见输出的 RGB。 与 MonoGame 的原目标 Alpha 等价性没有被主张。

它在哪里停下

SeasonXNA 不是 MonoGame 的替代品,它也不试图成为。它是一个有界的 兼容层,面向 2D 精灵、图像和文本调用点 — 服务于想在新项目或 活跃开发的项目里,用熟悉的 XNA 形状绘图调用、同时获得 Season 渲染器、应用外壳及其他层次的人。

如果目标是忠实移植一款已发布的 XNA 作品,FNA 正是为此而生的工具。 如果目标是带内容管线、效果和渲染目标的完整 XNA 形状框架,答案仍然是 MonoGame。这里提供的是一个窄小、经过验证的桥 — 而窄小正是 它的意义。