杀戮尖塔Mod制作入门:从零跑通第一个可玩模组

发布时间:2026/10/15 10:59:34
杀戮尖塔Mod制作入门:从零跑通第一个可玩模组 简介这是一份面向《杀戮尖塔》模组开发初学者的入门教程PDF适合具备一定Java基础、希望从零开始搭建MOD开发环境的玩家与编程爱好者。内容围绕modthespire、stslib、basemod三大支持库展开讲解游戏本体安装、创意工坊订阅、IDE选择与Maven环境配置并逐步演示如何创建Maven项目、编辑pom.xml引入BaseMod与游戏本体依赖最终构建出可运行的MOD工程。资源包共1个PDF文件约1.5MB篇幅紧凑涵盖从环境准备到项目构建的完整流程同时穿插控制台输出、基础变量、函数作用域、监听函数与继承等语法要点帮助读者理解MOD代码如何基于游戏本体类进行扩展。目前已有2101人学习适合作为动手实践前的参考手册为后续开发自定义卡牌、角色或游戏机制打下基础。1. 杀戮尖塔Mod制作入门从“想改一张卡”到跑通第一个可玩模组很多玩家第一次动念头做《杀戮尖塔》Mod都是因为某张卡牌数值看着不顺眼或者想给某个角色加一套自己设计的机制。但真打开工程目录看到一堆 JSON、Java 类和资源路径很容易在第一步就卡住。这篇笔记面向的是有基础编程概念、想动手做一个能进游戏、能打出效果的 Mod 的从业者或爱好者。我会按“环境怎么搭、卡牌怎么加、遗物怎么挂、本地化怎么配、打包怎么避坑”的顺序把一条最小可玩路径拆开讲。你不需要先成为 Java 高手但需要愿意照着命令跑一遍、看报错、改配置。读完至少能自己做出一个带自定义卡牌和遗物的可加载 Mod并知道后续扩展该往哪走。2. 环境与工程骨架把 Mod 塞进游戏能识别的目录2.1 先分清 ModTheSpire 和 BaseMod 各自管什么《杀戮尖塔》的 Mod 加载体系里ModTheSpire 是入口负责在游戏启动时扫描 Mod 目录、加载 jar 包、管理依赖和补丁注入。BaseMod 则是更上层的开发框架封装了卡牌注册、遗物注册、本地化、存档字段等常用接口。很多新手会把两者混在一起结果把 BaseMod 的 API 直接写进没有依赖它的工程编译能过进游戏就报 ClassNotFound。常见做法是工程里同时声明对 ModTheSpire 和 BaseMod 的依赖但代码里只调用 BaseMod 暴露的注册方法。ModTheSpire 的 API 一般只在需要做底层补丁或自定义控制台命令时才直接碰。选型理由很简单BaseMod 已经帮你处理了大部分注册流程你只需要在receivePostInitialize或receiveEditCards这类回调里把自定义内容挂上去不用自己写字节码注入。2.2 用 Gradle 搭一个能编译出 jar 的最小工程我一般会从空目录开始手写build.gradle而不是直接复制大型 Mod 的工程因为依赖越多第一次跑通越容易被版本冲突带偏。下面是一个能编译出可加载 jar 的最小配置依赖坐标用本地libs目录里的 jar避免网络仓库拉不到旧版本。plugins { id java } group com.example.mymod version 0.1.0 repositories { flatDir { dirs libs } // 本地放 ModTheSpire.jar 和 BaseMod.jar } dependencies { compileOnly name: ModTheSpire compileOnly name: BaseMod compileOnly org.jetbrains:annotations:24.0.0 } jar { archiveBaseName MyFirstMod from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } duplicatesStrategy DuplicatesStrategy.EXCLUDE manifest { attributes( Mod-The-Spire-Version: 1.0, Mod-Loader-Version: 1.0 ) } }逻辑说明compileOnly表示这两个 jar 只在编译期参与不会打进最终产物因为游戏启动时 ModTheSpire 已经提供了它们。jar任务里把运行时依赖一起打包是为了让 BaseMod 等库在加载时能找到。manifest里的属性是 ModTheSpire 识别 Mod 的基本要求缺了可能直接不显示在 Mod 列表里。参数说明archiveBaseName决定生成的 jar 名字建议和 Mod 名一致方便排查。duplicatesStrategy设成 EXCLUDE 是为了避免多个依赖里出现同名文件导致打包失败。如果你用的 BaseMod 版本较新可能还需要在 manifest 里加Mod-Dependencies字段但第一次跑通可以先不加等加载报错再补。2.3 入口类要继承哪个基类、重写哪些方法入口类通常继承BaseMod并加上SpireInitializer注解。这个注解让 ModTheSpire 在扫描时能找到初始化入口。下面是一个最小入口只做卡牌和遗物注册不做多余逻辑。SpireInitializer public class MyFirstMod implements BaseMod { public static final String MOD_ID MyFirstMod; public MyFirstMod() { BaseMod.subscribe(this); // 把自己注册进事件总线 } public static void initialize() { new MyFirstMod(); } Override public void receiveEditCards() { BaseMod.addCard(new StrikePlus()); } Override public void receiveEditRelics() { BaseMod.addRelic(new SimpleCharm(), RelicType.SHARED); } Override public void receiveEditStrings() { BaseMod.loadCustomStringsFile(CardStrings.class, localization/zhs/cards.json); BaseMod.loadCustomStringsFile(RelicStrings.class, localization/zhs/relics.json); } }逻辑说明BaseMod.subscribe(this)让当前实例接收 BaseMod 的事件回调否则receiveEditCards不会被触发。initialize是静态入口ModTheSpire 通过反射调用它。receiveEditCards和receiveEditRelics分别在对应阶段被调用你只需要在这里把自定义内容加进去。参数说明RelicType.SHARED表示遗物在所有角色池里都可能出现如果想限定某个角色可以换成对应的类型。loadCustomStringsFile的第一个参数是字符串类第二个是资源路径路径相对于 jar 根目录不要写成绝对路径。3. 自定义卡牌从类结构到实际可打出的数值3.1 卡牌类必须实现的抽象方法和常见返回值一张自定义卡牌通常继承AbstractCard并实现use、upgrade、makeCopy等抽象方法。下面这张“打击”是在基础打击上提高伤害用来验证注册流程是否通畅。public class StrikePlus extends AbstractCard { public static final String ID MyFirstMod:StrikePlus; public StrikePlus() { super(ID, 打击, images/cards/strike_plus.png, CardType.ATTACK, CardColor.RED, CardRarity.BASIC, CardTarget.ENEMY); this.baseDamage 9; this.damage this.baseDamage; } Override public void use(AbstractPlayer p, AbstractMonster m) { addToBot(new DamageAction(m, new DamageInfo(p, this.damage, this.damageTypeForTurn), AbstractGameAction.AttackEffect.SLASH_DEFAULT)); } Override public void upgrade() { if (!this.upgraded) { this.upgradeName(); this.upgradeDamage(3); } } Override public AbstractCard makeCopy() { return new StrikePlus(); } }逻辑说明构造函数里的参数依次是 ID、显示名、图片路径、卡牌类型、颜色、稀有度、目标类型。baseDamage和damage要同时设置否则第一次打出时可能按 0 计算。use里用addToBot把伤害动作加入队列这是 BaseMod 推荐的写法避免直接操作游戏内部队列导致顺序错乱。参数说明ID 建议用ModID:类名的格式避免和其他 Mod 冲突。图片路径是 jar 内路径图片尺寸一般用 250x190 或 500x380太大加载慢太小会模糊。upgradeDamage(3)表示升级后伤害加 3如果你想让升级只改描述不改数值可以只调upgradeName。3.2 卡牌描述和本地化 JSON 的字段对应关系卡牌显示名和描述不写在 Java 里而是放在本地化 JSON。下面是一个最小cards.json字段名必须和卡牌 ID 对应。{ MyFirstMod:StrikePlus: { NAME: 打击, DESCRIPTION: 造成 !D! 点伤害。 } }逻辑说明!D!是 BaseMod 的占位符会被替换成当前伤害值。如果你用了格挡可以用!B!用了魔法值可以用!M!。字段名必须和卡牌 ID 完全一致大小写敏感。参数说明JSON 文件要放在localization/zhs/目录下zhs表示简体中文。如果你还想支持英文可以再加localization/eng/。文件编码用 UTF-8不要带 BOM否则可能解析失败。3.3 把卡牌加入角色池和掉落池的两种方式只调用BaseMod.addCard只是让卡牌被注册但不一定出现在掉落里。常见做法是在receiveEditCards里同时调用BaseMod.addCard和CardLibrary.add或者通过补丁把卡牌加入对应颜色的池子。更稳妥的方式是使用 BaseMod 提供的addCardToPool方法但不同版本 API 略有差异。我一般会先只注册进游戏用控制台card命令测试能否打出确认没问题再研究掉落池。如果你希望卡牌在铁甲战士的奖励里出现可以在receivePostInitialize里调用BaseMod.addCardToPool(CardColor.RED, new StrikePlus())。注意不要重复添加否则可能出现同一张卡多次 roll 到。测试阶段可以先用控制台直接给卡减少变量。4. 遗物与本地化让自定义内容在游戏里正常显示4.1 遗物类要继承的基类和触发时机遗物比卡牌多一层“触发时机”的概念。下面这个“简单护符”在每场战斗开始时给玩家 1 点力量用来验证遗物注册和战斗钩子。public class SimpleCharm extends AbstractRelic { public static final String ID MyFirstMod:SimpleCharm; public SimpleCharm() { super(ID, simple_charm.png, RelicTier.COMMON, LandingSound.MAGICAL); } Override public String getUpdatedDescription() { return DESCRIPTIONS[0]; } Override public void atBattleStart() { addToBot(new ApplyPowerAction(AbstractDungeon.player, AbstractDungeon.player, new StrengthPower(AbstractDungeon.player, 1), 1)); } Override public AbstractRelic makeCopy() { return new SimpleCharm(); } }逻辑说明atBattleStart是遗物在战斗开始时触发的钩子addToBot保证力量在战斗开始流程里正确应用。DESCRIPTIONS[0]对应本地化 JSON 里的描述数组通常只写一条。参数说明RelicTier.COMMON决定遗物稀有度影响掉落权重。LandingSound.MAGICAL是拾取音效可以换成SOLID、CLINK等。图片路径是 jar 内路径建议 128x128。4.2 遗物本地化 JSON 和卡牌 JSON 的差异遗物本地化文件结构略有不同描述通常是一个数组因为有些遗物会根据升级或状态显示不同文本。{ MyFirstMod:SimpleCharm: { NAME: 简单护符, DESCRIPTIONS: [ 战斗开始时获得 1 点力量。 ] } }逻辑说明DESCRIPTIONS是数组getUpdatedDescription里用DESCRIPTIONS[0]取第一条。如果你有多个状态描述可以按索引取不同条目。参数说明文件名建议用relics.json放在和cards.json同一目录。字段名同样要和遗物 ID 完全一致。4.3 用控制台命令快速验证遗物是否生效进游戏后按~打开控制台输入relic MyFirstMod:SimpleCharm可以直接获得遗物。然后进入一场战斗看力量是否增加。如果遗物没出现先检查receiveEditRelics是否被调用再检查 jar 里是否包含本地化文件。控制台命令是排查注册问题最快的方式比反复重开游戏看掉落高效得多。5. 避坑与排查第一次做 Mod 最容易翻车的几个点5.1 游戏启动报“Mod 加载失败”但看不到具体类名现象ModTheSpire 列表里显示 Mod 但标红日志里只有一行“Failed to load”。原因通常是 manifest 缺少必要属性或者入口类没有SpireInitializer注解。解决检查build.gradle的 manifest 段确认Mod-The-Spire-Version存在检查入口类注解和initialize方法是否为public static。5.2 卡牌能注册但打出后游戏崩溃现象控制台能给卡但打出瞬间闪退。原因多半是use方法里直接操作了游戏内部队列或者DamageInfo的伤害类型传了 null。解决统一用addToBot包装动作damageTypeForTurn直接用父类字段不要自己 new 一个未初始化的枚举。5.3 本地化文本显示为“未找到”现象卡牌和遗物名字显示为 ID 或空白。原因通常是 JSON 路径不对、文件名大小写不匹配、或者 JSON 格式有误。解决确认文件在 jar 根目录的localization/zhs/下用jar tf查看打包结果用在线 JSON 校验工具检查格式字段名和 ID 逐字符比对。5.4 图片加载失败导致卡牌显示为空白现象卡牌能打出但图片是空白或粉色方块。原因一般是图片路径写错或者图片尺寸不是 2 的幂次导致部分渲染问题。解决确认图片放在images/cards/下路径大小写一致尺寸用 250x190 或 500x380格式用 PNG不要用 JPG。5.5 打包后 jar 里缺少依赖类现象编译通过进游戏报NoClassDefFoundError。原因通常是compileOnly的依赖没有被打进 jar而游戏环境里又没有提供。解决确认 BaseMod 和 ModTheSpire 由游戏提供其他第三方库用implementation并确保jar任务包含runtimeClasspath。如果还不行用jar tf检查 jar 内是否包含报错类。6. 进阶技巧用控制台和日志把调试速度提上来第一次跑通最小 Mod 后真正耗时间的是反复调试数值和触发时机。我自己的习惯是先把控制台命令用熟再考虑写自动化测试。控制台里card、relic、fight、kill这几个命令能覆盖大部分验证场景。比如改完卡牌伤害后不用重开游戏直接card MyFirstMod:StrikePlus拿一张打出去看伤害数字。遗物触发时机不对就用relic给遗物然后fight进战斗观察。日志方面ModTheSpire 的日志文件通常在游戏目录的mods同级或logs下。我一般会开着日志窗口用tail -f跟踪这样崩溃时能立刻看到最后一行。如果日志里出现NullPointerException但没有行号可以在build.gradle里加上-g编译参数保留调试信息方便定位。另一个实用技巧是给自定义内容加一个“调试开关”。比如在入口类里读一个系统属性如果为 true 就只注册一张测试卡减少干扰。这样在排查注册流程时不会被其他卡牌和遗物的报错淹没。等核心流程稳定后再逐步放开。最后说一个我踩过的坑不要一上来就做复杂机制比如自定义能量、自定义卡牌类型、跨角色共享遗物。这些都会引入额外补丁和兼容问题。先把一张白板卡和一个白板遗物跑通确认注册、本地化、图片、打包、加载五个环节都没问题再往上叠机制。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询