模组:创建 SMAPI 模组
← 模组:目录
如果您是第一次制作模组 请参阅 模组:目录#创建模组 来了解 C# 模组和内容包模组之间差异的简短描述。
您想使用 C# 创建“SMAPI 模组”吗?”本指南适合您!
介绍
什么是 SMAPI 模组?
SMAPI 模组使用 SMAPI 编辑接口来扩展游戏逻辑。当游戏中发生某些事情时(例如当一个对象被放置在世界中时),该模组可以做出响应,定期运行代码(例如每个更新周期运行一次),更改游戏的资源和数据等。SMAPI 模组是使用 .NET 的 C# 编写的,游戏使用 MonoGame 运行逻辑(绘制到屏幕、用户输入等)。
.NET 的另一种语言 Visual Basic 也可以用于编写模组,如果您了解该语言的对应语法即可使用它。得益于 .NET 新版本的特性,从游戏版本 1.5.5 开始使用该语言编写模组也可以直接跨平台运行,社区中已经存在使用该语言的模组并且运行正常,也许您正在运行它们。本文中会提及一些代码示例。
为什么模组使用 SMAPI?
SMAPI 可以做许多事情,例如:
- 将你的模组加载到游戏中。如果没有 SMAPI 来加载代码模组,则不可能实现它们。
- 提供接口和事件,以原本无法实现的方式与游戏进行交互。例如游戏资源或数据更改、玩家配置、翻译、反射等的简化接口。这些内容将在指南后面介绍。
- 加载时重写模组以实现跨平台兼容性。这让您而不必担心游戏在 Linux/Mac/Windows 版本之间的差异。
- 重写模组来更新它。SMAPI 可检测并修复常见情况下因游戏更新而损坏的模组代码。
- 拦截错误。如果模组崩溃或导致错误,SMAPI 将拦截错误,在控制台窗口中显示错误详细信息,并在大多数情况下自动恢复游戏。这意味着模组不会意外导致游戏崩溃,并且可以更轻松地排除错误。
- 提供更新检查。当模组有新版本可用时,SMAPI 会自动提醒玩家。
- 提供兼容性检查。SMAPI 会自动检测模组何时不兼容,并在导致问题之前将其禁用,这样玩家就不会遇到损坏的游戏。
我能制作一个模组吗?
是的!本指南将帮助你逐步创建一个简单的模组。然后你可以继续学习,让它做您想做的事即可。
如果你是编程新手,许多模组开发人员开始时几乎没有或完全没有编程经验。如果你下定决心,当然可以沿途学习,但是您应该为陡峭的学习曲线做好准备。刚开始时不要太过于自信,弄清楚它的时候,最好从一个小的模组开始。一开始很容易变得不知所措并放弃。模组社区非常热情,所以不要害怕问问题!
如果你已经具备编程经验,那应该没问题。具有 C# 或 Java 的编程经验将使事情变得容易,但这并不重要。如果不熟悉 C#,则可以浏览下面的“学习C#”参考资料以填补所有空白。
我可以不使用 SMAPI 来制作模组吗?
当然。许多的 SMAPI 模组支持 内容包,可以让你提供它们所使用的 JSON 文本文件、图像等。例如,你可以 使用 Content Patcher 来编辑游戏的贴图并且不需要任何编程技术。本指南的其余部分是关于创建新的 SMAPI 模组的。有关内容包,请参阅 模组:Content Patcher (或模组的文档(如果为其他模组创建内容包)).
我在哪里可以得到帮助?
星露谷官方聊天室欢迎你的到来:Stardew Valley Discord
开始
学习 C#
由于模组是用 C# 编写的,因此最好先熟悉它。无需记住所有内容,但是掌握基础知识(例如字段、方法、变量和类)将使其他所有内容都变得更加容易。
一些有用的资源:
- C# 快速开始 通过交互式学习 C# 基础知识。
- C# Fundamentals for Absolute Beginners 是一个视频指南,将引导您完成 C#,从基本概念到事件驱动编程(这是 SMAPI 模组最常用的)。
- 已经知道如何编程? 请访问 LearnXinYMinutes 快速了解 C# 语法和概念。
SMAPI 经常使用的几个概念
- 泛型
- 基于事件的编程
- 对于序列化,SMAPI 通常使用 NewtonSoft
要求
在你开始之前:
- 熟悉 模组的使用,本指南的其余部分假定你已经熟悉使用模组。
- 安装游戏
- 安装 SMAPI
- 安装开发环境
- 在 Linux 上:安装 MonoDevelop 或 Rider
- 在 Mac 上:安装 Visual Studio for Mac(这是一个重新命名的 MonoDevelop)
- 在 Windows 上:安装 Visual Studio Community,当安装程序询问工作负载时,选择“.NET 桌面开发”
- 安装 NET 6.0 SDK(x64版本,也可以直接在 Visual Studio Community 单个组件中选择)
如果不熟悉 Visual Studio(Windows/Mac)或者 MonoDevelop(Linux),在 模组:IDE 参考 页面解释了如何完成本指南所需的重要工作
创建一个基本模组
快速启动
如果您有足够的经验可以跳过本教程,以下是本节的摘要:
展开以查看快速开始 |
---|
|
创建解决方案
SMAPI 模组是一个动态链接库(DLL),具有由 SMAPI 调用的入口方法
- 打开 Visual Studio 或 MonoDevelop
- 使用“类库”项目创建解决方案(请参阅如何创建项目)(不要选择“类库(.NET Framework)”!那是在游戏 1.5.4 以及之前版本中所使用的)
- 目标框架设置为 .NET 6(请参阅 如何更改目标框架)您可能需要 安装 SDK。这是游戏安装和使用的版本
- 添加 Pathoschild.Stardew.ModBuildConfig NuGet 包(请参阅 如何添加 NuGet 包)
- 如果报错“找不到类型或命名空间名称“StardewModdingAPI””,则可能没有检测到游戏路径,需要将 GamePath 属性设置为游戏的目录。这可以通过将“GamePath”属性添加到项目文件(“.csproj”或“.vbproj”)中的“PropertyGroup”节点下来完成
- 之后重新启动 Visual Studio 或者 MonoDevelop
添加代码
接下来添加代码
- 删除 Class1.cs 或者 MyClass.cs 文件 (参阅 如何删除文件).
- 在项目中添加一个 C# 类文件,取名为 ModEntry.cs (参阅 如何添加文件).
- 在此文件中输入代码 (把 YourProjectName 换成你的解决方案的名字):
using System; using Microsoft.Xna.Framework; using StardewModdingAPI; using StardewModdingAPI.Events; using StardewModdingAPI.Utilities; using StardewValley; namespace YourProjectName { /// <summary>模组入口点</summary> public class ModEntry : Mod { /********* ** 公共方法 *********/ /// <summary>模组的入口点,在首次加载模组后自动调用</summary> /// <param name="helper">对象 helper 提供用于编写模组的简化接口</param> public override void Entry(IModHelper helper) { helper.Events.Input.ButtonPressed += this.OnButtonPressed; //意思是将 OnButtonPressed 方法绑定到 SMAPI 的 ButtonPressed 按钮按下事件 //this 表示本对象,也就是当前的 ModEntry 类 } /********* ** 私有方法 *********/ /// <summary>在玩家按下键盘、控制器或鼠标上的按钮后引发</summary> /// <param name="sender">对象 sender 表示调用此方法的对象</param> /// <param name="e">对象 e 表示事件数据</param> private void OnButtonPressed(object sender, ButtonPressedEventArgs e) { // 如果玩家还没有进入存档,则取消执行 if (!Context.IsWorldReady) return; // 向控制台输出按下了什么按钮 this.Monitor.Log($"{Game1.player.Name} 按下了 {e.Button}.", LogLevel.Debug); } } }
以下是该代码的功能细分:
using X
(参阅 using directive) 使该命名空间中的类在你的代码中可用namespace YourProjectName
(参阅 namespace keyword)定义命名空间,入门时不必担心,因为在新建类文件时 Visual Studio 或 MonoDevelop 会自动添加它public class ModEntry : Mod
(参阅 class keyword) 创建你的模组的主类文件,并继承 SMAPI 的 Mod 类。SMAPI 将自动检测你的 Mod 子类,而 Mod 对象使你可访问 SMAPI 的接口public override void Entry(IModHelper helper)
是将模组加载到游戏中时 SMAPI 将调用的方法。这里的helper
对象提供了对许多 SMAPI 接口的便捷访问helper.Events.Input.ButtonPressed += this.OnButtonPressed
添加了一个事件绑定到当按下按钮的事件发生时。换句话说,当一个按钮被按下 (也就是 helper.Events.Input.ButtonPressed 事件触发了), SMAPI 会调用你的 this.OnButtonPressed 方法。参阅 SMAPI 中的事件 获取更多信息
如果您使用的是 Visual Basic 语言,以下是使用该语言的 Entry 示例,实现了和上面的 C# 代码相同的步骤和功能
Imports StardewModdingAPI
Imports StardewModdingAPI.Events
Imports StardewValley
Namespace YourProjectName
Public Class ModEntry
Inherits [Mod]
Public Overrides Sub Entry(helper As IModHelper)
AddHandler helper.Events.Input.ButtonPressed, AddressOf OnButtonPressed
End Sub
Public Sub OnButtonPressed(sender As Object, e As ButtonPressedEventArgs)
If Not Context.IsWorldReady Then Exit Sub
Monitor.Log($"{Game1.player.Name} 按下了 {e.Button}.", LogLevel.Debug)
End Sub
End Class
End Namespace
添加你的清单
模组的清单为 SMAPI 提供模组的信息
- 向你的解决方案添加一个名为 manifest.json 的文本文件
- 将这些代码复制到文件中:(注意要区分键值的大小写,避免出现意外情况)
{ "Name": "<模组的名字>", "Author": "<你的名字>", "Version": "1.0.0", "Description": "<一句话简单描述你的模组是干什么的>", //(可选) "UniqueID": "<你的名字>.<模组的名字>", "EntryDll": "<你的解决方案的名字>.dll", "MinimumApiVersion": "4.0.0", "UpdateKeys": [] //(可选)常用的更新键:"Nexus:???", "Gtihub:user/repository", "Moddrop:???" }
- 正确填写其中的信息,不要留着空信息
当游戏启动时,它将在控制台中输出。更多信息请参阅 清单文档
试试你的模组
- 构建项目。
如果正确执行了“创建项目”步骤,这会自动将模组添加到游戏的 Mods</ samp> 文件夹。 - 通过 SMAPI 运行游戏。
到目前为止,只要您在游戏中按下某个键时该模组就会向控制台窗口发送一条消息。
更改控制台文本颜色
控制台中默认文本颜色的设置方式可能会导致它们不可读。 要更改文本颜色:
- Steam:打开 Steam 并右键单击库中的游戏。单击“管理”下的“浏览本地文件”,然后打开“/Stardew Valley/smapi-internal/”文件夹中的 config.json 文件,搜索“ConsoleColors”,然后编辑“Trace”和“Debug”颜色,以便它们在控制台中可见。
- Linux:linux上的默认路径是 ~/.local/share/Steam/steamapps/common/Stardew Valley/smapi-internal/config.json,除非你已经安装 您的游戏位于不同的驱动器或者标准 Steam 库文件夹之外。
- Windows:Windows 上的默认路径应为“C:\Program files (x86)\Steam\steamapps\common\Stardew Valley\smapi-internal\config.json”,除非游戏在不同的驱动器或 Steam 库文件夹之外。
疑难解答
如果上述教程创建的模组不能正常运行:
- 重复查看以上步骤,以确保你没有跳过任何内容
- 检查是否有任何的错误消息,也许可以解释为什么它不起作用:
- 在 Visual Studio 中尝试重新构建解决方案,查看 输出 面板 或者 错误列表
- 在 MonoDevelop 中点击 Build > Rebuild All 等待处理完毕。然后点击 "Build: XX errors, XX warnings" 在顶部的条,点击 XX Errors 和 Build Output 选项卡
- 参阅 疑难解答.
- 如果其他所有方法均失败,请到 Stardew Valley Discord 寻求帮助 :)
常见问题
SMAPI 的文档在哪里?
这只是“入门”教程。更多信息请参阅 SMAPI 接口说明
我可以看看其他模组的代码吗?
是的,近 70% 的 SMAPI 模组都是开源的。要找到他们的代码:
- 打开 模组兼容性页面
- 单击搜索框下方的“show advanced info”
- 在“code”列中查找链接
我如何使我的模组跨平台工作?
SMAPI 将自动调整模组使其可以在 Linux、MacOS 和 Windows 上运行。但是你也应该采取一些措施来避免出现问题:
- 使用 跨平台构建配置 包以自动设置您的项目引用,这使跨平台兼容性变得更容易,并使代码可以在任何平台上进行编译。(如果遵循上述指南,那么就没问题了)
- 使用 Path.Combine 方法来拼接路径,而不是使用斜杠或者除号,因为不同的操作系统所使用的分隔符是不一样的
// ✘ 不要这样做!这在 Linux 和 Mac 上不会起作用 string path = this.Helper.DirectoryPath + "\assets\image.png"; // ✓ 这样才是对的 string path = Path.Combine(this.Helper.DirectoryPath, "assets", "image.png");
- 使用 this.Helper.DirectoryPath 来获取当前模组文件夹路径,请勿尝试自行确定模组路径
// ✘ 不要这样做!如果 SMAPI 重写了程序集(例如对其进行了更新或跨平台),它将崩溃 string modFolder = Assembly.GetCallingAssembly().Location; // ✓ 这样就没问题 string modFolder = this.Helper.DirectoryPath;
- “资源名称”标识可以通过
Game1.content.Load<T>("asset name")
等内容 API 加载的资产。这“不是”文件路径,并且资源名称并不总是与文件路径匹配。比较资源名称时,请确保使用PathUtilities.NormalizeAssetName("some/path")
而不是 path helpers// ✘ 不要这样做! 它不适用于 Windows。 bool isAbigail = (asset.Name == Path.Combine("Characters", "Abigail")); bool isAbigail2 = (asset.Name == PathUtilities.NormalizePath("Characters", "Abigail")); // ✓ 这样就可以了 bool isAbigail = (asset.Name == PathUtilities.NormalizeAssetName("Characters", "Abigail"));
请注意,无需标准化传递给 SMAPI API 的资源名称,SMAPI API 会自动标准化它们(尽管这样做也没什么坏处):
// ✓ 这样就可以了 helper.Content.Load<Texture2D>("Characters/Abigail"); helper.Content.Load<Texture2D>(@"Characters\Abigail");
如何反编译游戏代码?
观察游戏代码的工作方式通常对开发很有用。游戏的代码都编译在 StardewValley.dll 文件中,可以对其进行反编译以获得原始代码的近似可读性。(由于反编译和生成优化的问题,可能无法准确还原逻辑,但已经足够看到它在做什么。)
要反编译游戏代码:
- 首次步骤:
- 在 Windows 上安装 ILSpy(在 Release 的 Assets 下获取“ILSpy_binaries”文件),或在 Linux 和 macOS 上安装 Avalonia ILSpy
- 打开 ILSpy
- 单击“视图 > 选项”,滚动到底部的“Other”部分,然后启用“始终限定成员引用”
- 在 ILSpy 中打开 Stardew Valley.dll
- 确保在语言下拉列表中选择“C#”(不是 IL、IL with C# 或 ReadyToRun)
- 右键单击“Stardew Valley”并选择“保存代码”以创建可以在 Visual Studio 中打开的反编译项目
- 如果您使用 Avalonia ILSpy,请确保将 .csproj 文件扩展名添加到保存对话框中的文件名中,如下所示:Stardew-Valley.csproj (否则该项目将无法正确反编译)
- 首次步骤:
另外有一个很不错的反编译工具但已经不再维护了:dnSpy
要解包 XNB 数据或图像文件,请参阅 模组:编辑 XNB 文件
“目标 .NET 6.0”是什么意思?
这里有多种不同的东西
- 目标版本:这是编译二进制文件所针对的 .NET 版本,最新版本的游戏必须以 .NET 6.0 为目标
- SDK 版本:这是安装的 .NET 版本,可以定位低于 SDK 版本的任何版本。如果在 VS 2022 中安装了 .NET 7.0,仍然可以使用 .NET 6.0
- C# 版本:语言的版本与 .NET 版本分开(尽管存在对应关系),可以在项目中使用
<langversion>
属性来指定语言版本