全站通知:

模组:迁移至游戏本体1.3

来自星露谷物语维基
跳到导航 跳到搜索

模组:目录

当前页面编写的内容面向模组的开发者。模组玩家请阅读模组:模组兼容性

关于更新 Content Patcher 或 XNB 模组,请参阅将 XNB 更改迁移到星露谷物语 1.3

本页面说明了如何更新你的 SMAPI 模组代码,以兼容星露谷物语 1.3。

概述

单人模式

从宏观上看,更新 SMAPI 模组的步骤如下:

  1. 模组构建 NuGet 包 更新到 2.1-beta 版本。(你可能需要勾选“包括预发行版”复选框才能看到 beta 版本。)
    这会增加对星露谷物语 1.3 的支持,并添加代码分析功能,该功能会将星露谷物语 1.3 中的常见问题报告为生成警告。
  2. 重启 Visual Studio 以完全安装该包。
  3. 重新生成你的解决方案。
  4. 修复你在 Visual Studio 的“错误列表”窗格(或其他编辑器的等效位置)中看到的任何编译器错误和警告。
  5. 请参阅下文以获取有关特定更改和警告的帮助。
  6. 测试所有模组功能以确保它们正常工作。

如果你在更新代码时需要帮助,请随时在星露谷物语 Discord 上提问。

多人模式

单人模式的说明同样适用于多人模式。星露谷物语 1.3 会自动将大多数世界变化同步给其他玩家(请参阅Net 字段),因此许多模组也将在多人模式下工作。有些模组可能需要进一步的更改,但这只能根据具体情况来决定。

有三种常见的多人模式兼容性方法:

  • 让任何人都可以安装模组。如果你的模组对世界进行了更改,请确保在不同玩家拥有不同版本或配置,或者只有部分玩家安装了该模组的情况下,它仍然能够正常工作。
  • 只让主玩家安装模组,这样可以避免其他玩家更改相同数据带来的复杂问题。要做到这一点,请在你的代码中检查 SMAPI 的 Context.IsMainPlayer
  • 仅在单人模式下启用。这可以消除所有多人模式和同步的复杂问题,尽管玩家可能会感到失望。要做到这一点,请在你的代码中检查 SMAPI 的 Context.IsMultiplayer

常见问题:

  • 如果你的模组添加了自定义的建筑或物品,游戏在尝试将它们同步给其他玩家时可能会崩溃。

主要变更

⚠ Net 字段

“Net 类型”是星露谷物语 1.3 用于在玩家之间同步数据的几个新类之一,因其名称中带有 Net 前缀而得名。一个 Net 类型可以表示像 NetBool 这样的简单值,也可以表示像 NetFieldDictionary 这样的复杂值。许多现有的字段已被转换为 Net 类型(称为“Net 字段”),每个字段都包装了其底层值:

NetString str = new NetString("bar");
if (str.Value == "bar") // true

对模组的影响:

  • 游戏会定期收集所有可以从 Game1.netWorldState 访问到的 Net 字段,并将它们与其他玩家同步。这意味着许多模组的更改将在多人模式下自动同步。
  • Net 字段可以隐式转换为其等效的普通值(例如 bool x = new NetBool(true)),但它们的转换规则可能违反直觉且容易出错。例如,item?.category == null && item?.category != null 可能会同时为真。请始终避免隐式转换以最大程度地减少错误。

建议的修复方法:

  • 安装最新的模组构建包后,重新生成你的项目。该包将检测到你需要更新的 Net 字段引用,并显示相应的警告。请参阅下文的修复常见的生成警告

⚠ 农场帮手的位置变更

在多人模式下,如果当前玩家不是主玩家:

  • Game1.locations 不包含实际的位置列表。它包含一组本地生成的位置,这些位置与实际的游戏内位置不匹配。这也影响了像 Utility.getAllCharacters() 这样的相关功能,该功能会搜索游戏内的位置。目前对此尚无修复方法。你可以使用 SMAPI 的 helper.Multiplayer.GetActiveLocations() 来获取当前从主机同步过来的位置列表,但目前无法获取所有位置。这意味着非主玩家安装的 SMAPI 模组无法获取所有的 NPC、位置、物体等。
  • Game1.currentLocation 始终是一个活动的位置,但在玩家切换位置时可能为 null。请确保任何对该字段的引用都能处理其为 null 的情况。

Game1.player.friendships 已过时

