ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

杀戮尖塔Mod开发入门:从加载失败到可调试工程

杀戮尖塔Mod开发入门:从加载失败到可调试工程 简介本资源是一份面向Java初学者与游戏模组开发新手的《杀戮尖塔》MOD制作系统入门教程聚焦零基础环境搭建与核心开发流程。内容覆盖ModTheSpire、StsLib、BaseMod三大依赖库的订阅与配置原理IntelliJ IDEA集成开发环境搭建Maven项目结构初始化含pom.xml关键依赖配置以及Mod代码编写基础——包括控制台调试、变量与函数使用、类作用域、监听机制、继承关系等面向对象实践要点。资源为单文件PDF文档1.5MB结构清晰含图示说明如MOD运行架构图、分步操作指引与典型代码片段便于边学边练。目前已有2097人学习下载适合希望从环境配置起步、扎实掌握杀戮尖塔MOD开发全流程的编程入门者与独立游戏爱好者。1. 为什么刚做完第一个Mod就崩溃在启动界面杀戮尖塔Mod制作不是改个JSON就能跑通的事你照着某篇“三分钟上手”的教程把mod.info填好、classes目录扔进mods文件夹、双击启动器——结果游戏卡在黑屏三秒后弹出报错窗口“Failed to load mod: No main class found”。这不是玄学是杀戮尖塔Slay the SpireMod生态里最典型的「信任陷阱」它表面是个Java桌面游戏实则用了一套高度定制的类加载机制反射入口资源热重载链你写的每个类都得精准对齐它的运行时契约差一个注解、少一行SpireInitializer、甚至包名里多一个空格都会让整个Mod变成黑匣子。这个标题说的不是“怎么写技能”而是“怎么让游戏承认你的代码存在”——它是所有后续功能新卡牌、新角色、新事件的前提是Mod开发者的「启动验证关」。适合刚配好JDK 17、能跑通HelloWorld但没碰过Java注解和Gradle依赖管理的开发者也适合被“Mod加载失败”反复暴击、想从根上理清类加载路径的老手。别急着抄技能逻辑先让System.out.println(Mod loaded!)真正在控制台里跳出来。2. 用Gradle JDK 17搭出可编译、可调试、可复现的Mod工程骨架杀戮尖塔Mod不是靠手动复制class文件硬塞进去的野路子它依赖一套稳定的构建链源码编译 → 字节码注入 → 资源打包 → 运行时加载。这套链的起点必须是一个能被IntelliJ或VS Code识别、能断点调试、能一键生成mod.jar的工程。常见误区是直接用IDE新建Java项目——它缺了三个关键东西StS官方SDK的依赖声明、字节码增强插件用于注入SpirePatch、以及资源路径映射规则。我们用Gradle来收口。2.1 初始化Gradle工程并声明StS SDK依赖在空目录下执行gradle init --type java-application --dsl kotlin --test-framework junit-jupiter然后编辑build.gradle.kts替换全部内容为plugins { java maven-publish } repositories { mavenCentral() // StS官方Maven仓库必须否则找不到BaseMod等核心类 maven { url uri(https://maven.scryfall.com/) } } dependencies { // 核心Mod框架版本必须与你本地StS游戏版本严格匹配 implementation(com.megacrit.cardcrawl:modthespire:3.4.0) implementation(com.evacipated.cardcrawl.mod:BaseMod:5.38.0) // 游戏本体SDK关键提供Card, AbstractPlayer等类定义 implementation(com.megacrit.cardcrawl:slay-the-spire:2.0.0) // 注意此版本号需与你Steam库中StS的版本一致 // 测试依赖方便写单元测试验证逻辑 testImplementation(org.junit.jupiter:junit-jupiter:5.10.0) } java { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } // 强制使用JDK 17编译StS 2.0已弃用JDK 8 tasks.withTypeJavaCompile { options.encoding UTF-8 options.release.set(17) }提示slay-the-spire:2.0.0这个坐标不是随便写的。它对应StS 2.0正式版2023年10月发布如果你玩的是Beta测试分支或旧版StS如1.19.x必须去 StS Modding Wiki 查对应SDK版本号。用错版本编译通过但运行时报NoClassDefFoundError——这是血泪经验。2.2 创建标准Mod结构mod.info、主类、资源目录Gradle工程建好后在src/main下创建以下结构src/main/ ├── java/ │ └── com/example/myfirstmod/ # 包名必须小写不能含大写字母或下划线 │ ├── MyFirstMod.java # 主初始化类必须有SpireInitializer │ └── patches/ # 存放所有SpirePatch类非必须但推荐 ├── resources/ │ ├── mod.info # 必须游戏靠它识别Mod元数据 │ └── images/ # 卡牌图标、角色立绘等图片放这里 └── assets/ # 音效、字体等二进制资源可选mod.info内容必须严格按JSON格式注意逗号和引号{ name: My First Mod, author: dev, description: A minimal working mod for learning., version: 0.1.0, stspire_version: 2.0.0, modthespire_version: 3.4.0, requires: [basemod] }参数说明stspire_version必须与build.gradle.kts中slay-the-spire依赖版本一致modthespire_version必须与modthespire依赖版本一致requires声明依赖的其他Mod如BaseMod是几乎所有Mod的基础文件名必须是mod.info全小写无扩展名错误且必须放在resources/根目录——放错位置游戏完全无视该Mod。2.3 写出第一个能被StS识别的主类SpireInitializer是钥匙在MyFirstMod.java中写package com.example.myfirstmod; import com.evacipated.cardcrawl.mod.stslib.patches.core.AbstractCreature.HpChangeEffect; import com.megacrit.cardcrawl.core.CardCrawlGame; import com.megacrit.cardcrawl.localization.UIStrings; import com.megacrit.cardcrawl.modthespire.lib.SpireInitializer; import com.megacrit.cardcrawl.modthespire.lib.SpirePatch; import com.megacrit.cardcrawl.modthespire.lib.SpireReturn; import com.megacrit.cardcrawl.screens.mainMenu.MainMenuScreen; SpireInitializer // 关键没有这行StS启动时根本不会扫描这个类 public class MyFirstMod { public static final String MOD_ID my_first_mod; // 构造函数不重要重点是静态初始化块 static { System.out.println([MyFirstMod] Initializing...); } // 此方法会被ModTheSpire自动调用必须是public static void无参数 public static void initialize() { System.out.println([MyFirstMod] Loaded successfully!); // 这里可以注册卡牌、角色、事件等 } // 可选打一个最简单的补丁验证patch机制是否生效 SpirePatch( clz MainMenuScreen.class, method update ) public static class TestPatch { public static SpireReturnVoid Prefix(MainMenuScreen _inst) { System.out.println([MyFirstMod] Patch executed on main menu update!); return SpireReturn.Continue(); } } }逻辑说明SpireInitializer是ModTheSpire的钩子它告诉加载器“这个类是Mod入口请在启动时执行其initialize()方法”initialize()方法名是约定死的不能改成init()或start()TestPatch是可选的但它能验证你是否真的打通了字节码注入链——如果控制台出现Patch executed...说明SpirePatch已生效包名com.example.myfirstmod必须全小写且与mod.info中name字段无强关联但必须和src/main/java/下的目录结构完全一致大小写敏感。3. 把Mod编译成jar并让StS真正加载它三步验证法编译出的jar包不是普通Java程序它必须满足StS的类加载器要求主类在META-INF/MANIFEST.MF中声明、资源路径与代码路径对齐、无冗余依赖。直接gradle build会生成带依赖的fat jar但StS不需要——它只加载你的代码资源依赖由ModTheSpire统一提供。3.1 用Gradle任务生成StS兼容的精简jar在build.gradle.kts末尾添加// 专门生成StS可用的mod.jar不含依赖仅代码resources val modJar by tasks.registering(Jar::class) { archiveBaseName.set(my_first_mod) from(sourceSets.main.get().output) from(sourceSets.main.get().resources.srcDirs) duplicatesStrategy DuplicatesStrategy.EXCLUDE manifest { attributes[Main-Class] com.example.myfirstmod.MyFirstMod attributes[Mod-ID] my_first_mod // 必须与mod.info中name一致 } } // 让build任务依赖它 tasks.named(build) { dependsOn(modJar) }执行./gradlew modJar生成的jar位于build/libs/my_first_mod.jar。参数说明archiveBaseNamejar文件名建议与mod.info中name小写化后一致如My First Mod→my_first_modfrom(sourceSets.main.get().output)只打包编译后的.class文件from(sourceSets.main.get().resources.srcDirs)只打包resources/下的文件含mod.infoduplicatesStrategy DuplicatesStrategy.EXCLUDE避免mod.info被重复打包导致解析失败manifest.attributes[Mod-ID]这是StS内部识别Mod的ID必须与mod.info中name完全一致包括大小写和空格不mod.info中name是显示名Mod-ID是技术ID建议全小写无空格。3.2 将jar放入StS mods目录并配置启动参数找到你的StS安装目录Steam默认路径Steam\steamapps\common\SlayTheSpire\确认以下结构存在SlayTheSpire/ ├── mods/ # 必须存在若无则手动创建 │ └── my_first_mod.jar # 放这里 ├── ModTheSpire.jar # 必须已安装ModTheSpirev3.4.0 └── SlayTheSpire.exe # 启动器注意mods/目录必须是StS根目录下的子目录不是SlayTheSpire/mods/也不是SlayTheSpire/ModTheSpire/mods/。放错静默忽略。3.3 启动StS并验证三阶段日志启动StS前先打开命令行进入StS根目录用以下命令启动强制输出日志到控制台java -Dfile.encodingUTF-8 -jar ModTheSpire.jar观察控制台输出分三阶段验证加载阶段出现Loading mod: my_first_mod且无ERROR字样初始化阶段出现[MyFirstMod] Initializing...和[MyFirstMod] Loaded successfully!运行阶段进入主菜单后出现[MyFirstMod] Patch executed on main menu update!每帧一次。如果只看到阶段1没阶段2说明SpireInitializer未生效检查包名/类名拼写、JDK版本、mod.info位置如果阶段1、2都有但没阶段3说明SpirePatch未注入成功检查clz和method参数是否拼错MainMenuScreen.class必须导入正确包如果控制台一片空白检查java -jar ModTheSpire.jar是否真的执行了不是双击exe以及ModTheSpire.jar是否为v3.4.0。4. Mod加载失败的5个高频坑现象→原因→解决Mod开发初期80%的失败不是逻辑错误而是环境契约没对齐。以下是我在模拟项目X中踩过的、被问得最多的5个坑按发生频率排序4.1 现象控制台报java.lang.NoClassDefFoundError: com/megacrit/cardcrawl/core/CardCrawlGame原因build.gradle.kts中slay-the-spire依赖版本与本地StS游戏版本不匹配。例如你StS是v2.0.0但SDK写了2.0.1或用了1.19.0的SDK。StS启动时加载的是自己jar里的类而你的Mod引用了不同版本的类签名JVM拒绝链接。解决查你Steam库中StS的版本号右键→属性→更新→当前分支版本去 StS Modding Wiki - SDK Versions 查对应slay-the-spireMaven坐标修改build.gradle.ktsclean后重编译./gradlew clean modJar。4.2 现象StS启动后Mods列表里看不到你的Mod控制台无任何相关日志原因mod.info文件名错误如mod_info.json、MOD.INFO、mod.info.txt或放置路径错误如放在mods/子目录里或放在resources/之外。StS只认mods/根目录下、文件名为mod.info全小写无扩展名的JSON文件。解决用ls -la mods/确认文件名是mod.info不是mod_info或mod.info.json用file mods/mod.info确认是UTF-8纯文本不是UTF-8-BOM删除mods/下所有其他文件只留my_first_mod.jar和mod.info。4.3 现象SpirePatch补丁从不执行但initialize()正常打印原因SpirePatch的clz参数指向的类在StS启动时尚未加载。例如MainMenuScreen.class在CardCrawlGame初始化前就尝试patch但此时MainMenuScreen类还没被ClassLoader加载补丁注册失败。解决换更晚的时机打补丁比如CardCrawlGame的create方法SpirePatch(clz CardCrawlGame.class, method create) public static class GameCreatePatch { ... }或用SpirePatch的optional true参数容忍类不存在但不推荐掩盖问题。4.4 现象Mod能加载但自定义卡牌不显示图标控制台报TextureAtlas not found: images/cards/MyCard.png原因资源路径大小写不一致。Windows文件系统不区分大小写但StS的TextureLoader在Linux/macOS下严格区分。你代码里写images/cards/mycard.png但文件实际叫MyCard.png在Windows能跑Linux直接报错。解决统一用小写字母命名所有资源文件my_card.png在代码中引用时路径字符串必须与文件名完全一致包括大小写用Gdx.files.internal(images/cards/my_card.png).exists()在initialize()里提前校验。4.5 现象修改代码后重新编译modJarStS仍加载旧版本控制台日志还是老的println原因StS或ModTheSpire缓存了旧jar。ModTheSpire会把jar解压到临时目录如%TEMP%\ModTheSpire\mods\即使你替换了mods/下的jar它仍用缓存。解决完全退出StS和ModTheSpire删除%TEMP%\ModTheSpire\整个文件夹Windows或/tmp/ModTheSpire/macOS/Linux重启ModTheSpire它会重新解压jar。5. 用断点调试定位“Mod加载但逻辑不执行”的真实原因从黑匣子到白盒当initialize()能打印但你加的卡牌没注册、事件没触发问题往往藏在StS的异步初始化链里。这时候光看日志不够必须进代码里看变量值。我一般用三步法把黑匣子捅穿5.1 在IntelliJ中配置远程调试让StS连接你的IDEStS本身是Java进程支持JDWP协议。在build.gradle.kts的modJar任务后加// 为调试准备生成带调试信息的jar可选但推荐 tasks.withTypeJavaCompile { options.debug true options.debugOptions.debugLevel source,lines,vars }然后启动StS时加JVM参数java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -Dfile.encodingUTF-8 -jar ModTheSpire.jar在IntelliJ中Run → Edit Configurations → → Remote JVM DebugHost填localhostPort填5005点击Debug按钮等待显示Connected to the target VM。5.2 在关键节点打条件断点过滤无关调用不要在initialize()第一行打普通断点——它只停一次。要停在StS真正调用你逻辑的地方。例如你想知道“为什么新卡牌没出现在卡池”就在BaseMod的卡牌注册入口设断点打开BaseMod.javaIntelliJ会自动下载源码找到addCard()方法在方法第一行设断点右键→More→勾选Condition输入card ! null card.cardID ! null card.cardID.contains(my_first_mod)这样只有你的卡牌注册时才暂停避免被StS内置卡牌刷屏。5.3 用CardCrawlGame.logger替代System.out让日志进StS日志文件System.out.println在StS里可能被重定向或丢弃。真正的日志应该进StS/logs/latest.logimport com.megacrit.cardcrawl.core.CardCrawlGame; public static void initialize() { CardCrawlGame.logger.info([MyFirstMod] Starting initialization...); // 注册卡牌... CardCrawlGame.logger.info([MyFirstMod] Registered 3 cards.); // 如果出错 CardCrawlGame.logger.error([MyFirstMod] Failed to register card: e.getMessage(), e); }技巧latest.log文件实时追加用tail -f logs/latest.logLinux/macOS或Get-Content logs/latest.log -WaitPowerShell监控比盯着控制台高效十倍。5.4 验证Mod生命周期四个必查状态点StS Mod有明确生命周期每个点都可能失败。我在每个点加一句日志形成诊断链状态点触发时机日志示例失败意味着SpireInitializerModTheSpire扫描类时[INIT] Class scanned类未被发现包名/注解错initialize()StS启动后、游戏初始化前[INIT] Method called入口方法未执行依赖缺失receivePostInitialize()BaseMod回调游戏对象已创建[POST] BaseMod readyBaseMod未正确依赖requires漏写onPlayerTurnStart()第一次玩家回合开始[TURN] First turn补丁未生效时机太早把这四句日志写进MyFirstMod.java启动后看哪一句没出现就精准定位断点在哪一层。我做模拟项目X时曾卡在receivePostInitialize()三天——最后发现是mod.info里requires写了basemod但实际应该写BaseMod首字母大写。这种细节文档里不会写只能靠日志链一层层剥。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进