☰
废墟图书馆Mod开发实战:BepInEx实现自动掷骰与战斗动画优化
2026/9/29 18:55:59 网站建设 项目流程

很多从《废墟图书馆》入门模组开发的玩家,最初的需求往往不是做一套完整的规则重构,而是解决“战斗演出太拖沓”“每次拼点都要等动画”“伤害数字不够直观”这类体验问题。网上关于这类自制 mod 的教程非常零散,要么只讲安装现成插件,要么直接给一个闭源成品,几乎没有系统性拆解“从原理到实现”的教程。所以这篇文章会围绕自制 mod 的三个高频诉求展开:自动掷骰 EX、拼点动画优化、伤害显示优化。我会把 mod 开发的环境搭建、核心原理、典型代码结构和排错思路一起讲清楚,适合第一次写 BepInEx 插件的新手,也适合想快速定位问题的高阶玩家。

需要说明的是,本文属于技术教程,只适用于你已经合法购买了《废墟图书馆》,并在官方允许的范围内进行 mod 开发。游戏版本、mod 框架版本都会影响具体代码,所有示例代码都需要你结合自己的实际环境做调整。

1. 背景与核心概念:我们到底在优化什么

1.1 为什么会出现“自动掷骰 EX”这类 mod

《废墟图书馆》的核心战斗是“书页对抗”,也就是双方书页的骰子进行拼点,点数大的一方获得这次拼点的胜利,并触发对应的命中效果。原版流程中,玩家需要在多个可用书页之间选择,然后等待骰子动画、拼点动画、伤害结算动画依次播放。一两场战斗还好,但到了后期刷核心书页、挑战楼层、反复尝试特定异想体时,这些不可跳过的演出会明显拖慢节奏。

“自动掷骰 EX”这类 mod 解决的是操作与节奏问题。它不是单纯做一个“自动战斗”脚本,而是在保留策略深度的前提下,把重复、机械的选择和等待简化掉。常见的实现思路包括:

  • 自动选择最优书页:按当前拼点目标、速度骰子、卡组构成自动选牌。
  • 自动判定掷骰结果:跳过手动点击“掷骰”的等待,直接展示最终点数。
  • 可配置开关:允许玩家只在普通战斗开启,或者在特定楼层关闭。

1.2 拼点动画优化到底优化的是什么

拼点动画优化并不是“删除动画”。动画系统本身承担着战斗反馈、特效表现和抽帧演出的任务,全部砍掉会让游戏失去打击感。合理的优化思路是:

  • 降低动画播放的时间,而不是直接跳过。
  • 跳过已经重复看过多次的过渡镜头。
  • 合并多段战斗结算,减少中间的停顿帧。
  • 在“高速模式”下保留关键命中表现,去掉冗余的特写。

这类 mod 的本质是调整游戏内部的动画状态机、延时逻辑和镜头控制参数。

1.3 伤害显示优化是锦上添花还是刚需

原版伤害数字通常飘在目标单位头顶,数值小、存在时间短,部分 buff/debuff 的增伤结果并不能一眼看出来。伤害显示优化 mod 一般会做这几件事:

  • 放大伤害数字字体,增加对比度。
  • 区分物理伤害、混乱伤害、火焰伤害等不同颜色。
  • 将多次伤害汇总为单次数值,或者按书页分组显示。
  • 延长数字停留时间,减少“来不及看”的窘境。

严格来说,伤害显示优化并不改变游戏数值,它改变的是信息呈现效率。对攻略制作、直播、极限单楼层挑战的玩家来说,这个优化甚至比自动掷骰更实用。

2. 开发环境准备:搭建一个可控的 mod 工程

2.1 选用什么模组框架

《废墟图书馆》使用 Unity 开发,多数社区 mod 依赖 BepInEx 插件框架。BepInEx 负责把 C# 插件注入到游戏进程,并提供日志、配置、事件等基础设施。游戏本身的战斗逻辑通过 Harmony 补丁进行修改。

在开始之前,你需要先确认以下信息:

  • 游戏本体版本,不同版本对应的程序集结构可能不同。
  • BepInEx 版本,建议优先使用社区常见稳定版,不要盲目追求最新版。
  • Harmony 版本,BepInEx 5 内部集成了 Harmony,BepInEx 6 之后通常需要单独引入 HarmonyLib 依赖。

