模组:迁移至SMAPI 4.0
←目录
此页面解释如何升级您的 C# 模组以与 SMAPI 4.0.0 兼容(内容包不受影响)。您现在即可升级模组,无需等待 4.0 发布。
概览
该版本有何变化
五年前,SMAPI 2.0.0引入了内容 API(即 IAssetLoader 和 IAssetEditor)。自此,它成为了 SMAPI 最重要的部件之一;比如,它是 Content Patcher 的基础,而现在大约有 41.1% 的模组依赖于 Content Patcher。然而,自发布以来,此 API 并没有本质变化;时至今日,它已经无法支持所有用例。
SMAPI 4.0.0 的发布致力于解决此问题。新版本完全翻新了内容 API:
- 现在可以通过 helper 完全发现此 API,与其他 API 保持一致。这更符合模组开发者的直觉。
- 加载操作不再总是互斥,以免频繁出现模组冲突。现在可以为每个加载操作指定优先级了。
- API 不再隐式处理本地化:现在 Data/Bundles 和 Data/Bundles.fr-FR 不再被认为是相同文件(尽管仍然可以根据所需进行本地化无关的更改)。
- 添加了内容包标签,用于指示正在加载/编辑素材的内容包。这反映于日志消息中,以简化故障排除,避免内容包故障被报告给框架模组作者。
- 添加了编辑优先级,可用于微调与其他模组或编辑的兼容性,
SMAPI 4.0.0 支持 Stardew Valley 1.6 且去除了所有弃用 API。
这是一次巨变吗
不是。尽管这是一次主要更新,我们会极力减小其影响:
- 旧版的内容 API 会在很长一段时间里继续得到支持,但会在 SMAPI 控制台中引起愈发明显的警告,以提示旧版 API 已过时、应当被移除;
- 会提交拉取请求以更新受影响的模组;
- SMAPI 4.0 发布时,会为尚未提供官方更新的模组提供非官方更新;
- 会积极沟通和记录更改以帮助开发者。
上述措施意味着 SMAPI 4.0.0 将会最小程度地影响模组兼容性,尽管其更改范围较大。
如何升级模组
您无需手动梳理代码。SMAPI 会自动提示您的代码中的弃用 API。
- SMAPI 会在控制台窗口中输出弃用信息(具体格式取决于弃用级别,但您可以搜索您的模组名称):

- 当您在 Visual Studio 中查看代码时,会看见构建错误,以及相应的修复提示:

