Skip to content

UI Factory

自 2.2.1

LDLib2 为最常见的使用场景提供了三种预构建的工厂辅助类。每种工厂都会自动处理菜单注册、客户端–服务端路由以及生命周期验证。

工厂触发方式适用场景
BlockUIMenuType玩家右键点击方块方块 / Block Entity UI
HeldItemUIMenuType玩家右键点击手持物品手持工具、设备、扳手
PlayerUIMenuType任意服务端调用命令、按键绑定、任意触发器

KubeJS 用户可以通过 LDLib2UIFactory 绑定来打开上述任意 UI,并通过 LDLib2UI 事件组注册 UI 构建器——无需使用 Java 或 Kotlin。


BlockUIMenuType — 方块 / Block Entity UI

当方块(或 Block Entity)在右键点击时打开 UI,请使用此类。

在你的 Block 类上实现 BlockUIMenuType.BlockUI 接口:

java
public class MyBlock extends Block implements BlockUIMenuType.BlockUI {

    @Override
    public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos,
                                            Player player, BlockHitResult hit) {
        if (!level.isClientSide) {
            BlockUIMenuType.openUI((ServerPlayer) player, pos);
        }
        return InteractionResult.SUCCESS;
    }

    @Override
    public ModularUI createUI(BlockUIMenuType.BlockUIHolder holder) {
        return ModularUI.of(UI.of(
            // 在此处构建你的元素树
        ), holder.player);
    }
}

传递给 createUI 的上下文对象 / 事件中可用的字段:

字段类型描述
playerPlayer打开 UI 的玩家
posBlockPos方块的位置
blockStateBlockState方块的当前状态

HeldItemUIMenuType — 手持物品 UI

当手持物品打开 UI(例如便携设备、扳手或扫描仪)时,请使用此类。

在你的 Item 类上实现 HeldItemUIMenuType.HeldItemUI 接口:

java
public class MyItem extends Item implements HeldItemUIMenuType.HeldItemUI {

    @Override
    public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
        if (!level.isClientSide) {
            HeldItemUIMenuType.openUI((ServerPlayer) player, hand);
        }
        return InteractionResultHolder.success(player.getItemInHand(hand));
    }

    @Override
    public ModularUI createUI(HeldItemUIMenuType.HeldItemUIHolder holder) {
        return ModularUI.of(UI.of(
            // 在此处构建你的元素树
        ), holder.player);
    }
}

传递给 createUI 的上下文对象 / 事件中可用的字段:

字段类型描述
playerPlayer手持物品的玩家
handInteractionHand正在使用的手
itemStackItemStack打开 UI 的物品堆叠

PlayerUIMenuType — 玩家触发的 UI

当需要打开一个不绑定到特定方块或物品的 UI 时,请使用此类——例如从命令、按键绑定或任意服务端触发器打开。

使用唯一 ID 注册一个 holder 工厂,然后在需要时调用 openUI

java
// 注册一次(例如在模组初始化期间)
var MY_UI_ID = ResourceLocation.fromNamespaceAndPath("mymod", "my_ui");

PlayerUIMenuType.register(MY_UI_ID, player -> {
    return p -> ModularUI.of(UI.of(
        // 在此处构建你的元素树
    ), p);
});

// 之后打开(服务端)
PlayerUIMenuType.openUI(serverPlayer, MY_UI_ID);

事件中可用的上下文对象:

字段类型描述
playerPlayerUI 为其打开的玩家

INFO

LDLib2UIFactory.openPlayerUI 必须从服务端调用。由于没有方块或物品可供验证,UI 始终被视为有效。


KubeJS: 脚本放置

LDLib2UI 事件(.block.item.player)在服务端和客户端都会触发:

  • 服务端触发它以构建 ModularUIContainerMenu
  • 客户端触发它以构建 ModularUIContainerScreen

两侧都必须为相同的 ID 注册处理器——否则 UI 只会存在于一侧。

选项 A: 分离客户端和服务端脚本

client_scriptsserver_scripts 中都注册构建器。你可以为每一侧提供不同的实现(例如客户端使用不同的样式表):

javascript
// client_scripts/main.js
LDLib2UI.block("mymod:my_block_ui", event => {
    event.modularUI = ModularUI.of(UI.of(
        new UIElement().addClass("panel_bg")
    ), event.player);
})
javascript
// server_scripts/main.js
LDLib2UI.block("mymod:my_block_ui", event => {
    event.modularUI = ModularUI.of(UI.of(
        new UIElement().addClass("panel_bg")
    ), event.player);
})

选项 B: Startup 脚本(推荐)

startup_scripts客户端和服务端都会运行,因此一次注册即可覆盖两侧:

javascript
// startup_scripts/main.js — 在客户端和服务端都会执行
LDLib2UI.block("mymod:my_block_ui", event => {
    event.modularUI = ModularUI.of(UI.of(
        new UIElement().addClass("panel_bg")
    ), event.player);
})

不要在 startup 脚本中过早创建 UI

Minecraft 资源(样式表、纹理等)在 startup 脚本运行时尚未加载。 始终在事件处理器 lambda 内部创建 UI,它在 UI 打开时运行——而不是在脚本加载时运行。

javascript
// ❌ 错误 — 在 startup 时构建,资源尚未就绪
const myUI = ModularUI.of(UI.of(new UIElement()), null);
LDLib2UI.block("mymod:my_block_ui", event => {
    event.modularUI = myUI; // 已过时;资源尚未加载
})

// ✅ 正确 — 在玩家打开 UI 时惰性构建
LDLib2UI.block("mymod:my_block_ui", event => {
    event.modularUI = ModularUI.of(UI.of(new UIElement()), event.player);
})

Released under the MIT License.