在编程中,即使是最优秀的人也难免会遇到问题、漏洞和错误。
本指南概述了一些通用步骤,即使没有他人帮助,你也可以通过这些步骤识别并解决这些问题。 独立解决问题可以让你学到很多东西,也会让你感到很有成就感。
但是,如果你遇到了困难,无法独自找到解决方案,向他人寻求帮助也完全没问题!
控制台和日志记录器
定位问题最简单快捷的方法是将日志记录到控制台。
可以在运行时将值打印到控制台,告知开发者代码的当前状态,并方便分析变更和潜在错误。
在模组的实现 ModInitializer 的入口点类中,默认定义了一个日志记录器 (LOGGER),用于将所需的输出打印到控制台。
java
public class ExampleModDebug implements ModInitializer {
public static final String MOD_ID = "example-mod";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
// ...
}1
2
3
4
5
6
2
3
4
5
6
每当你需要了解代码中任何一点的某个值时,请通过将 String 传递给其方法来使用此 LOGGER。
java
ExampleModDebug.LOGGER.info("You interacted with an entity!");1
记录器支持多种模式将文本打印到控制台。 根据使用的模式,记录的行将以不同的颜色显示。
java
ExampleModDebug.LOGGER.debug("Debug message for development...");
ExampleModDebug.LOGGER.info("Neutral, informative text...");
ExampleModDebug.LOGGER.warn("Non-critical issues...");
ExampleModDebug.LOGGER.error("Critical exceptions, bugs..."); 1
2
3
4
2
3
4
INFO
所有记录器模式都支持多重过载,这样你就可以提供更多信息,例如堆栈跟踪!
例如,我们确保当在实体上使用 TestItem 时,它将在控制台中输出其当前状态。
java
@Override
public InteractionResult interactLivingEntity(ItemStack stack, Player user, LivingEntity entity, InteractionHand hand) {
Level level = user.level();
// Values are used in a String to provide more information in the console
String output = "Is Client World: %s | Health: %s / %s | The item was used with the %s"
.formatted(user.level().isClientSide(), entity.getHealth(), entity.getMaxHealth(), hand.name());
ExampleModDebug.LOGGER.info(output);
if (!user.level().isClientSide()) {
// you can log non-critical issues differently as a warning
ExampleModDebug.LOGGER.warn("Don't touch that!");
// The LOGGER can print the Stacktrace too in addition to the logging message
if (stack.getCount() > 1) {
IllegalArgumentException exception = new IllegalArgumentException("Only one item is allowed");
ExampleModDebug.LOGGER.error("Error while interacting with an entity", exception);
throw exception;
}
}
return InteractionResult.SUCCESS;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在记录的行中,你可以找到:
Time- 打印日志信息的时间Thread- 打印信息的线程。 你通常会看到一个 服务器线程 和一个 渲染线程,这告诉你代码是在哪一端执行的Name- 日志记录器的名称。 最好在此处使用你的模组 ID,以便日志和崩溃信息显示哪个模组记录了日志Message- 消息应简洁且具有描述性。 包含相关值或上下文Stack Trace- 如果提供了异常的堆栈跟踪,日志记录器也可以打印该堆栈跟踪
保持控制台整洁
请记住,如果模组在其他环境中使用,所有这些信息也会被打印出来。
为了防止你的模组污染日志文件,请为日志选择合适的日志级别。 如果你记录的数据仅在开发期间相关,强烈建议使用 debug 级别。
WARNING
debug 级别默认是隐藏的,要在开发期间显示它,请参阅调试日志记录。
如果你仍然不确定选择哪个日志级别,一个经验法则是:只有在出现问题时才使用非调试级别。 整合包开发者和用户并不太关心例如物品初始化之类的问题;他们更想知道例如数据包是否未能正确加载。
调试日志记录
调试日志级别默认是隐藏的,但可以通过某些配置在开发环境中专门启用。
为此,请在项目的根目录中创建 log4j-dev.xml 配置文件,并添加以下内容。 确保将 name 属性内的值替换为你模组的 modid。 此配置允许你的模组的调试日志显示在控制台中。
xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Loggers>
<Logger name="example-mod" level="debug" additivity="false">
<AppenderRef ref="SysOut"/>
<AppenderRef ref="ServerGuiConsole"/>
</Logger>
</Loggers>
</Configuration>1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
然后在 build.gradle 中,告诉 Loom 使用我们的新配置。
java
loom.log4jConfigs.from "log4j-dev.xml"1
现在,当我们发送调试日志消息时,我们将能够在控制台中看到它们。
java
LOGGER.debug("Debug logging is enabled");1

TIP
如果调试消息仍然不可见,你的运行配置可能已过时。 要解决此问题,请使用适用于你的 IDE 的 Gradle 任务重新生成它们。
定位问题
日志记录器会在行首打印你的模组 ID。 你可以搜索 (⌘/CTRL+F) 来高亮显示。
缺失的资源和纹理(黑紫方格占位符)也会在控制台中记录警告,并且通常会显示预期值。


断点
一种更复杂的调试方法是在 IDE 中使用断点。 顾名思义,断点用于在特定位置暂停正在执行的代码,以便检查和修改软件的状态。
TIP
要使用断点,你必须使用 Debug(调试)选项而不是 Run(运行)选项执行实例:

我们再次以自定义物品为例。 该物品的 CUSTOM_NAME DataComponentType 应该会根据其在任何石头方块上使用而改变。 然而,在这个例子中,该物品似乎总是会触发“成功”手势动画,而圆石似乎并没有改变其名称。
我们该如何解决这两个问题? 让我们来探究一下……
java
// problematic example code:
public class TestItem extends Item {
public TestItem(Properties properties) {
super(properties);
}
@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
Player user = context.getPlayer();
BlockPos targetPos = context.getBlockPos();
ItemStack itemStack = context.getItemInHand();
BlockState state = level.getBlockState(targetPos);
if (state.is(ConventionalBlockTags.STONES)) {
Component newName = Component.literal("[").append(state.getBlock().getName()).append(Component.literal("]"));
itemStack.set(DataComponents.CUSTOM_NAME, newName);
if (user != null) {
user.displayClientMessage(Component.literal("Changed Item Name"), true);
}
}
return InteractionResult.SUCCESS;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
点击行号即可设置断点。 如有需要,你可以一次设置多个断点。 断点会在执行所选行之前停止。

然后让正在运行的 Minecraft 实例执行这部分代码。 你也可以在游戏运行时设置断点。
在本例中,自定义物品需要作用于方块。 Minecraft 窗口应该会冻结,并且在 IntelliJ 中,断点旁边会出现一个黄色箭头,表示调试器已到达该点。
在底部,会打开一个 Debug(调试)窗口,并自动选中 Threads & Variables(线程和变量)视图。 你可以使用 Debug(调试)窗口中的箭头控件移动执行点。 这种在代码中移动的方式称为“步进”。

| 操作 | 说明 |
|---|---|
| 步过(Step Over) | 进入下一行执行,基本上是沿着逻辑移动实例 |
| 步入(Step In) | 进入方法内部,显示正在发生的事情。 如果一行中有多个方法,你可以通过点击来选择要进入的方法。 这对于 Lambda 表达式也是必需的。 |
| 运行到光标处 | 逐步执行逻辑,直到到达代码中的光标。 这对于跳过大段代码非常有用。 |
| 显示执行点 | 将编码窗口的视图移动到调试器当前所在的位置。 即使你当前正在处理其他文件和选项卡,此操作也有效。 |
INFO
“步过” F8 和“步入” F7 操作是最常见的,因此请尝试熟悉这些快捷键!
如果你完成了当前检查,你可以按绿色的 恢复程序 按钮(F9)。 这将解冻 Minecraft 实例,并且可以进行进一步的测试,直到遇到另一个断点。 但现在我们先继续查看 调试 窗口。
在顶部,你可以看到所有当前正在运行的实例。 如果两个实例都在运行,你可以在客户端和服务器实例之间切换。 在其下方,你可以查看调试操作和控件。 如果需要查看日志,你还可以切换到 控制台 视图。
在左侧,你可以看到当前活动的线程,及其下方的堆栈跟踪。
在右侧,你可以检查和操作已加载的值和对象。 你还可以将鼠标悬停在代码中的值上:如果它们在作用域内并且仍处于加载状态,则会弹出一个窗口显示它们的具体值。
如果你对特定对象的内容感兴趣,可以使用其旁边的小箭头图标。 这将展开所有嵌套数据。
如果执行点未经过这些值,或者这些值位于完全不同的上下文中,则显然不会加载这些值。

你可以使用 调试 窗口中的输入行执行许多不同的操作,例如,你可以访问当前加载的对象并对其使用方法。 这将在下方添加一个新条目,显示请求的数据。

让我们在示例中进行逐过程,以便加载 BlockState 变量。 现在我们可以检查目标方块的 BlockState 是否确实位于 Block 标签中。
TIP
按下输入行右侧的 + 图标来固定当前调试会话的结果。

我们可以看到,ConventionalBlockTags.STONES 标签不包含圆石,因为圆石有一个单独的标签。
断点切换和条件
有时你只需要在满足特定条件时暂停代码。 为此,请创建一个断点并右键单击它以打开其设置。 在那里,你可以设置布尔语句作为条件。
空心断点图标表示非活动断点,这些断点不会暂停活动的 Minecraft 实例。 你可以在断点的设置弹出窗口中切换断点,也可以直接在断点上单击鼠标中键来切换。
所有断点都将列在 IntelliJ 的 书签 窗口中。


热交换活动实例
你可以在实例运行时使用带有锤子图标的 构建 > 构建项目 操作对代码进行有限的更改。 你还可以通过右键单击 IntelliJ 顶部菜单栏上的空白处将图标放在 运行配置 下拉元素旁边。

此过程也称为“热交换”,要求 Minecraft 实例以 调试 模式而不是 运行 模式启动(参见上文)。
这样,你就不需要再次重新启动 Minecraft 实例。 它还可以更快地测试屏幕元素对齐和其他功能平衡。 如果“热交换”成功,IntelliJ 将通知你。

Mixin 是个例外。 你可以设置运行配置,允许它们在运行时更改。 更多信息,请参阅热交换 Mixin。
其他更改可以在游戏中重新加载。
assets/文件夹的更改 -> 按 F3+Tdata/文件夹的更改 -> 使用/reload命令
为了完成之前的示例,让我们在语句中添加一个条件。 一旦我们到达断点,我们可以看到我们总是获得“成功”的手势动画,因为我们没有返回任何其他内容。
应用修复并使用热交换,即可立即在游戏中看到更改。
java
@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
Player user = context.getPlayer();
BlockPos targetPos = context.getClickedPos();
ItemStack itemStack = context.getItemInHand();
BlockState state = level.getBlockState(targetPos);
if (state.is(ConventionalBlockTags.STONES) || state.is(ConventionalBlockTags.COBBLESTONES)) {
Component newName = Component.literal("[").append(state.getBlock().getName()).append(Component.literal("]"));
itemStack.set(DataComponents.CUSTOM_NAME, newName);
if (user != null) {
user.displayClientMessage(Component.literal("Changed Item Name"), true);
}
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
日志与崩溃
先前执行的实例的控制台会被导出为日志文件,位于 Minecraft 实例的 logs 目录中。 最新的日志通常名为 latest.log。
用户可以将此文件分享给模组开发者以供进一步检查,详情请参阅上传日志。
在开发环境中,你可以在 项目 窗口的 run > logs 文件夹中找到先前的日志,并在 run > crash-reports 文件夹中找到崩溃报告。
询问社区!
还是不明白是怎么回事? 你可以加入 Fabric Discord 服务器,与社区成员交流!
你也可以访问 Fabric 官方 Wiki,了解更多高级问题。