- 您可以阅读如下章节以了解如何更新特定的 API。
破坏性更改
内容拦截API
IAssetLoader 和 IAssetEditor 接口不再存在。它们已被 AssetRequested 事件取代,该事件用法如下:
public class ModEntry : Mod
{
/// <inheritdoc />
public override void Entry(IModHelper helper)
{
this.Helper.Events.Content.AssetRequested += this.OnAssetRequested;
}
/// <inheritdoc cref="IContentEvents.AssetRequested" />
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
private void OnAssetRequested(object sender, AssetRequestedEventArgs e)
{
if (e.Name.IsEquivalentTo("Portraits/Abigail"))
{
e.LoadFromModFile<Texture2D>("assets/portrait.png", AssetLoadPriority.Medium);
}
}
}
迁移提示:
- 素材名称不再本地化无关。例如,Data/Bundles 和 Data/Bundles.fr-FR 不再指示同一素材。如果希望应用本地化无关的更改,请查看 e.NameWithoutLocale 而非 e.Name。
- 组合了旧版的 CanLoad 和 CanEdit/Edit 方法,因此您只需检查一次条件逻辑。
- 现在加载素材时必须指定 AssetLoadPriority 参数以指定加载的优先级。AssetLoadPriority.Exclusive 相当于旧版的行为,但可能降低模组兼容性。参见 IntelliSense 文档以了解更多。
内容加载API
旧版的 helper.Content API 令人困惑,因为它处理原版素材和模组素材的方式并不相同。有些方法接受可选的 ContentSource 参数(但很容易遗漏),而有些方法仅支持原版素材或模组素材,而那些希望同时覆盖二者的文档十分抽象。而且旧版 API 加载的素材没有缓存,可能会影响性能,并阻碍某些功能的实现,例如新内容事件。
新版中将其分割为两个 API 以解决此问题:
| 字段 | 说明 |
|---|---|
| helper.ModContent | 加载模组素材,没有缓存(类似于 helper.Content),因此每次加载时都会从文件中重新读取。 |
| helper.GameContent | 从 Content 文件夹或内容拦截中加载素材,有缓存(新版内容事件需要缓存才能工作)。 |
下面是迁移旧版方法和属性的教程:
| 旧版代码 | 迁移方法 |
|---|---|
| helper.Content.AssetEditors helper.Content.AssetLoaders |
使用内容事件。 |
| helper.Content.CurrentLocale helper.Content.CurrentLocaleConstant helper.Content.InvalidateCache |
使用 helper.GameContent. |
| helper.Content.GetActualAssetKey | 使用 helper.ModContent.GetInternalAssetName,并移除 ContentSource 参数。新版方法会返回 IAssetName 类型值;您可以更新代码以使用它,也可使用其 Name 属性,相当于旧版的字符串值。 |
| helper.Content.GetPatchHelper | 使用 helper.GameContent 或 helper.ModContent。 |
| helper.Content.Load | 使用 helper.GameContent 或 helper.ModContent,并移除 ContentSource 参数。
迁移说明:
|
| helper.Content.NormalizeAssetName | 使用 helper.GameContent.ParseAssetName。新版方法会返回 IAssetName 类型值;您可以更新代码以使用它,也可使用其 Name 属性,相当于旧版的字符串值。 |
其他API更改
| 旧版代码 | 迁移方法 |
|---|---|
| Constants.ExecutionPath | 使用 Constants.GamePath。 |
| GameFramework.Xna | XNA 不再用于任何平台;您可以安全地移除任何针对 XNA 的逻辑。 |
| helper.ConsoleCommands.Trigger | 不再支持。您可以使用模组API来与其他模组进行整合。 |
| IAssetInfo.AssetName | 使用 Name 代替,新属性有内建的工具方法用于处理素材名称。 |
| IAssetInfo.AssetNameEquals(name) | 使用 Name.IsEquivalentTo(name)。 |
| IContentPack.LoadAsset | 使用 ModContent.Load。 |
| IContentPack.GetActualAssetKey | 使用 ModContent.GetInternalAssetName,并移除 ContentSource 参数。新版方法会返回 IAssetName 类型值;您可以更新代码以使用它,也可使用其 Name 属性,相当于旧版的字符串值。 |
| PerScreen<T>(null) | 向构造函数传入空做法已被弃用。现在应当调用 PerScreen<T>() 以使用默认值。
|
| SDate.Season | SDate.Season 现在为 Season 枚举,以与游戏保持一致。如果您确实需要使用字符串,请使用 SDate.SeasonKey。 |
可空引用类型注解
现在 SMAPI 提供对 C# 可空引用类型的完整注解。您需要在模组代码中启用它才能生效。如果您的模组使用了它们,您将从 Visual Studio 获得有用的代码分析警告,以避免在可空/不可空时发生错误。例如:
// warning: dereference of a possibly null reference
var api = this.Helper.ModRegistry.GetApi<IExampleApi>("SomeExample.ModId");
api.DoSomething();
// warning: possible null reference argument for parameter 'message'
string? message = null;
this.Monitor.Log(message);
因为 C# 可空引用类型注解的限制,无法覆盖三种边界情况。这些情况也被写进了 IntelliSense 文档。
| API | 边界情况 |
|---|---|
| helper.Reflection | GetField、GetMethod 和 GetProperty 方法被标记为返回不可空值,因为它们在目标不存在时会抛出错误。即使您显式地指定了 required: false ,这一情况也不会改变;因此,请无论如何也要对返回值判空。
|
| helper.Translation | 翻译被标记为不可空,因为可能会回退到 "missing translation: key" 缺省值。即使您显式地调用了 translation.UsePlaceholder(false),这一情况也不会改变;因此,请无论如何也要对返回值判空(如果需要)。
|
| PerScreen<T> | 其可空性依赖于您的设置。例如,PerScreen<string> 代表不可空字符串,PerScreen<string?> 代表可空字符串。然而,调用不可空引用类型的空构造函数仍然会返回空,因为这是类型默认值。例如:
var perScreen = new PerScreen<string>();
string value = perScreen.Value; // 尽管标记为非空,仍会返回空。
为避免这种情况,可以指定默认的非空值: var perScreen = new PerScreen<string>(() => string.Empty);
string value = perScreen.Value; // 默认返回空字符串。
|
移除依赖
SMAPI 4.0.0 不再使用如下依赖,因此不会再自动加载它们。如果您手动引用了它们之一,请将其复制到您的模组发布文件夹,或参见如下的迁移建议。
| 依赖 | 建议 |
|---|---|
| System.Configuration.ConfigurationManager.dll | 使用标准的配置 API 代替之。 |
| System.Runtime.Caching.dll | 避免使用此 DLL 的 MemoryCache 或 ObjectCache,因为可能影响性能。如果您需要缓存到期(cache expiry),请考虑使用更快速的 Microsoft.Extensions.Caching.Memory 包(但仍然很繁重)。否则,可以考虑直接使用 Dictionary<TKey, TValue> 字段。 |
| System.Security.Permissions.dll | 通常只有 System.Configuration.ConfigurationManager.dll 或 System.Runtime.Caching.dll 才需要它,因此可以删除。 |
其他更改
贴图原始数据
创建 Texture2D 实例开销较大,且需要调用显卡。现在,如果您不需要加载整张贴图,可以使用 IRawTextureData 加载贴图,然后将其传入接收贴图的 SMAPI API 中。
例如,您无需再创建 Texture2D 实例,而可以像这样叠加图像:
private void OnAssetRequested(object? sender, AssetRequestedEventArgs e)
{
if (e.Name.IsEquivalentTo("Portraits/Abigail"))
{
e.Edit(asset =>
{
IRawTextureData ribbon = this.Helper.ModContent.Load<IRawTextureData>("assets/ribbon.png");
asset.AsImage().PatchImage(source: ribbon);
});
}
}

沪公网安备 31011002002714 号