HUD 叠加层

hud/HudRenderer.java(480 行)+ hud/HudSettingsScreen.java。HUD 绘制被图钉的任务——即在任务列表里点图钉按钮挑出来的那几条,见 客户端图钉配置。

渲染入口

@SubscribeEvent
public void onRenderHud(RenderGameOverlayEvent.Post event)

由 ClientProxy.init() 注册在 MinecraftForge.EVENT_BUS。用 Post 而非 Pre,在游戏 HUD 文本之后绘制。

两个提前返回条件:

  1. if (event.type != RenderGameOverlayEvent.ElementType.TEXT) return; —— 只在 TEXT 阶段画
  2. if (mc.currentScreen != null && !(mc.currentScreen instanceof HudSettingsScreen)) return; —— 有其他界面打开时不画,但 HUD 设置界面打开时照画(方便边拖边看)

之后 if (!cfg.isHudVisible()) return; 与「无图钉则 return」。

maxPinnedTasks 在这里生效:all.size() > maxTasks ? all.subList(0, maxTasks) : all——超出部分直接截断,且因为图钉列表有序,保留的是前 N 条。

缩放与坐标

渲染时 glPushAttrib(GL_ALL_ATTRIB_BITS) + glPushMatrix() + glScaled(scale, scale, 1.0),结束时对称还原。所有坐标都要除以 scale,因为 GL 矩阵已经放大过了(源码注释)。

背景用 ColorUtils.backgroundHud.getColor()(0x88000000)画一个矩形,可用 showBackground 关掉。

布局常量

常量 值
MAX_BLOCK_WIDTH 160
LINE_H 10
ICON_SIZE 10(物品图标缩到与行高一致)
ICON_GAP 2
BLOCK_GAP 4(两个任务块之间)
PADDING 4

computeHudPosition(cfg, sw, sh, fr, pinned) 返回 [startX, startY, blockW, totalH] 四元组。源码注释:「Every frame starts here, both on the HUD and on the settings screen, so the counts are checked once.」——它必须先于绘制计算,因为块高度依赖要显示多少行清单/子任务。

totalH 按固定公式累加:每任务一行状态行(LINE_H)、清单超出部分一行 +N more、清单与子任务都非空时两个小节标题各占 LINE_H * 2、子任务超出部分再一行 +N more。

九宫格锚点

computeHudPosition 的 switch 按 Anchor 枚举定位,default -> sw - blockW - 2 对应 RIGHT、下一层 default -> sh - totalH - 2 对应 BOTTOM。也就是说九宫格只决定「角落 / 边中 / 中央」三个层级的基准点,再用 offsetX / offsetY 微调。

HUD 上的物品计数

COUNTS 是一个 static final Map<ItemStack, Integer>,用 IdentityHashMap。类注释解释:按被追踪的栈做键,一个栈只属于一个任务或清单项,所以总是带着同一个矿辞典名;snapshotStacks / snapshotSizes 记录快照,物品栏与快照一致时计数有效。

两处防退化处理:

  • 数量地板是 1(Math.max(1, ...)),注释「Same floor as the server check」,与服务端判定一致,见 物品完成追踪
  • 已勾选项不再显示计数——注释「A checked item stays checked once the items are spent, so its count would only confuse.」
  • 缓存有上限:注释说明「Task syncs replace the tracked stacks, so stale keys pile up until the inventory changes. The cap keeps a long idle session from growing the map.」

HUD 调的是服务端同一个 ItemTrackHandler.countItem(),所以显示数与服务端判定数一致。HUD 的数字仅是展示,不触发完成。

清单 vs 子任务:两个独立概念

tasknh.hud.section.checklist=Checklist %s 与 tasknh.hud.section.subtasks=Subtasks %s 是两个小节,各有独立的显示上限 maxChecklistShown 与 maxSubtaskRowsShown(都是 1–10,默认 3)。源码在 computeHudPosition 中只有「清单与子任务都非空时」才加两个标题的高度。

HUD 设置界面

HudSettingsScreen extends GuiScreen,这是 TaskNH 唯一继承原版 GuiScreen 的类(主界面走 ModularUI)。TaskListWidget 里的主题切换按钮用 mc.displayGuiScreen(new HudSettingsScreen()) 打开它。

常量 值
HANDLE_SIZE 10(拖拽手柄)
PANEL_H 24
PANEL_PADDING 6

doesGuiPauseGame() 覆写为 false——在这个界面里游戏不暂停,所以拖动时能直接看到 HUD 在动。

拖动状态由 dragging / dragOffsetX / dragOffsetY / dragAnchorX / dragAnchorY 五个字段维持,其中注释标明 dragAnchorX/dragAnchorY 是「拖动开始时的锚点,拖动期间保持不变」。拖动过程中只调 setOffsetXRaw / setOffsetYRaw 写内存,松手才 save()(见 客户端图钉配置)。

面板上有各开关的文案:tasknh.hud.settings.scale(缩放)、.bg(背景 ON/OFF)、.hud(HUD ON/OFF)、.checklist(清单条数)、.subtasks(子任务条数)、.tasks(任务条数)、.reset(重置),以及提示 tasknh.hud.settings.hint = “Drag handle to reposition HUD | ESC to close”。reset 调 PinnedTasksConfig.resetToDefaults()。

ESC 关闭走 mc.displayGuiScreen(null)。