本文不写死具体版本号,因为版本迭代太快。你的目标不是“用最新版本”,而是“让 mod 在当前游戏版本上能稳定运行”。

2.2 安装 BepInEx

安装 BepInEx 的基本流程是:

  1. 将 BepInEx 压缩包中的BepInEx文件夹、doorstop_config.ini、winhttp.dll等文件解压到游戏根目录。
  2. 启动一次游戏,让 BepInEx 生成LogOutput.log和plugins文件夹。
  3. 关闭游戏,确认BepInEx/plugins目录存在。
  4. 将你的 mod 程序集(.dll)放入BepInEx/plugins目录,再次启动游戏。

如果你使用 mod 管理器,可以跳过手动安装,但建议你还是了解手动流程,便于排查问题。

2.3 IDE 与 .NET 环境

C# mod 开发推荐使用 Visual Studio 或 Rider。示例项目使用 .NET Framework 4.7.2 或更高版本,具体目标框架取决于 BepInEx 的运行时。记住一点:Unity 游戏常驻 Mono 或 IL2CPP 运行时,不同游戏会有差异,《废墟图书馆》这类基于 Mono 的 Unity 游戏通常可以直接加载 .NET Framework 程序集。

如果你不想管理复杂工程,也可以在 BepInEx 目录下直接建立一个简单的csproj,引用游戏程序集和 BepInEx 核心 DLL。下面是一个工程骨架示例:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net472</TargetFramework> <AssemblyName>AutoDiceEX</AssemblyName> <LangVersion>7.3</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="0Harmony"> <HintPath>你的BepInEx路径\BepInEx\core\0Harmony.dll</HintPath> <Private>false</Private> </Reference> <Reference Include="BepInEx"> <HintPath>你的BepInEx路径\BepInEx\core\BepInEx.dll</HintPath> <Private>false</Private> </Reference> <Reference Include="Assembly-CSharp"> <HintPath>你的游戏路径\LibraryOfRuina_Data\Managed\Assembly-CSharp.dll</HintPath> <Private>false</Private> </Reference> <Reference Include="UnityEngine"> <HintPath>你的游戏路径\LibraryOfRuina_Data\Managed\UnityEngine.dll</HintPath> <Private>false</Private> </Reference> </ItemGroup> </Project>

请把HintPath替换为你本机的真实路径。引用游戏程序集时不建议把 DLL 拷贝进输出目录,避免补丁加载时出现类型冲突。

2.4 获取反编译参考

编写 Harmony 补丁之前,你必须知道要 Patch 哪个类的哪个方法。社区通行的做法是使用 dnSpy 或 ILSpy 打开Assembly-CSharp.dll,找到战斗管理类、拼点判定类、伤害结算类、UI 数字类。这个过程并不复杂,但比较耗时。

一个建议是:先不要急着写代码,把游戏关键流程的调用关系用文档记录下来。比如“点击书页→确认使用→播放动画→拼点判定→伤害飘字”每个阶段对应哪些方法。有了这份记录,你的补丁才会稳定,而不是靠猜测写函数名。

3. 核心原理拆解:三个功能背后的关键技术

3.1 自动掷骰 EX 的实现思路

自动掷骰 mod 通常要 Hook 两个环节:选牌阶段和掷骰阶段。

  • 选牌阶段:当玩家速度骰子激活时,原版弹出手牌选择。自动模式可以通过读取当前敌人行动,计算出每个可出牌页的期望收益,自动选择最优项。
  • 掷骰阶段:原版在确认出牌后还需要点击确认,或者等待计时。自动模式可以直接跳过等待,使用随机数生成器模拟掷骰结果。

从补丁技术上看,你需要在合适的方法后执行你自己的逻辑。这里有一个通用伪代码示例:

using HarmonyLib; // 这里的 BattleManager 与 ChooseCard 方法名是示例,请以反编译结果为准 [HarmonyPatch(typeof(BattleManager), nameof(BattleManager.ChooseCard))] static class AutoChoosePatch { static bool Prefix(BattleManager __instance, ref int selectedIndex) { // 如果本回合使用自动模式,直接根据计算逻辑覆盖 selectedIndex if (AutoDiceConfig.Instance.AutoChoose) { selectedIndex = AutoDecisionHelper.GetBestCardIndex(); // 返回 false 表示跳过原方法大部分逻辑,但请注意原方法是否还有必要执行剩余步骤 // 某些情况下你需要返回 true 并修改参数,让原方法继续执行 return false; } return true; } }

