把 Minecraft 里的挖掘进度、方块放置余量、当前手持物品与耐久,实时镜像到 Windows 的 NotchPeninsula 灵动岛。
| 作品 | 目录 | 平台 | 技术栈 |
|---|---|---|---|
| NPS · Minecraft(模组) | DynamicIsland-MC/ | Minecraft Java 26.1.2 | Fabric Loom 1.18.2 · Java 25 |
| NPS · Minecraft(插件) | NpsDynamicIsland/ | Windows · NotchPeninsula | C# · .NET 10 · SkiaSharp 2.88 |
两者由同一条本地回环 UDP 链路驱动:模组采集并推送,插件接收并呈现。
一、能力总览
游戏端不再绘制任何界面 —— 动态岛只呈现在 Windows 桌面上。模组只做三件事: 跟踪状态、经本地回环把状态发出去、提供一个手动唤出的按键。
| 能力 | Fabric 模组(游戏内) | NPS 插件(Windows 桌面) |
|---|---|---|
| 动态岛本体 | 不绘制 HUD | IWidget 组件,位于灵动岛内 |
| 背景 | 无 | 不铺底色,内容直接画在宿主自己的背景上 |
| 待机态 | 上报当前物品(HELD):主手优先,主手为空取副手 | 只画一个图标,不占文字宽度;两手都空时是 Minecraft 图标;与游戏断开时整个组件隐藏 |
| 挖掘展开 | 推送进度百分比 | 环形进度条套在图标外,右侧依次是方块名与百分比 |
| 放置展开 | 推送背包余量 | 图标 + 方块名 ×余量 |
| 滚轮换物品 | 推送当前选中物品 | 图标 + 物品名 ×数量(单个省略 ×1) |
| 耐久 | 可损坏物品附上剩余 / 上限 | 物品旁画一条耐久条与 剩余/上限,低于两成转告警色 |
| 物品图标 | 下发纹理 PNG 的 Base64 | 解码后按物品缓存,以最近邻采样绘制原版像素纹理;拿不到时退回 Minecraft 图标 |
| 展开动画 | — | 由宿主弹簧动画完成宽度过渡 |
| 手动唤出 | 按 G 推送当前手持物品 | 收到后展开卡片 |
| 查看详情 | 无 | 左键点击组件切换详情页(连接状态、报文统计) |
| 自动收起 | — | 放置 / 物品卡片 3.2 秒后回到待机 |
| 心跳 | 每 2 秒一条 PING | 8 秒收不到任何报文才判定断开 |
1.1 数据链路
Fabric 模组 NotchPeninsula 插件
┌───────────────────────┐ ┌────────────────────────┐
│ MiningTracker │ │ IslandLinkServer │
│ PlacementTracker │──UDP 127.0.0.1──▶ │ :25585 │
│ SlotTracker │ 25585 │ ↓ │
│ IslandBridge (发送) │ 单行文本协议 │ IslandSnapshot (快照) │
└───────────────────────┘ │ IslandWidget (宿主绘制) │
└────────────────────────┘协议(单行 UTF-8 文本,| 分隔):
DI|TYPE|数值|名称|RRGGBB|图标|耐久当前|耐久上限
DI|IDLE
DI|PING状态报文固定八段,缺失的可选段写成空串 —— 定长分段让插件按固定下标取值。
- 名称由模组按客户端语言本地化后下发,插件不做翻译。
- 颜色取自方块的
MapColor,非方块物品下发-1。 - 图标是客户端资源包里 16×16 纹理 PNG 的 Base64(实测 150–330 字节)。
- 耐久仅对可损坏物品下发;缺失为空串,插件就不画耐久条。
HELD与SLOT的分工:SLOT表示「刚切换了物品」,会让岛上展开卡片;HELD只更新待机图标,不会展开。
二、安装
2.1 前置条件
| 端 | 要求 |
|---|---|
| Minecraft | Java 版 26.1.2 + Fabric Loader ≥ 0.19.3 |
| 模组依赖 | Fabric API(26.1.2 对应版本,如 0.155.3+26.1.2) |
| NotchPeninsula | 已安装并运行(插件目录 plugins\ 与主程序同级) |
2.2 安装模组
- 把
nps-minecraft-1.0.0.jar复制到游戏目录的mods\文件夹。 - 同时确认
mods\中存在 Fabric API。 - 启动游戏。按键设置中会出现「NPS · Minecraft」分类,
G键用于把当前手持物品唤出到岛上。
2.3 安装插件
- 把
NpsMinecraft.dll复制到NotchPeninsula\plugins\(或复制整个NpsMinecraft\文件夹, 内含 dll 与plugin.json)。 - 在宿主「插件中心」确认插件已加载,显示为「NPS · Minecraft」。
- 在「设置 → 显示设置 → 显示内容」中勾选它,即可上岛。
三、配置
| 项 | 默认值 | 如何修改 |
|---|---|---|
| UDP 端口 | 25585 | 两端必须一致。模组侧在 IslandBridge.PORT,插件侧读宿主设置键 port(注册表 HKCU\SOFTWARE\NotchPeninsula\Plugin.com.nps.dynamicisland.port) |
| 模组按键 | G | 游戏内「选项 → 控制 → 按键绑定 → NPS · Minecraft」 |
| 心跳间隔 | 2 s(40 tick) | 模组 DynamicIslandMod.PING_INTERVAL_TICKS |
| 断连判定窗口 | 8 s | 插件 IslandSnapshot.LinkTimeoutMs。必须大于心跳间隔,否则岛上会反复切换连接状态 |
| 卡片保留时长 | 3.2 s | 模组 ItemInfo 调用侧与插件 IslandSnapshot.PlacedHoldMs(插件为准) |
不需要放行防火墙
端口只绑定在 127.0.0.1,不监听外部网卡,因此无需添加防火墙规则。
四、使用
- 进入世界后,岛上只显示当前物品的原版图标(待机态,不占文字宽度)—— 主手没有东西时取副手;两手都空时是 Minecraft 图标。与游戏断开时整块内容隐藏,退出游戏后岛上不会残留。
- 用工具长按挖掘方块 —— 环形进度条从正上方顺时针推进,图标嵌在环内,右侧依次是方块名与百分比。
- 手持方块右键放置 —— 展开显示该方块在背包中的剩余总数,3.2 秒后收起。
- 滚轮切换物品 —— 展开显示名称与持有数量;若该物品可损坏,旁边还有耐久条与
剩余/上限。 - 手持工具时随时能看到耐久条,剩余低于两成会转为告警色。
- 按
G—— 手动把当前手持物品唤出到岛上(与滚轮切换同样的卡片)。 - 左键点击岛上的图标 —— 展开详情页,可查看连接状态与报文统计。
创造模式下的行为
方块瞬间破坏,因此不显示挖掘进度(无进度可言);放置方块不消耗物品, 模组改用「按下使用键的瞬间」作为信号,余量照常显示。
五、排查
| 现象 | 检查项 |
|---|---|
| 岛上只有一个图标、一直不展开 | 属正常待机状态;挖掘 / 放置 / 滚轮换物品或有事件时才展开 |
| 待机图标是 Minecraft 图标 | 主手与副手都是空的;也可能是该物品取不到纹理 |
| 岛上的内容忽然整块消失 | 8 秒内没收到任何报文即视为断开,此时按设计隐藏。检查模组是否仍在运行、两端端口是否一致 |
| 卡片收起后宽度没收回去 | 到期归位没有递增 Revision,组件收不到「要重测宽度」的通知 |
| 明显卡顿 | Revision 被在挖掘期间递增了 —— 进度不改变宽度,不该触发宿主重测 |
| 图标是 Minecraft 图标而不是物品本身 | 该物品在 assets/minecraft/textures/{item,block}/ 下没有同名 PNG,或图标超过 1100 字节上限被丢弃 |
| 文字里少了几个字符 | 当前字体画不出这些字,已按要求跳过而不渲染成方块 |
| 耐久条不出现 | 仅对「可损坏」物品显示;方块、食物等没有耐久 |
| 挖掘时不展开 | 进度只在「按住攻击键 + 准星指向有硬度的方块」期间推进。创造模式不显示进度;硬度为 0 的方块(草、花、火把)与空气同样不上岛 |
| 放置后不显示余量 | 仅对方块物品生效;需在使用键按下的瞬间准星指向方块 |
| 滚轮换物品没反应 | 只在槽位真正改变时推送;进世界后的第一次采样只上报待机图标,不弹卡片 |
按 G 没反应 | 需已进入世界 |
| 插件一直显示「未监听」 | 端口被占用,换一个端口并同步修改模组侧 |
六、从源码构建
6.1 模组
需要 JDK 25(Fabric 26.1 要求)。
cd DynamicIsland-MC
./gradlew build # 产物:build/libs/nps-minecraft-1.0.0.jar工程自带 Gradle Wrapper,其分发地址已指向国内镜像(腾讯云),无需另装 Gradle。
依赖策略:
build.gradle只声明实际用到的 4 个 Fabric API 模块 (fabric-api-base、fabric-lifecycle-events-v1、fabric-key-mapping-api-v1、fabric-rendering-v1), 而不是聚合包net.fabricmc.fabric-api:fabric-api。聚合包会一次拉取四十余个子模块、 其中数个数 MB,在慢速网络下极易超时;按需引入把首次构建的下载量降到十分之一左右。 运行时所需的完整 Fabric API 由fabric.mod.json的depends声明,玩家自行安装。
6.2 插件
需要 .NET 10 SDK(宿主的目标框架为 net10.0-windows10.0.19041.0)。
cd NpsDynamicIsland
dotnet build NpsDynamicIsland.csproj -c Release构建过程会自动:
- 把产物
NpsMinecraft.dll与plugin.json打包到dist/NpsMinecraft/; - 若检测到宿主插件目录,自动复制过去。
命名说明
工程目录与工程文件名仍是 NpsDynamicIsland,只有产物按 NPS · Minecraft 统一命名 (NpsMinecraft.dll 与 nps-minecraft-1.0.0.jar)—— 宿主插件目录里其余插件也都是 NpsXxx.dll 这种纯 ASCII 形式,跟着这个习惯走最省事。
七、实现说明
7.1 性能设计
岛上每帧都在重绘,因此两端都做了明确的节流约束:
| 端 | 约束 | 违反的后果 |
|---|---|---|
| 插件 | 只有「宽度可能变化」时才递增 Revision —— 挖掘进度不改变宽度,不得递增 | 宿主每秒被要求重测 20 次全部插件组件,实测就是「很卡」的主因 |
| 插件 | 连接状态翻转时也要递增 Revision —— 断开会让整个组件让位,宽度归零 | 退出游戏后岛上的内容一直挂着不走 |
| 插件 | 图标按物品名缓存;报文省略图标段时沿用同名缓存 | 每帧一次 PNG 解码 |
| 插件 | SKPaint 按线程复用,不每帧新建 | 每帧若干个原生对象的分配与析构 |
| 模组 | 图标按物品 3 秒节流重发,其余报文省略图标段 | 报文体积翻数倍 |
| 模组 | 挖掘推送两道闸门:进度变化不足 1% 不发,间隔不足 100 ms 也不发 | 每 tick 一条报文 |
7.2 挖掘进度为何这样算
原版的破坏推进在 MultiPlayerGameMode 内部,读它需要 Mixin 注入私有字段,会随版本更新而失效。 改用公开 API 在客户端 tick 内复现同一公式(速度 ÷ 硬度 ÷ 30 或 100),避开了对内部结构的依赖, 代价是不含服务端侧的特殊加成。
7.3 无法实现项
| 设想中的能力 | 模组端 | 插件端 | 说明 |
|---|---|---|---|
| 鼠标点击动态岛 | ✗ | ✓ | 游戏内鼠标被视角锁定,HUD 不接收鼠标事件。替代:按 G 唤出 |
| 拖动动态岛到任意位置 | ✗ | ✗ | 组件位置由宿主统一排布,插件无权指定坐标 |
| 在灵动岛上滚轮调节 | ✗ | ✗ | 宿主不向插件转发 WM_MOUSEWHEEL,不使用低层钩子的插件无法实现 |
| 自定义插件配置界面 | ✗ | ✗ | 宿主未提供「在设置窗口里给插件一块配置区」的接口,配置通过注册表设置键调整 |
八、约束声明
- 零宿主源码修改:插件只通过宿主公开的插件契约交互,宿主仓库保持逐字节未改动;
- 模组不注入游戏内部私有状态,全部使用公开 API 与 Fabric 提供的事件。