1. 项目概述:为什么我们需要Steamodded?
如果你和我一样,是个《Balatro》的深度玩家,那你肯定经历过这个阶段:被游戏里那些精妙的牌组构建和风险回报机制深深吸引,但玩了几百小时后,总感觉“要是能这样改一下就好了”。也许是觉得某个小丑牌太弱想加强,也许是觉得某个星球牌的效果可以更有趣,又或者,你和我朋友一样,突发奇想想做一个“所有卡牌都是披萨主题”的模组。这就是模组(Mod)的魅力所在——它让一个已经足够优秀的游戏,变成了一个拥有无限可能的创意沙盒。
然而,对于绝大多数玩家来说,“做模组”这三个字听起来就让人头大。它似乎意味着要打开复杂的游戏文件,学习某种陌生的脚本语言,甚至可能要和反编译工具打交道。传统的模组制作,门槛确实不低。但今天要聊的Steamodded,彻底改变了这个局面。它不是一个简单的工具,而是一套为《Balatro》量身定制的、完整的模组开发解决方案。它的核心目标就一个:让没有任何编程基础的普通玩家,也能在几分钟内,创建并运行属于自己的《Balatro》模组。
简单来说,Steamodded 提供了一套标准化的“脚手架”。你不需要从零开始搭建房子,它已经为你准备好了坚固的地基、清晰的图纸和所有必要的工具。你只需要专注于最有趣的部分:发挥你的创意,设计独一无二的小丑牌、塔罗牌、星球牌,或者任何你想象中的游戏内容。它通过一个直观的图形界面(GUI)和结构化的文件管理,将模组开发从“黑盒操作”变成了“填空游戏”。接下来,我们就一步步拆解,如何用这5个步骤,从零到一实现你的模组梦。
2. Steamodded 核心架构与设计思路拆解
在动手之前,理解 Steamodded 是如何工作的,能让你后续的每一步都走得更加清晰,遇到问题时也知道该往哪个方向排查。它的设计哲学非常明确:解耦、标准化、可视化。
2.1 解耦:模组与游戏本体的安全隔离
这是 Steamodded 最聪明也最基础的设计。传统模组制作常常需要直接修改游戏的原生文件(.lua 脚本、.png 图片等),这带来了巨大的风险:一次错误的修改可能导致游戏无法启动,或者与其他模组冲突,更别提游戏更新后,所有修改都可能“灰飞烟灭”。
Steamodded 采用了完全不同的思路。它不直接触碰《Balatro》的游戏本体文件。相反,它在游戏目录之外,建立了一个独立的“模组工作区”。所有你创建的模组内容——新的脚本、新的图片、新的配置——都存放在这个独立的空间里。当游戏启动时,Steamodded 的加载器会介入,告诉游戏:“嘿,除了你自带的那些资源,也看看我这边文件夹里的东西。” 游戏会优先加载并运行你模组中的内容。
这样做的好处是显而易见的:
- 绝对安全:你的任何操作都不会损坏原版游戏。模组失效了?直接删除模组文件夹即可,游戏瞬间恢复原样。
- 易于管理:每个模组都是独立的文件夹,安装、卸载、更新都是一键操作(复制或删除文件夹)。
- 高度兼容:理论上,只要模组之间不修改同一个游戏对象,它们可以无限叠加。你可以同时运行一个加强小丑牌的模组、一个增加新卡背的模组和一个修改UI颜色的模组。
2.2 标准化:基于Lua的模块化脚本结构
《Balatro》本身是用 Lua 语言开发的,因此 Steamodded 也自然选择 Lua 作为模组的开发语言。但别担心,你不需要成为 Lua 专家。Steamodded 已经为你封装好了几乎所有与游戏交互的复杂接口。
它定义了一套清晰的、模块化的脚本结构。例如,创建一个新的小丑牌(Joker),你不再需要从头编写一个几百行的 Lua 文件。Steamodded 要求你按照一个固定的模板来组织信息:
local joker = { name = “我的超级小丑”, slug = “my_super_joker”, config = { extra = { x_mult = 1.5 } }, spritePos = {x=0, y=0}, loc_txt = { name = “超级倍率”, text = { “每打出{ X:mult }张牌”, “本回合乘倍率{X:mult}” } }, rarity = 2, cost = 5, unlocked = true, discovered = true, blueprint_compat = false, eternal_compat = true }这个结构里,每一个字段都有明确的含义:
name,slug: 模组内部标识,slug必须是英文且唯一。config: 定义这张牌的可配置参数,比如这里的x_mult(乘倍数)。spritePos: 这张牌在精灵图(Sprite Sheet)上的坐标,对应你为它绘制的图片。loc_txt: 游戏中显示的本地化文本,支持占位符(如{X:mult}会替换为config.extra.x_mult的值)。rarity,cost: 稀有度和商店售价。unlocked,discovered: 初始是否已解锁和发现。blueprint_compat,eternal_compat: 是否与蓝图牌、永恒牌兼容。
为什么这么设计?这种高度结构化的方式,将“游戏逻辑”和“数据定义”分开了。你绝大部分时间只是在填写这个“数据定义”表格。而“如何让这张牌在游戏中生效”的核心逻辑,Steamodded 的底层框架已经处理好了。你只需要在少数需要自定义效果的地方,注入一小段 Lua 函数。这极大地降低了入门门槛。
2.3 可视化:GUI工具链的辅助
对于艺术家和设计师来说,写代码可能是最痛苦的一环。Steamodded 社区也考虑到了这一点。虽然核心开发可能围绕文本和代码,但配套的工具链正在向可视化发展。
例如,创建卡牌所需的Sprite Sheet(精灵图,即包含所有卡牌图像的大图)和Atlas(图集索引文件),已经有社区开发者制作了图形化的打包工具。你只需要准备好一堆单独的 PNG 图片,拖入工具,它就能自动帮你生成符合游戏规格的Sprite Sheet和描述文件,省去了手动计算坐标、编写JSON的麻烦。
这种“核心框架标准化 + 周边工具可视化”的组合拳,确保了无论是偏好代码的逻辑派,还是偏好美术的设计派,都能找到适合自己的高效工作流。
3. 五步实操:从零构建你的第一个Balatro模组
理论说得再多,不如亲手做一遍。下面,我们就严格按照 Steamodded 的流程,创建一个最简单但也最经典的新小丑牌模组:“储蓄罐”。它的效果是:每回合结束时,如果你未使用的金钱超过 $5,则储存 $1,并永久增加该小丑牌的打分乘数(例如每储存 $1,乘数 +0.1)。
3.1 第一步:环境搭建与工具准备
工欲善其事,必先利其器。这一步的目标是建立一个干净、可用的模组开发环境。
- 安装原版《Balatro》:确保你在 Steam 上拥有并安装了最新版本的游戏。这是所有模组运行的基础。
- 下载 Steamodded 加载器:前往 Steamodded 的官方 GitHub 发布页面,下载最新版本的
Steamodded.dll文件。这是整个模组系统的“引擎”。 - 部署加载器:
- 找到你的《Balatro》游戏安装目录。通常路径为
Steam\steamapps\common\Balatro。 - 将下载的
Steamodded.dll文件复制到该目录下。 - 关键操作:在该目录中,找到游戏的主执行文件
Balatro.exe。为其创建一个快捷方式。然后,右键点击快捷方式,选择“属性”,在“目标”栏的末尾添加以下启动参数:
完整的“目标”栏看起来应该像:--luadebug“X:\...\Balatro.exe” --luadebug - 这个参数是至关重要的,它启用了游戏的 Lua 调试控制台,是 Steamodded 加载模组和输出日志信息的必要条件。
- 找到你的《Balatro》游戏安装目录。通常路径为
- 创建模组工作区:在游戏目录外,找一个你喜欢的地方(比如
D:\MyBalatroMods),新建一个文件夹。这个文件夹将存放你所有的模组项目。为我们的“储蓄罐”模组再新建一个子文件夹,命名为PiggyBank。
注意:强烈建议将模组工作区放在游戏目录之外,并做好版本管理。你可以使用 Git 来初始化这个
PiggyBank文件夹,这样能方便地回溯任何修改,也是与社区分享模组的标准方式。
3.2 第二步:创建模组骨架与元信息
每个 Steamodded 模组都必须有一个标准的入口文件来声明自己。
- 在
PiggyBank文件夹内,创建一个名为main.lua的文件。这个文件是模组的“身份证”和“总目录”。 - 用任何文本编辑器(推荐 VSCode、Sublime Text 或 Notepad++)打开
main.lua,输入以下基础代码:
local mod = { id = “piggy_bank”, name = “储蓄罐模组”, version = “1.0.0”, description = “添加一个可以存钱增长倍率的小丑牌——储蓄罐。”, author = “你的名字”, dependencies = {}, -- 如果依赖其他模组,在这里声明 enabled = true } return mod- 创建模组内容文件夹:在
PiggyBank文件夹内,继续创建以下子文件夹,这是 Steamodded 约定的标准结构:jokers/- 存放所有新小丑牌的脚本sprites/- 存放所有图片资源localization/- 存放多语言文本(可选,初期可省略)
这个结构就像一本书的目录,让 Steamodded 加载器能准确地知道去哪里找什么类型的内容。
3.3 第三步:实现核心逻辑 - 编写小丑牌脚本
现在进入最核心的部分:让“储蓄罐”活起来。
- 在
jokers/文件夹内,创建一个新的 Lua 文件,命名为piggy_bank.lua。文件名最好与牌的唯一标识(slug)一致,便于管理。 - 编写“储蓄罐”牌的完整数据与逻辑:
local piggy_bank = { name = “Piggy Bank”, slug = “piggy_bank”, config = { extra = { saved_money = 0, -- 已储存的金钱 mult_per_dollar = 0.1 -- 每储存1美元增加的乘数 } }, spritePos = {x=0, y=0}, -- 图片坐标,稍后确定 loc_txt = { name = “储蓄罐”, text = { “回合结束时,若持有金钱{>=5}”, “储存{1}美元。每储存1美元”, “永久获得{X:mult}乘数。” } }, rarity = 2, -- 稀有度,2代表罕见(Uncommon) cost = 5, -- 商店售价 unlocked = true, discovered = true, blueprint_compat = true, eternal_compat = true, -- 核心逻辑函数:计算当前乘数加成 calc = function(self, card, context) if context.end_of_round then local current_money = G.GAME.dollars or 0 if current_money >= 5 then -- 触发存钱逻辑 self.ability.extra.saved_money = self.ability.extra.saved_money + 1 G.GAME.dollars = G.GAME.dollars - 1 -- 从总金钱中扣除1 -- 这里可以添加一个存钱的特效或提示 card.ability.extra.mult = self.ability.extra.saved_money * self.ability.extra.mult_per_dollar return { message = “存入了1美元!”, dollars = -1 } end end -- 返回当前的乘数加成 if card.ability.extra.mult then return { mult = card.ability.extra.mult } end end } return piggy_bank代码关键点解析:
config.extra:这里定义了两个持久化变量,saved_money(储蓄总额)和mult_per_dollar(每美元乘数)。它们会随游戏存档。calc函数:这是小丑牌的“大脑”。它会在特定的游戏时刻(由context参数指明)被调用。context.end_of_round为真时,表示“回合结束”时刻。- 逻辑流程:回合结束时,检查当前金钱是否≥5。如果是,则储蓄额+1,总金钱-1,并基于新的储蓄额重新计算该牌提供的总乘数(
card.ability.extra.mult)。 - 返回值:
calc函数可以返回一个表(table),来告诉游戏它产生了什么效果。这里,存钱时返回一个提示信息message和金钱变化dollars;在游戏计算分数时,会返回它提供的mult乘数。
3.4 第四步:资源制作与集成 - 绘制卡牌图像
游戏不能只有逻辑,还得有“脸面”。我们需要为“储蓄罐”制作一张卡牌图像。
- 准备图像:使用 Photoshop、GIMP 甚至 Aseprite 等工具,创建一张 71x95 像素的 PNG 图片。这是《Balatro》中小丑牌的标准尺寸。你可以画一个可爱的猪猪储蓄罐。将文件保存为
piggy_bank.png,放入sprites/文件夹。 - 生成精灵图与图集:游戏并不直接加载单个 PNG 文件,而是加载一张包含所有图像的大图(精灵图)和一个索引文件(图集)。你需要使用社区工具(如 Balatro Sprite Packer)来处理。
- 将
piggy_bank.png拖入打包工具。 - 工具会输出两个文件:
your_mod_name.png(精灵图)和your_mod_name.json(图集描述文件)。 - 将这两个文件放入
PiggyBank模组根目录(与main.lua同级)。
- 将
- 在
main.lua中注册资源:返回修改main.lua,告诉 Steamodded 你使用了外部资源。
local mod = { id = “piggy_bank”, name = “储蓄罐模组”, version = “1.0.0”, description = “添加一个可以存钱增长倍率的小丑牌——储蓄罐。”, author = “你的名字”, dependencies = {}, enabled = true, -- 新增资源注册部分 sprite_atlas = “piggy_bank”, -- 对应 your_mod_name.json 的文件名(不含后缀) sprite_path = “PiggyBank.png” -- 对应 your_mod_name.png 的文件名 } -- 在文件末尾,注册我们的小丑牌 SMODS.Atlas { key = “piggy_bank”, path = mod.sprite_path, px = 71, -- 单帧宽度 py = 95 -- 单帧高度 } SMODS.Joker { key = “piggy_bank”, -- 与脚本中 slug 对应 loc_txt = piggy_bank.loc_txt, -- 从脚本中引用本地化文本 config = piggy_bank.config, rarity = piggy_bank.rarity, cost = piggy_bank.cost, unlocked = piggy_bank.unlocked, discovered = piggy_bank.discovered, blueprint_compat = piggy_bank.blueprint_compat, eternal_compat = piggy_bank.eternal_compat, pos = {x=0, y=0}, -- 精灵图中的坐标,与脚本中 spritePos 一致 atlas = “piggy_bank”, -- 使用的图集名称 calc = piggy_bank.calc -- 核心计算函数 } return mod关键点:SMODS.Joker和SMODS.Atlas是 Steamodded 提供的注册函数,用于将你的自定义内容正式注入游戏系统。pos = {x=0, y=0}意味着你的piggy_bank.png位于精灵图的左上角起始位置(第一行第一列)。如果你的精灵图里有多个图像,需要按顺序计算坐标。
3.5 第五步:测试、调试与发布
开发完成后,必须经过严格的测试。
- 安装模组:将整个
PiggyBank文件夹复制到《Balatro》游戏目录下的mods/文件夹内(如果没有则新建)。这是 Steamodded 加载器默认读取模组的位置。 - 启动游戏:使用之前创建的、带有
--luadebug参数的快捷方式启动游戏。 - 在游戏中测试:
- 开始一局新游戏或进入旧存档。
- 打开商店,查看小丑牌池。你应该能看到“储蓄罐”以设定的稀有度和价格出现。
- 购买并测试其功能:确保回合结束时,金钱≥5时会扣钱;检查卡牌描述是否正确更新;在计分时,确认乘数加成被正确应用(可以通过观察计分详情来验证)。
- 调试与日志:
- 如果模组没有生效,首先检查游戏启动时控制台(如果开启了)是否有错误信息。
- 在
calc函数中,可以使用print(“调试信息:”, variable)将变量值打印到控制台,这是最直接的调试手段。 - 仔细核对所有文件路径、名称拼写、Lua语法(特别是逗号、括号)以及
main.lua中的注册信息是否与脚本文件完全匹配。一个字母的错误都可能导致加载失败。
- 打包与分享:测试无误后,你可以将
PiggyBank文件夹压缩成.zip文件,分享到像 Balatro Mods Discord 频道或 Nexus Mods 这样的社区。记得附上一个简短的README.txt,说明模组功能和安装方法。
4. 进阶技巧与深度优化指南
完成基础模组后,你可能不满足于简单的功能。下面分享一些从社区和实战中积累的进阶技巧,能让你的模组更专业、更强大。
4.1 状态管理与数据持久化的陷阱
“储蓄罐”的saved_money是保存在牌自身的ability.extra中的。这在大多数情况下工作良好。但你需要特别注意一些边缘情况:
- 牌被复制或转化时:如果“储蓄罐”被“蓝图牌”复制,或者被“幻灵牌”效果转化,它的
ability数据可能会被重置或覆盖。为了更健壮,可以考虑将关键数据存储在更全局的地方,例如G.GAME表下,并以唯一ID进行关联。 - 存档与读档:任何存储在
ability.extra或你自定义的全局表中的简单数据类型(数字、字符串、布尔值)通常都能正确序列化存档。但避免存储函数、闭包或复杂的Lua对象,这会导致存档损坏。确保你存储的数据都是可被游戏序列化机制理解的。
一个更健壮的储蓄额存储方案示例(在calc函数中):
local save_key = “piggy_bank_saved_” .. card.unique_id if not G.GAME[save_key] then G.GAME[save_key] = 0 end local saved = G.GAME[save_key] -- ... 使用 saved 进行逻辑计算 ... G.GAME[save_key] = new_saved_value -- 更新4.2 效果系统与游戏事件的深度利用
calc函数的context参数是模组与游戏交互的生命线。除了end_of_round,还有大量其他事件可以挂钩:
context.before和context.after:在某个动作(如出牌、使用塔罗牌)前后触发。context.cardarea:可以判断当前区域(手牌、出牌区、弃牌堆等)。context.other_card:当效果涉及另一张牌时(如销毁、复制),该牌的信息。
例如,如果你想做一个“当你弃牌时,有概率获得金钱”的小丑牌,就需要监听context.discard事件。深入阅读 Steamodded 的文档或查看游戏原版 Lua 文件(在--luadebug模式下可以探索),是掌握所有可用context的关键。
4.3 性能优化与内存管理
虽然单个模组影响微乎其微,但如果你制作大型模组或同时运行很多模组,性能就需要注意。
- 避免在
calc函数中进行重型计算或循环:calc函数在游戏过程中可能被调用得非常频繁。确保其中的逻辑尽可能轻量。 - 善用本地变量和缓存:在函数开头将频繁访问的全局变量(如
G.GAME下的某些值)赋值给本地变量,能提升一点点性能。 - 清理临时对象:如果你在模组中动态创建了任何游戏对象(虽然不常见),确保在它们不再需要时,将其引用置为
nil,以帮助 Lua 垃圾回收器工作。
4.4 与社区模组的兼容性考量
当你的模组打算公开发布时,兼容性就变得重要。
- 命名空间隔离:为你模组的所有全局变量、存储在
G表中的键,都加上独特的前缀(如pb_代表 Piggy Bank)。绝对避免使用过于通用的键名,如data、config,这极易与其他模组冲突。 - 依赖声明:如果你的模组必须依赖于另一个模组(例如,需要另一个模组提供的API函数),务必在
main.lua的dependencies字段中声明。这样 Steamodded 会在加载你的模组前,先加载依赖项。 - 效果叠加规则:仔细思考你的模组效果如何与其他修改相同游戏机制的模组共存。是叠加、覆盖、还是互斥?在模组描述中清晰地说明这一点。
5. 常见问题排查与社区资源指南
即使按照教程一步步来,也难免会遇到问题。这里汇总了一些最常见的“坑”及其解决方案。
5.1 模组加载失败问题排查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 游戏启动后模组完全没出现 | 1. 未使用--luadebug参数启动。2. 模组文件夹未放在 游戏目录/mods/下。3. main.lua有语法错误。 | 1. 检查快捷方式属性,确认参数已添加。 2. 确认文件夹路径正确。 3. 检查 main.lua文件,确保 Lua 语法正确(可使用在线 Lua 语法检查器)。 |
| 模组出现在列表中,但游戏内不生效 | 1.main.lua中enabled = false。2. 资源注册失败(图集路径错误)。 3. 脚本文件未正确注册或存在逻辑错误。 | 1. 检查main.lua中的enabled字段。2. 检查 sprite_path和sprite_atlas指向的文件是否存在、命名是否正确。3. 在 calc函数开头添加print(“函数被调用”)调试,看逻辑是否执行。 |
| 游戏崩溃或报 Lua 错误 | 1. 脚本中存在访问未定义变量 (nil)。2. 在错误的地方调用了游戏API。 3. 数据格式不符合游戏预期。 | 1. 仔细阅读崩溃时控制台输出的错误信息,它会指明出错的文件和行号。 2. 检查所有变量在使用前是否都已初始化。 3. 确认传递给游戏函数的参数类型和结构正确。 |
| 卡牌图像显示为红色问号或空白 | 1. 精灵图坐标pos设置错误。2. 图片尺寸不是 71x95。 3. 图集 .json文件格式错误。 | 1. 确认pos中的x, y坐标对应精灵图中正确的位置(从0开始计数)。2. 确保原始图片尺寸精确。 3. 使用社区打包工具重新生成图集,避免手动编辑 JSON。 |
5.2 调试心得:控制台是你的最佳伙伴
开启--luadebug后,游戏会附带一个 Lua 控制台(通常需要按特定键,如`或~呼出,具体取决于版本)。这是你最强的调试武器。
- 实时探查游戏状态:在控制台中,你可以输入
= G.GAME.dollars直接查看当前金钱。这对于验证你的模组逻辑是否正确修改了游戏状态至关重要。 - 动态执行代码:你可以临时写一小段 Lua 代码来测试某个想法,而无需重启游戏。
- 查看全局表:输入
= G或= SMODS可以打印出庞大的游戏对象结构,虽然杂乱,但当你需要寻找某个特定函数或变量时,它是唯一的途径。
5.3 不可或缺的社区资源
独自摸索总是困难的,Balatro 模组社区非常活跃,善用这些资源能让你事半功倍:
- Steamodded GitHub Wiki:这是最权威的文档。详细说明了所有可注册的对象(Joker, Tarot, Planet, Enhancement, Booster, Spectral)、
calc函数的完整上下文、以及SMODSAPI 的使用方法。遇到任何框架性问题,先查这里。 - Balatro Modding Discord 频道:这里是全球模组作者的实时交流中心。你可以在这里提问、分享作品、寻找合作者、获取最新的工具和教程。很多常见问题的解决方案都能在频道的精华(pinned)消息或历史记录中找到。
- 游戏原版文件:在
--luadebug模式下,你可以通过控制台探索G这个全局表,里面包含了游戏运行时的所有数据。更重要的是,游戏安装目录下的.lua文件(通常经过一定混淆,但仍有参考价值)是理解游戏原生机制的最佳范本。看看官方的小丑牌是怎么写的,能给你带来最直接的启发。 - 其他优秀模组的源代码:在 Nexus Mods 或 Discord 上,很多作者会开源他们的模组。下载一个功能复杂的模组,研读它的代码结构,是学习高级技巧(如创建复杂的多层效果、处理动画、添加自定义UI元素)的最快方式。
从一张简单的“储蓄罐”开始,你已经走完了 Steamodded 模组开发的全流程。这套工具链的强大之处在于,它用规范化解构了复杂性。当你熟悉了jokers/文件夹的工作方式后,为游戏添加新的塔罗牌(tarots/)、星球牌(planets/)甚至全新的卡牌类型,都只是依葫芦画瓢。剩下的,就完全取决于你的想象力和对《Balatro》游戏机制的理解深度了。模组开发不再是遥不可及的黑魔法,而是每个热爱这款游戏的玩家都能触及的创意表达。现在,打开你的编辑器,开始创造那些只存在于你脑海中的、疯狂而有趣的小丑牌吧。