在星露谷物语 1.3 中,Game1.player.friendships 字段始终为 null。请改用新的 Game1.player.friendshipData 字段,它用一个更有用的模型和更多数据包装了原始数据。

要转换旧代码:

旧字段 新的等效项
Game1.player.friendships[name][0] Game1.player.friendshipData[name].Points
Game1.player.friendships[name][1] Game1.player.friendshipData[name].GiftsThisWeek
Game1.player.friendships[name][2] Game1.player.friendshipData[name].TalkedToToday (0 → false, 1 → true)
Game1.player.friendships[name][3] Game1.player.friendshipData[name].GiftsToday (0 → false, 1 → true)

纹理构造函数参数

许多以前接受 Texture2D texture 参数的构造函数现在改为接受 string textureName 参数。通常最好使用 SMAPI 的内容 API 来覆盖纹理。你可以在对象构造后更改缓存的纹理(可能需要反射),但不要更改纹理名称,以避免多人同步问题。

保留的按键绑定

模组不会接收发送到聊天框的输入,也不会接收切换聊天框的按键(默认为 T)。

反射

如果你使用反射来访问私有游戏代码,请仔细检查你访问的字段/属性/方法是否仍然匹配。尤其要注意这些变化:

  • 字段/属性的返回类型发生变化;
  • 字段被属性替换。

覆盖物对象

星露谷物语 1.3 为 GameLocation 实例添加了一个 overlayObjects 字段。它们有两个特殊属性:

  • 它们不会同步给其他玩家,因此每个玩家都有自己的覆盖物对象。(这用于特殊的任务物品,这样其他玩家就无法拿走你的物品。)
  • 它们被放置在正常对象层的顶部。(如果物品放置的位置已经有一个对象,那么前一个对象将被隐藏,直到你捡起覆盖物对象,而不是被删除。)

移除的 SMAPI 已弃用 API

由于几乎所有的 SMAPI 模组都在星露谷物语 1.2 中失效,SMAPI 2.6 也放弃了对已弃用 API 的支持:

自版本 接口 替代方案
2.3 IReflectionHelper.GetPrivateField
IReflectionHelper.GetPrivateMethod
IReflectionHelper.GetPrivateProperty
分别重命名为 GetFieldGetMethodGetProperty;它们的返回值也已重命名(IPrivateFieldIReflectedFieldIPrivatePropertyIReflectedPropertyIPrivateMethodIReflectedMethod)。
2.3 IReflectionHelper.GetPrivateValue 请改用 GetPrivateField(...).GetValue()

SMAPI 事件变更

一些 SMAPI 事件被重写,以便在多人模式下更有意义。它们在底层也使用了新的架构,因此可以提供更有用的事件数据(例如,是添加/移除了,而不仅仅是当前值)。以下事件有破坏性变更:

旧事件 新事件 迁移说明
LocationEvents.CurrentLocationChanged PlayerEvents.Warped
  • EventArgsCurrentLocationChanged 更改为 EventArgsPlayerWarped
LocationEvents.LocationsChanged (名称相同)
  • EventArgsGameLocationsChanged 更改为 EventArgsLocationsChanged
  • 现在,当任何位置被添加/移除时(包括建筑内部),都会引发该事件,而不仅仅是 Game1.locations 中的主世界位置。如果你只需要处理主世界位置,可以检查 if (Game1.locations.Contains(e.NewLocation))
  • 事件数据以前包含当前的位置列表;现在它包含自上一帧以来添加或移除的位置。如果你以前使用 e.NewLocations,可以用 Game1.locations 替换它。
LocationEvents.LocationObjectsChanged LocationEvents.ObjectsChanged
  • 现在,当对象被添加/移除到任何位置时(包括建筑内部),都会引发该事件,而不仅仅是当前玩家所在的位置。如果你只需要处理当前玩家的位置,可以检查 if (e.Location == Game1.player.currentLocation)
  • 事件数据以前包含当前位置的对象列表;现在它包含该位置,以及自上一帧以来在该位置中添加/移除的对象。如果你以前使用 e.NewObjects,可以改用 e.Location.netObjects.FieldDict

有益的变更