这里要特别提醒:不是所有方法都适合用Prefix直接返回 false。如果原方法内部还会更新 UI 状态、切换动画、追加 buff,你一刀切跳过会导致界面状态错乱。更安全的做法是“修改参数,让原方法继续执行”,只在极少场合下完全跳过原逻辑。

自动掷骰 EX 常用的功能配置包括:

  • 删除确认弹窗。
  • 自动选择点数期望最高的书页。
  • 自动跳过掷骰前摇。
  • 在指定楼层禁用。
  • 使用快捷键临时开启/关闭。

新手最容易踩的坑是“期望最高”不一定等于“实际最优”,因为《废墟图书馆》有“守御骰”“打击骰”“招架骰”之间的克制关系,以及书页自带特效。所以自动选择模块最好做成插件式,允许玩家配置权重规则。

3.2 拼点动画优化的底层逻辑

拼点动画可以优化到什么程度,取决于你对游戏动画流程的理解。原版战斗中的拼点表现通常包括:

  1. 双方骰子放大特写。
  2. 骰子碰撞特效。
  3. 胜负判定后的受击方硬直。
  4. 伤害数字弹出。
  5. 短暂停顿等待玩家阅读。

优化有两个方向:一是缩短每个环节的延时,二是跳过重复度高的过渡。

在 Harmony 补丁中,你可以拦截控制动画等待的方法。比如有一个方法在拼点结束后会让战斗暂停 0.3 秒,那么你可以通过补丁把这个时间改为 0.1 秒,或者直接修改协程中的 WaitForSeconds 参数。

这里有一个通用示例,用于修改延时:

