简介《杀戮尖塔Mod制作入门1》是一份面向游戏玩家与Java初学者的模组开发教程。资源以《杀戮尖塔》为例系统讲解从环境搭建到实际编码的完整流程包括订阅ModTheSpire、STSLib、BaseMod三大支持库安装IntelliJ IDEA与Maven以及通过pom.xml配置游戏路径和依赖等关键步骤。正文还对控制台输出、变量与函数、类作用域、监听函数与继承等基础概念作了入门介绍适合希望为游戏添加自定义卡牌、角色或机制的开发者参考。资源包含1个PDF文档整体大小约1.5MB内容组织紧凑。目前已有2121人学习下载教程中的模块划分和代码示例对零基础者较为友好照着搭建环境可避开常见踩坑点。通过学习读者能独立创建第一个Maven工程并理解杀戮尖塔Mod的基本结构为后续更复杂的Mod开发打下基础。1. 杀戮尖塔Mod制作入门先把环境装成能跑的样子很多卡在杀戮尖塔Mod制作第一步的人并不是代码写不出来而是环境根本没对齐。我也见过不少照着教程走到一半就翻车的游戏本体装好了创意工坊里也订阅了支持库结果打开IntelliJ IDEA建Maven工程时pom.xml里三个依赖路径全是错的一编译就是满屏红色报错或者好不容易package成功进游戏在ModTheSpire界面里却看不到自己那个mod的名字。问题几乎都出在「三件套装没装齐、路径指没指对、版本对不对得上」这三件事上。杀戮尖塔本体是Java写的mod本质上是把你自己写的Java代码打包成jar再通过ModTheSpire这个加载器塞进游戏进程里。整套开发环境的核心就三样游戏本体加支持库Basemod、ModTheSpire、stslib、一个Java IDE、一个能构建jar的Maven。这篇笔记就把这三样从下载到配置再到跑通第一个mod的完整流程写清楚适合第一次接触Java和Maven的mod新手也适合之前配过环境但一直没跑通的开发者对照排查。2. 环境准备游戏本体、支持库与Maven的版本选择2.1 先订阅三个支持库Basemod、ModTheSpire、stslib先明确一个概念杀戮尖塔的mod几乎都依赖三大支持库——modthespire、stslib和basemod。Basemod提供基础的mod注册和订阅框架ModTheSpire是mod加载器游戏启动时会先拉起它的界面让你勾选要加载的modstslib则提供更多扩展API比如自定义卡牌颜色、特殊能量机制等。三者缺一不可尤其是前两个没有它们你的mod连加载入口都找不到。安装方式很简单在Steam的杀戮尖塔商店页面进创意工坊搜索Basemod、ModTheSpire、stslib分别点订阅。订阅后Steam会自动把这些mod下载到游戏库里默认位置是steamapps/workshop/content/646570/目录下646570就是杀戮尖塔在创意工坊的应用ID。三个支持库各自对应一个子目录里面就是jar文件。这个路径后面在pom.xml里会用到最好现在就去硬盘上确认一下这几个jar存在。注意不装这三样不只是没法开发mod连运行别人做的mod都不行。所以哪怕你只想玩别人的mod这套订阅步骤也省不掉。订阅完成后再看游戏本体安装目录steamapps/common/SlayTheSpire/下会有一个desktop-1.0.jar这是游戏主jar编译mod时必须引用它。到这里开发阶段需要的三个jar就齐了一个游戏本体两个支持库。2.2 IntelliJ IDEA社区版为什么推荐这种IDE方案杀戮尖塔是典型的Java面向对象工程手写代码不是不行但调试和构建体验会很差。主流的Java IDE是Eclipse和IntelliJ IDEA下文简称IJ我推荐用IJ社区版免费对Maven的支持也更顺滑。安装本身没有坑从官网下载社区版安装包一路Next装完。需要特别提醒的是如果你电脑上已经装了其他版本的Java装完IJ后最好确认一下默认JDK版本。杀戮尖塔本体基于Java 8编译开发mod时建议把编译器级别固定在1.8这个后面在pom.xml里会用到。很多报错「不支持的class版本」的人都是因为本地默认JDK太新编译出来的字节码游戏加载不了。2.3 Maven环境变量MAVEN_HOME和Path到底怎么配Maven是Java项目的构建工具负责把代码和依赖打包成jar。它的配置步骤在Windows下其实只有两步解压、配环境变量。先去Maven官网下载最新版解压到一个路径里比如E:\Maven\apache-maven-3.3.9。注意目录结构确保E:\Maven\apache-maven-3.3.9\bin这个路径真实存在后面环境变量指向的就是这一级。然后打开系统属性面板计算机或此电脑右键→属性→高级系统设置→环境变量做两件事# 1. 新建系统变量 MAVEN_HOME E:\Maven\apache-maven-3.3.9 # 2. 编辑系统变量 Path新增一行 %MAVEN_HOME%\bin配完环境变量后必须新开一个命令行窗口旧窗口不会刷新环境变量输入mvn -v验证。能看到Maven版本号、Java版本这些信息才算配置成功。这里有两个很容易踩的细节第一MAVEN_HOME的值不要以反斜杠结尾E:\Maven\apache-maven-3.3.9\这种写法偶尔会导致后续命令拼接路径时出现双反斜杠第二Path中新增的是%MAVEN_HOME%\bin不是在Path末尾直接粘E:\Maven\apache-maven-3.3.9\bin虽然两种写法效果一样但用变量的好处是以后升级Maven只改MAVEN_HOME一处就行。如果你用IJ做开发其实IJ自带了Maven插件不配置系统环境变量也能跑构建。但命令行Maven还是要配好因为pom.xml出问题时用mvn clean package在命令行复现报错信息比IDEA日志更直白排查效率高很多。3. 构建首个Mod工程pom.xml与ModTheSpire.json的完整配置3.1 新建Maven项目目录结构与test文件夹的作用以IJ为例打开IDEA后选New Project左侧选Maven注意Create from archetype那个勾选框不要选留空直接生成。填项目名和存放地址我习惯用tutorial作为示例工程名groupId写成org.example。这个groupId只是个组织标识不影响最终jar内容但artifact也就是artifactId会直接影响输出jar名后面finalName还会再覆盖一次。新建完成后左侧项目结构里会出现几个关键目录.idea是IJ的辅助配置与mod本身无关不用管src/main/java是你写代码的地方src/main/resources放资源文件比如卡面图片、特效贴图以及后面要写的ModTheSpire.jsonsrc/test/java是Maven自动生成的测试目录初学者一般用不到但不要删Maven构建流程会默认扫描它。很多第一次接触Maven的人看到src/test目录以为是自己建错了其实这是Maven的标准结构删了反而可能在package时报错。3.2 pom.xml核心steam.path、dependencies与system scopepom.xml是Maven工程的配置文件决定这个项目怎么编译、依赖哪些jar、输出到哪里。打开项目根目录下的pom.xml按下面的内容改?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdorg.example/groupId artifactIdtutorial/artifactId version0.1/version packagingjar/packaging nametutorialMod/name descriptiontutorial/description properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding steam.pathE:\SteamLibrary\steamapps/steam.path /properties dependencies dependency groupIdbin/groupId artifactIdBaseMod/artifactId scopesystem/scope version1.0/version systemPath${steam.path}/workshop/content/646570/1605833019/BaseMod.jar/systemPath /dependency dependency groupIdbin/groupId artifactIdSlayTheSpire/artifactId scopesystem/scope version1.0/version systemPath${steam.path}/common/SlayTheSpire/desktop-1.0.jar/systemPath /dependency dependency groupIdbin/groupId artifactIdModTheSpire/artifactId scopesystem/scope version1.0/version systemPath${steam.path}/workshop/content/646570/1605060445/ModTheSpire.jar/systemPath /dependency /dependencies /project这段配置里有几个关键点需要理解steam.path属性指向的是steamapps目录不是你SteamLibrary根目录。示例中我填的是E:\SteamLibrary\steamapps如果你的游戏库在其他盘或路径只改这一处。后面所有systemPath都是基于这个属性拼接的所以这个值错了三个依赖会同时失效。三个dependency都用了scopesystem意思是「这个jar不走Maven中央仓库直接引用本地绝对路径」。这是mod开发最常见的做法因为游戏本体jar和mod支持库谁也不会发布到Maven仓库里只能手动指路径。systemPath里的数字目录1605833019、1605060445是Steam创意工坊分配给具体订阅项的ID如果你订阅Basemod和ModTheSpire的版本不同这个数字可能不一样去实际目录看一眼确认即可。改完pom.xml后IDEA右下角会提示Maven配置发生更改点Import Changes让IDEA重新加载依赖。这一步别忽略不加载的话后面写代码时import会全部标红。3.3 装配插件maven-antrun-plugin自动拷贝jar到mods目录只有依赖配置还不够还需要告诉Maven两件事用哪个Java版本编译、编译完jar之后放哪里。继续在pom.xml里补上build节点build finalNametutorialMod/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.7.0/version configuration source1.8/source target1.8/target /configuration /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-antrun-plugin/artifactId version1.8/version executions execution phasepackage/phase configuration target copy filetarget/tutorialMod.jar tofile${steam.path}/common/SlayTheSpire/mods/tutorialMod.jar / /target /configuration goals goalrun/goal /goals /execution /executions /plugin /plugins /buildfinalName决定输出jar的文件名。如果不设置Maven默认会输出成tutorial-0.1.jar这种带版本号的格式finalName可以强制输出为tutorialMod.jar和后面antrun插件拷贝时定义的文件名保持一致。maven-compiler-plugin里的source和target都设成1.8这保证编译产物能被游戏本体正常加载如果你机器上的JDK是17或更高这两个值帮助你编译出1.8字节码但前提是代码里别用太新的语法特性。maven-antrun-plugin的作用是在package阶段完成后执行一段额外的文件操作。我用的是copy任务把刚构建出来的target/tutorialMod.jar直接复制到游戏的mods目录。杀戮尖塔的mod加载器只扫描SlayTheSpire/mods/目录所以这一步其实是变相帮你省了「手动把jar丢进mods文件夹」这个操作。注意拷贝目标路径也是基于${steam.path}拼接的如果前面steam.path配错了会出现构建成功但mods目录里根本没有文件的诡异情况。3.4 ModTheSpire.jsonmodid、dependencies与编码jar只是代码的载体ModTheSpire加载一个mod时还要读取一个标识文件——ModTheSpire.json它告诉加载器这个mod叫什么、作者是谁、依赖什么。在src/main/resources目录下新建文件命名为ModTheSpire.json区分大小写别写错内容如下{ modid: tutorial, name: tutorialMod, author_list: [A, B], description: 新手教程mod, version: 0.0.1, sts_version: 03-29-2018, mts_version: 2.6.0, dependencies: [basemod] }各字段含义modid是mod的唯一标识全小写不能有空格name是显示在ModTheSpire界面里的名字author_list是作者数组可以写多人description是简介支持中文version是mod版本号sts_version和mts_version是游戏本体和ModTheSpire的版本号用于兼容性校验dependencies声明依赖的mod这里写basemod表示本mod依赖Basemod就必须在BaseMod之后加载。JSON格式的坑主要在大括号和方括号的配对、逗号要不要加。花括号{}代表对象方括号[]代表数组author_list和dependencies都是数组元素之间用逗号分隔但最后一个元素后面不能有逗号。养成写完JSON就检查一遍的习惯这个文件如果有语法错误在游戏里表现就是mod直接消失不会报错。4. Mod代码骨架从SpireInitializer到监听函数4.1 创建Package和类SpireInitializer的初始化流程现在开始写代码。在src/main/java下新建一个package命名用mod的小写名称比如tutorial。package相当于文件夹作用是组织类文件避免类名冲突。然后在包里新建一个Java类类名首字母大写这里用经典的Tutorialpackage tutorial; import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer; SpireInitializer public class Tutorial { public Tutorial() { } public static void initialize() { new Tutorial(); } }这段代码是每个mod的固定入口。SpireInitializer是ModTheSpire提供的注解告诉它「这个类里有初始化方法加载mod时请调用」。initialize()必须是public static void方法内部调用构造函数创建一个Tutorial对象这个对象会作为mod实例被ModTheSpire持有。很多人不理解为什么Module初始化要拐这么个弯不能直接在initialize里写功能吗可以但后面做监听时要在构造函数里执行BaseMod.subscribe(this)完成事件订阅所以构造函数是必留的。4.2 控制台输出与基础变量调试的第一个工具Mod开发里最重要的调试手段就是往ModTheSpire的log控制台打输出。游戏加载mod时或运行中ModTheSpire会有一个独立的log窗口系统的System.out输出都会打到那里。在initialize方法里加一行System.out.print(hello world!);System.out.print是Java最基础的输出语句括号里的内容会原样打印到控制台。注意字符串要用半角双引号包裹语句以分号结尾。print不会自动换行如果想换行要用println或者手动加\n转义字符。常见的组合写法是System.out.print(hello world! 5*630); System.out.print(5 * 6 30); System.out.print(end of line!\n22 4); // 这里号表示字符串拼接第一个print输出固定文本第二个print直接输出表达式计算结果60第三个print演示转义和字符串拼接。这里的在字符串上下文中是连接符不是数学加法\n是换行符它不代表字符n。这套输出方法在验证mod是否加载成功、调试数值计算时非常实用。变量方面mod开发最常用的是四种int整型、float浮点型、boolean布尔型、String字符串型。声明变量由四部分组成——属性、类型、名称、初始化值public int number; private float dot 2.4f; public static boolean judge true; public final String HELLO Hello world!;public和private控制访问范围static表示这个变量属于类而不是某个对象所有对象共享final表示常量赋值后不能修改。float的数值后面要加f后缀不加会默认为double类型。String的初始化用双引号常量命名全部大写并用下划线分隔。4.3 监听函数BaseMod.subscribe与接口实现mod的核心能力是响应游戏内的事件。Basemod提供了一套订阅机制游戏触发某个动作时通知所有订阅了该事件的对象。具体做法是让你的类实现一个接口并在构造函数里调BaseMod.subscribe(this)。下面是一个实战例子监听「玩家抽牌」事件并把抽到的牌名打到log里package tutorial; import basemod.BaseMod; import basemod.interfaces.PostDrawSubscriber; import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer; import com.megacrit.cardcrawl.cards.AbstractCard; SpireInitializer public class Tutorial implements PostDrawSubscriber { public Tutorial() { BaseMod.subscribe(this); } public static void initialize() { new Tutorial(); } Override public void receivePostDraw(AbstractCard abstractCard) { System.out.println(abstractCard.name); } }接口名PostDrawSubscriber以Subscriber结尾对应游戏里「抽牌后」这个动作。实现它必须重写receivePostDraw方法方法的形参AbstractCard abstractCard就是刚抽到的那张牌对象通过abstractCard.name能拿到它的显示名称。Override注解表示这是重写父接口的方法写错方法名时IDE会直接报错相当于帮你检查拼写。调试时这个方法很直观——你抽一张牌log就多一行牌名。再看一个监听玩家受伤的例子package tutorial; import basemod.BaseMod; import basemod.interfaces.OnPlayerDamagedSubscriber; import com.evacipated.cardcrawl.modthespire.lib.SpireInitializer; import com.megacrit.cardcrawl.cards.DamageInfo; SpireInitializer public class Tutorial implements OnPlayerDamagedSubscriber { public Tutorial() { BaseMod.subscribe(this); } public static void initialize() { new Tutorial(); } Override public int receiveOnPlayerDamaged(int damage, DamageInfo damageInfo) { return damage; } }receiveOnPlayerDamaged会在玩家受到伤害时被调用形参damage是伤害数值damageInfo携带伤害来源信息。注意这个方法有返回值int意味着你可以在这里修改受到的伤害——比如实现减伤效果把damage改成damage / 2再返回。mod里修改游戏数值的玩法本质上都是靠这类带返回值的监听函数在游戏计算路径中间插入一段自己的逻辑。4.4 引用与继承如何理解对象传递和类继承Java中的一个经典困惑为什么调用swap(a, b)交换函数后原来的a、b数值没变原因在于基本类型按值传递函数里拿到的只是副本。这里不展开理论直接给结论int、float、boolean这类基本变量进函数是复制一份在函数里怎么改都不影响外面但对象不一样比如前面监听函数里abstractCard.name NULL这样改外面那张牌的名字真的会变。原因在对象声明方式上A a new A();new A()真正创建了对象a不是对象本身而是对象的引用。引用就像替身或快捷方式传给函数时复制的是「指向同一个对象的入口」所以函数里通过引用修改的字段原对象也变了。理解这一点就能解释为什么监听函数能改游戏对象的状态而普通的int参数传进函数里却改动无效。Java提供了基本类型的封装类比如Integer对应int、Boolean对应boolean把基本变量也包装成对象这样在泛型和某些框架场景里能用统一的引用方式处理。继承则是mod开发中处理「多个相似事物公共逻辑」的手段。机器人职业有多种能量球它们都有「被激发」「每回合执行操作」这些共性但如果每个球类都单独写一遍代码就冗余了。正确做法是先建一个父类描述共性然后让具体球类继承它子类各自补充差异。杀戮尖塔自带的大量卡牌和角色逻辑也用同样的设计思路写mod时经常会继承AbstractCard、AbstractPower这些类来定义自己的新卡和新能力掌握继承才能读懂游戏本体源码。5. 常见问题与排查加载失败、编译报错和编码坑的修复5.1 jar没进mods目录steam.path的路径错位现象Maven构建成功控制台一路BUILD SUCCESS但打开游戏mods目录里面什么都没有或者ModTheSpire界面看不到自己的mod。原因绝大多数情况下是steam.path属性配错了。注意steam.path要指向steamapps这一层而不是SteamLibrary根目录。很多人抄到配pom时将steam.path写成了E:\SteamLibrary于是antrun的copy目标拼出来是E:\SteamLibrary\common\SlayTheSpire\mods这个路径在磁盘上根本不存在正确路径是E:\SteamLibrary\steamapps\common\SlayTheSpire\modsMaven的copy任务不会因为目标目录不存在而报错它只是默默复制失败。解决先手动打开资源管理器确认steamapps\common\SlayTheSpire\mods目录是否存在不存在就新建一个。然后回pom.xml核对steam.path值重跑mvn clean package构建完成后用文件夹直接检查SlayTheSpire\mods\tutorialMod.jar的最后修改时间。这个排查顺序比在游戏里反复开关效率高得多。5.2 ModTheSpire界面看不见modjson字段与依赖声明现象jar文件确实在mods目录里但打开ModTheSpire的mod选择界面列表里就是没有你的mod。原因ModTheSpire加载时会对jar内的ModTheSpire.json做严格校验。最常见的是modid重复——创意工坊或本地已有的其他mod占用了相同modid其次是dependencies声明有问题比如你声明了[basemod]但界面里没勾选Basemod加载器会认为依赖缺失而跳过再有就是JSON格式错误多一个逗号或少一个引号都会让整个mod被静默丢弃。解决在ModTheSpire启动时观察log窗口加载失败的mod会把具体原因打印出来。改modid为全局唯一值检查JSON语法兼容性然后确认游戏启动时的mod列表里Basemod和你的mod前的勾选框都打上了。注意modid不能带大写字母和连字符tutorial_mod这种写法也可能触发校验问题。5.3 编译找不到游戏类依赖jar的systemPath失效现象IDEA里写import com.megacrit.cardcrawl.cards.AbstractCard;后整行标红报「程序包com.megacrit.cardcrawl不存在」或者Maven构建时报错无法从systemPath读取jar。原因pom.xml里三个jar的systemPath指向的路径不存在或者路径中包含非ASCII字符、空格未正确转义。还有一种情况steam.path属性中如果使用反斜杠\在XML解析时反斜杠本身没有转义问题但如果你在Windows路径里用了中文目录名Maven的路径处理偶尔会出幺蛾子。解决先去文件管理器逐层核对systemPath拼出来的完整路径确认jar真实存在。如果IDEA标红但路径没问题点一下Maven工具窗口的刷新按钮Reload All Maven Projects让IDEA重新读取依赖。我的习惯是用属性拼接路径时先展开写一遍完整的绝对路径确认无误后再改回${steam.path}格式这样能快速区分是属性值错还是拼写错。5.4 中文乱码与版本不匹配编码、mts_version的坑现象游戏内mod名称或卡牌描述显示成乱码或者mod加载时提示sts_version/mts_version不匹配。原因乱码是因为保存编码不一致。pom.xml里虽然设置了project.build.sourceEncodingUTF-8但你编辑ModTheSpire.json或Java文件时IDE右下角显示的编码如果是GBK保存后就会以GBK编码写入日志报版本不匹配则是因为json里的mts_version和当前装的ModTheSpire实际版本号对不上——教程示例里的2.6.0是旧版本你现在装的很可能是更新版本。解决全工程强制设为UTF-8。在IDEA设置里把File Encodings的三项Global Encoding、Project Encoding、Properties Files都改成UTF-8已有文件如果已经乱码只能重新编辑一遍。版本号则打开ModTheSpire的jar包里的META-INF/MANIFEST.MF或用压缩工具查看版本信息把json里的mts_version改成实际版本。一个非常实用的小技巧把空的sts_version和mts_version先清掉再填如果加载还是报不匹配就去ModTheSpire目录翻它的config文件等号后面的值就是加载器期望的版本写法的参考。6. 装完验证跑通看log、勾mod、跑一次package全链路环境配好、工程搭好后最后一步是把整个链路真正跑通一次而验证方式不能只是「构建成功了」——构建成功只代表Maven配置对了不代表游戏能加载你的mod。我的验证流程固定按以下五步走每一步出问题都能定位到具体环节。第一步命令行验证Maven环境新开一个终端执行mvn clean package注意这里有两个动作clean负责清掉上次构建的残留避免旧jar混进来。第二步检查构建产物和拷贝结果看target目录下有没有tutorialMod.jar再看游戏目录SlayTheSpire\mods\tutorialMod.jar是否同步生成。这一步过了说明pom.xml里的依赖和插件链路没问题。第三步用带ModTheSpire的方式启动游戏。在Steam里对杀戮尖塔设置启动项为--with-mod-the-spire或者直接运行ModTheSpire的启动器。启动后ModTheSpire会先弹出一个mod选择界面这个界面就是加载器扫描所有可用mod的展示窗口。在这里勾选Basemod和你的tutorialMod再点Play进入游戏。第四步观察ModTheSpire的log窗口。如果一切正常你会看到模组初始化时打印的hello world!以及类似2ms这样一行「mod加载耗时」输出。这一步是验证mod代码真正被游戏进程执行了的铁证。如果没看到输出回去检查SpireInitializer注解是否拼对、类是不是public。第五步进游戏里做功能验证。如果只是验证环境跑通可以在战斗中抽几张牌看log窗口里是否出现卡牌名字——这是最直观的监听函数生效证明。如果你跟着作业走到了监听函数章节可以把玩家受伤监听和战斗结束监听组合起来在receivePostBattle里打印本场战斗累计受到的伤害总和这个功能会把环境验证和代码能力一起考了。从那以后我每次搭新的mod工程都强制自己先跑一遍这套「构建→查目录→看log→游戏内触发事件」的全链路确认每个环节都通才写正式功能。环境问题当场解决后面的开发会顺很多如果log里有任何一行报错也一定当场处理掉再继续因为很多玄学问题拖到后面排查会更花时间。希望这篇环境落地的笔记能帮到你让第一个mod尽早跑起来。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?