星露谷物语 1.3 包含一些对模组作者有益的变更。这些变更不是破坏性的,但值得注意并加以利用。其中一些最相关的变更是...

  • 更多的方法和属性现在是虚拟的(virtual)。
  • Game1.WorldDate 是一个新字段,它提供了一种更有用的方式来检查日期。它结合了日、季节和年份,并提供了诸如星期几和日期比较等有用的逻辑。这整合了 SMAPI 的 SDate 类中的许多功能。
  • 许多类型检查现在允许子类(例如 item.GetType() == typeof(Axe)item is Axe)。
  • 任何 GameLocation 现在都可以设置 IsGreenhouse = true,这样作物就可以全年生长。
  • 任何 NPC 现在都可以设置 IsSocial 来决定他们是否出现在社交菜单中。
  • 蜂房现在可以在任何位置找到附近的花朵,而不仅仅是放置在农场上时。
  • 自定义地图的图块集(tilesheet)不再需要使用技巧来避免游戏的季节性逻辑。不以季节名称和下划线开头的图块集将不会被季节化。
  • 一些支持即将推出的 SMAPI 功能和修复的变更。

修复常见的生成警告

请确保检查 Visual Studio(或其他 IDE 中的等效工具)中的“错误列表”窗格,并修复所有警告。以下是一些常见的警告:

处理器架构不匹配...

警告示例:“正在生成的项目“{0}”的处理器体系结构与引用“{1}”的处理器体系结构不匹配。这种不匹配可能会导致运行时失败。

这个警告是正常的。该错误表示你的构建目标设置为“Any CPU”,但星露谷物语仅支持 x86,因此无论如何它都只能在 x86 环境下运行。你可以忽略它,或者将你的平台目标更改为 x86。

此代码隐式转换...

警告示例:“此代码将‘{0}’从 Net{1} 隐式转换为 {2},但 Net{1} 具有不直观的隐式转换规则。请考虑与实际值进行比较以避免错误。有关详细信息,请参阅 https://smapi.io/package/avoid-implicit-net-field-cast。

你的代码正在引用一个Net 字段,这可能会导致一些细微的错误。你引用的字段有一个等效的非 Net 属性,例如用 monster.Healthint 类型)代替 monster.healthNetBool 类型)。请更改你的代码以使用建议的属性。

FieldName 是一个 Net* 字段...

警告示例:“{0}' 是一个 Net{1} 字段;请考虑改用 {2} 属性。有关详细信息,请参阅 https://smapi.io/package/avoid-net-field。

你的代码正在引用一个Net 字段,这可能会导致一些细微的错误。你应该访问其底层值:

  • 对于引用类型(可以包含 null 的类型),你可以使用 .Value 属性(对于 NetDictionary 则使用 .FieldDict):
    if (building.indoors.Value == null)
    

    或者在比较之前转换值:

    GameLocation indoors = building.indoors.Value;
    if(indoors == null)
       // ...
    
  • 对于值类型(不能包含 null 的类型),请检查父对象是否为 null(如果需要),并与 .Value 进行比较:
    if (item != null && item.category.Value == 0)
    

FieldName 字段已过时...

警告示例:“‘Character.friendships’字段已过时,应替换为‘friendshipData’。有关详细信息,请参阅 https://smapi.io/package/avoid-obsolete-field。

你正在引用一个不应再使用的字段。请使用建议的字段名称来修复它。

无法创建分析器 ... 的实例

更新到最新的 Visual Studio;该 NuGet 包使用了一项新功能,旧版本中不可用。

常见问题解答

如何在多人模式下测试我的代码?

你可以在同一台计算机上通过启动两个游戏实例来测试多人模式下的模组:

  1. 准备玩家一:
    1. 像往常一样启动 SMAPI。
    2. 在标题屏幕上:点击“合作”,然后点击“主持”。
    3. 开始一个新的存档(除非你已经创建了一个)。确保将“起始小屋”设置为至少一个(每个额外玩家都需要一个小屋)。
  2. 准备玩家二:
    1. 再次启动 SMAPI。(这会自动创建一个单独的日志文件。)
    2. 在标题屏幕上:点击“合作”,然后点击“加入局域网游戏”。
    3. 将“输入 IP...”框留空,然后点击“确定”。

为什么不使用 Net 字段的隐式转换?

本迁移指南建议避免 Net 字段的隐式转换。对于许多模组来说,这可能是迁移过程中最繁琐的部分,而且即使不这样做,代码也能正常编译,所以很容易让人想跳过这一步。我们不建议这样做。尽管 Net 字段的转换在大多数情况下都能正常工作,但它们的转换规则可能会在意想不到的地方引发奇怪的错误。最好是完全避免它,而不是去学习所有可能导致问题的情况。如果你真的想这样做,你可以禁用生成警告,并自己决定何时使用隐式转换。