[HarmonyPatch(typeof(BattleManager), nameof(BattleManager.WaitForDiceAnim))] static class AnimSpeedPatch { static void Postfix(BattleManager __instance) { if (AnimConfig.Instance.FastPose) { // 通过 反射 或 Traverse 修改内部 waitingTime 字段 Traverse traverse = Traverse.Create(__instance); traverse.Field("waitingTime").SetValue(0.05f); } } }

这里的字段名只是举例。真实的字段名称、类型、上级类都可能不同,必须结合反编译结果调整。

如果动画优化涉及跳过特写镜头,你还需要处理“镜头重新对齐”的问题。直接 Skip 动画可能导致镜头停留位置不正确,下一回合战斗 UI 错位。稳妥方案是把动画播放速度提高,而不是跳过,这样镜头状态仍由原版协程正常推进。

3.3 伤害显示优化的实现路径

伤害显示优化往往不是 Patch 战斗逻辑,而是 Patch UI 组件。你需要找到伤害数字生成的类,比如DamageNumberPrinter、BattleDamageUI之类的命名。

常见的修改方式有:

  • 找到生成数字的Text或TMP_Text组件。
  • 修改字号、颜色、对齐方式。
  • 延迟销毁时间。
  • 把多个伤害数字合并到同一个父节点。

示例:

using TMPro; [HarmonyPatch(typeof(DamageNumberUI), nameof(DamageNumberUI.ShowDamage))] static class DamageDisplayPatch { static void Postfix(DamageNumberUI __instance, TextMeshProUGUI ___damageText) { if (DamageDisplayConfig.Instance.BigNumber) { ___damageText.fontSize = 48; } } }

这里的TextMeshProUGUI取决于游戏是否使用 TextMeshPro。如果不确定,可以直接反射查找Text组件,并修改其fontSize属性。UI 修改的难点在于,同一个数字可能被多个系统复用,只改参数不改布局,容易和原来的 UI 锚点冲突。

一些进阶 mod 会把伤害数字做成分组聚合,即在多次伤害结算完成后,将本轮累计值显示在一个大数字上。实现聚合时要注意“什么时机聚合”,过早聚合会挡住敌人身上的持续伤害,过晚聚合又会让玩家觉得卡顿。通常是在战斗行动结束事件中刷新聚合文本。

4. 完整实战案例:做一个“自动掷骰 EX + 拼点动画优化 + 伤害显示优化”三合一 Mod

为了让说明更具体,我们在这个章节设计一个练习项目:一个包含三个功能模块的插件,插件名为CombatQualityOfLife。我们不会一次性堆出完整成品代码,而是按照模块逐步实现,重点展示结构。

4.1 创建项目结构

推荐的项目目录如下:

CombatQualityOfLife/ ├── CombatQualityOfLife.csproj ├── Plugin.cs ├── Config/ │ ├── AutoDiceConfig.cs │ ├── AnimConfig.cs │ └── DamageDisplayConfig.cs ├── Patches/ │ ├── AutoDicePatch.cs │ ├── AnimSpeedPatch.cs │ └── DamageDisplayPatch.cs └── Helpers/ ├── AutoDecisionHelper.cs └── DamageAggregator.cs

这个结构适合中小型 mod。如果后续功能膨胀,可以再拆成独立项目。

4.2 编写插件入口

插件入口继承BaseUnityPlugin,在Awake中初始化配置和 Harmony 补丁。注意把Harmony.PatchAll()放在配置初始化之后,避免补丁执行时读取默认空配置。

// 文件路径:CombatQualityOfLife/Plugin.cs using BepInEx; using BepInEx.Logging; using HarmonyLib; namespace CombatQualityOfLife { [BepInPlugin("com.example.combatqol", "Combat Quality Of Life", "1.0.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Log; private void Awake() { Log = Logger; Log.LogInfo("CombatQualityOfLife 加载中..."); // 初始化三个功能配置 AutoDiceConfig.Instance.Init(Config); AnimConfig.Instance.Init(Config); DamageDisplayConfig.Instance.Init(Config); // 创建 Harmony 并 Patch 所有打了标记的方法 var harmony = new Harmony("com.example.combatqol"); harmony.PatchAll(); Log.LogInfo("CombatQualityOfLife 加载完成"); } } }

Config是 BepInEx 提供的配置入口,AutoDiceConfig等类会从中读取玩家设置。不要嫌配置代码麻烦,后期调整开关全靠它们。

4.3 配置模块

BepInEx 配置类支持字符串、整数、浮点、布尔等类型。每个配置项都可以设置说明文本和取值范围。

// 文件路径:CombatQualityOfLife/Config/AutoDiceConfig.cs using BepInEx.Configuration; namespace CombatQualityOfLife { public class AutoDiceConfig { public static AutoDiceConfig Instance { get; } = new AutoDiceConfig(); public ConfigEntry<bool> AutoChoose { get; private set; } public ConfigEntry<bool> SkipConfirm { get; private set; } public ConfigEntry<KeyboardShortcut> ToggleKey { get; private set; } public void Init(ConfigFile config) { AutoChoose = config.Bind( "AutoDice", "AutoChoose", true, "是否自动选择最优书页"); SkipConfirm = config.Bind( "AutoDice", "SkipConfirm", true, "是否跳过出牌确认动画"); ToggleKey = config.Bind( "AutoDice", "ToggleKey", new KeyboardShortcut(UnityEngine.KeyCode.F8), "自动掷骰快速开关快捷键"); } } }

用ConfigEntry<KeyboardShortcut>可以很方便地绑定快捷键。配置项最好分区块管理,比如AutoDice、Anim、DamageDisplay,不要让所有配置挤在同一段。

4.4 自动掷骰补丁

自动掷骰的核心是“选牌辅助”。为了避免过度干预原版逻辑,这里采用“修改目标参数”的方式:在玩家选牌完成后,如果该回合没有手动覆盖,则自动将选中的卡替换为期望值更高的选择。

// 文件路径:CombatQualityOfLife/Patches/AutoDicePatch.cs using HarmonyLib; namespace CombatQualityOfLife { // BattleCardController 只是示例类,请替换为反编译到的真实类名 [HarmonyPatch(typeof(BattleCardController), "OnCardSelected")] static class AutoDicePatch { static void Prefix(BattleCardController __instance, ref int cardId) { if (!AutoDiceConfig.Instance.AutoChoose.Value) { return; } // 调用决策帮助类,根据当前战场状态计算更好的卡牌 ID int betterCard = AutoDecisionHelper.GetBetterCard(cardId); if (betterCard != cardId) { Plugin.Log.LogInfo($"自动替换书页:{cardId} -> {betterCard}"); cardId = betterCard; } } } }

这段代码的意图是只修改传入的cardId,让原方法继续走正常 UI 流程。决策类AutoDecisionHelper内部需要读取敌我状态、速度骰子、当前暂停阶段,因此它往往要访问游戏内部 API。建议在开发初期先用简单规则:选速度骰子中点数最高的书页来保证先手,之后再继续扩展。

4.5 动画速度补丁

动画优化使用一个后缀补丁,在每次拼点动画开始时,把内部表示“人类感知等待”的时间缩短。

// 文件路径:CombatQualityOfLife/Patches/AnimSpeedPatch.cs using HarmonyLib; namespace CombatQualityOfLife { // 示例:PoseAnimController 是动画控制器类,请以实际为准 [HarmonyPatch(typeof(PoseAnimController), "PlayPoseAnim")] static class AnimSpeedPatch { static void Postfix(PoseAnimController __instance) { if (!AnimConfig.Instance.FastPose.Value) { return; } Traverse.Create(__instance).Field("poseDuration").SetValue(0.1f); } } }

poseDuration字段可能不是真实的字段。使用Traverse的好处是,即使字段不存在,它也不会直接抛异常,而是返回一个空对象;但这也意味着你的补丁可能没生效。最可靠的方式还是先用反编译工具确认字段类型和访问修饰符,再决定用直接赋值还是反射。

如果游戏中动画时间是通过Time.timeScale控制的,你也可以尝试在某个区间内临时修改Time.timeScale = 2f。不过这会影响全局时间,包括 UI 动画,不太推荐在新手上手阶段使用。

4.6 伤害数字补丁

伤害显示补丁需要寻找 UI 组件。为了降低耦合,这里用HarmonyPostfix修改TextMeshProUGUI的字体大小和颜色。

// 文件路径:CombatQualityOfLife/Patches/DamageDisplayPatch.cs using HarmonyLib; using TMPro; namespace CombatQualityOfLife { // DamageTextUI 是示例类,请替换为实际类 [HarmonyPatch(typeof(DamageTextUI), "ShowNumber")] static class DamageDisplayPatch { static void Postfix(DamageTextUI __instance, TextMeshProUGUI ___numberText) { if (!DamageDisplayConfig.Instance.BigNumber.Value) { return; } ___numberText.fontSize = 56; ___numberText.color = new UnityEngine.Color(1f, 0.9f, 0.3f); } } }

___numberText是 Harmony 的命名约定:三个下划线表示获取目标类的私有字段。如果字段名不是numberText,补丁就不会正确注入。建议先写一个临时日志输出,确认字段值是否为 null。

4.7 构建与输出

在 Visual Studio 中构建解决方案,生成结果如CombatQualityOfLife.dll。将 DLL 放到BepInEx/plugins/CombatQualityOfLife/目录下,之后启动游戏。

如果你想在游戏目录之外调试,可以加一个“构建后复制”命令:

copy /Y "$(TargetPath)" "你的游戏目录\BepInEx\plugins\CombatQualityOfLife\"

注意路径不要包含中文字符或空格,避免某些老版本门禁问题。

4.8 预期表现与验证

启动游戏后,在 BepInEx 日志中可以看到:

[Info] CombatQualityOfLife 加载中... [Info] CombatQualityOfLife 加载完成

进入一场战斗:

  • 自动掷骰开启时,选择书页后应立即进入判定,无需反复确认。
  • 拼点动画明显变快,但不会跳过命中反馈。
  • 伤害数字更大更醒目,且不会和 UI 边框重叠。

如果某一个功能没有生效,优先看日志里有没有异常堆栈,而不是直接怀疑代码逻辑。

5. 常见问题与排查思路

mod 开发中,环境问题往往比逻辑问题更容易让人头疼。下面整理一张排查表,覆盖大多数新手会遇到的场景。

问题现象常见原因解决思路
插件没有加载DLL 没有放在 BepInEx/plugins 目录或目录层级不对检查是否放在 plugins 根目录下的子文件夹中,确认有BepInEx程序集依赖
日志出现 Harmony 补丁目标类型找不到游戏版本更新后类名或方法名变化重新反编译新版本游戏程序集,更新 Patch 目标
补丁执行了但功能没效果你修改的是本地副本,不是原方法真正使用的字段用 dnSpy 查看索引器或真实字段名,添加临时日志验证
自动掷骰选择了奇怪的书页权重计算只考虑了骰子点数,忽略了书页特效扩展决策逻辑,给书页效果加入权重,或在调试模式输出候选列表
动画优化导致 UI 错位简单跳过动画后,镜头/状态没有正常推进不要跳过,改为把延时参数调低;确保协程依然运行
伤害数字颜色和背景看不清直接改全局颜色,没有适配场景按伤害类型读取原有数据结构,分类型设置透明度
游戏闪退反射字段名错误,或修改了不可变字段先注释功能,二分定位是哪段补丁导致;确保字段可写
按快捷键没反应快捷键被 Unity 输入系统拦截注册Update中的Input.GetKeyDown而不是ConfigEntry事件

6. 最佳实践与工程建议

6.1 使用版本管理

哪怕是一个小 mod,也建议用 Git 管理源码。游戏更新后,你可以快速 diff 哪些函数轨道需要重新反编译,哪些补丁已失效。每次提交时记录游戏版本号,例如“支持 v1.0.2 的自动掷骰”,这样后续回滚会更容易。

6.2 日志要分级,不要什么都 Log

插件启动输出加载信息,补丁命中输出调试信息,配置修改输出变更信息。不要在每个补丁内部疯狂 Log,否则日志文件会迅速膨胀。你可以在开发阶段用LogInfo,发布阶段注释或改成LogDebug。

6.3 保持“最小修改原则”

尽量使用 Postfix 修改结果,而不是 Prefix 跳过整个方法。Harmony 的 Ability 是强大,但越少干预游戏原逻辑越好。每当你决定 cover 一个原方法,都要思考:原方法内部的静态状态、字段生命周期、协程关联会不会受影响。

6.4 配置要允许全关

不要把 mod 功能和原版战斗绑定死。所有功能都应该有独立开关,且默认值尽量贴近原版体验。这样当玩家遇到兼容性问题时,可以先关闭某一个模块,而不是整包卸载。

6.5 做好备份与测试环境

修改游戏程序集或配置前,备份整个游戏根目录或者只备份BepInEx和Managed目录。测试时使用一个“最小 mod 集”:只放你的 mod,排除其他 mod 的干扰。很多所谓“冲突问题”其实是两个 mod 同时修改同一个方法导致的,排查时要逐步启用。

6.6 安全与合规

不要逆向加密或绕过游戏授权,也不要分发包含他人版权的素材。自制 mod 属于玩家社区行为,分享时请附上源码和依赖说明,并注明适用于哪个游戏版本。发布前再次确认代码中没有隐藏的恶意功能,比如数据上传、修改存档等。

6.7 性能优化

自动掷骰决策如果每帧都执行,会占用不必要的 CPU。建议只在战斗阶段切换时执行,或者用协程延迟执行。伤害聚合文本如果频繁创建和销毁对象,会造成 GC 压力,建议对象池复用 Text 组件。

7. 总结与学习路线

到这里,你已经了解了《废墟图书馆》自制 mod 的三个核心需求背后的大致技术路径:通过 BepInEx 加载插件,用 Harmony 修改战斗流程,通过反编译确认真实类型和字段,再以配置来驱动开关。有了这套方法论,你再去看社区里那些开源 mod,会很快明白它们大概改了哪个方法、为什么那样写。

下一步,你可以沿着以下方向继续深入:

  • 学习 Unity UI 的 RectTransform 与布局原理,把伤害显示优化做成真正的自定义面板。
  • 了解 BepInEx 配置文件的动态重载,让玩家不需要重启游戏就能调整自动掷骰规则。
  • 研究游戏的书页特效调用链,在自动选择时加入更多规则,比如优先保留关键书页,而不是只看当前点数。
  • 尝试把三个模块拆分成独立插件,互相通过事件通信,形成一套小型 mod 体系。

如果你在实践过程中卡在某一步,建议按这个顺序排查:先确认插件是否加载,再确认 Harmony 补丁是否命中,最后确认你修改的字段或方法是不是真实存在的。记住,日志永远是你最好的老师。希望这篇教程能帮你打造出第一套自己满意的《废墟图书馆》体验增强 mod。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询