自定义物品
想要创建一个自定义物品,需要包含两个部分:
- 插件内成功注册到 Nukkit-MOT
 - 资源包内定义物品贴图发送给客户端
 
接下来以 糖果剑 为例,演示自定义物品的步骤。
在插件中注册物品
我们按照以下序列图进行操作
创建新物品的类
在插件中,新建 CandyCaneSword 类,继承 ItemCustom 并重写相应方法
package cn.nukkitmot.exampleplugin.custom.item;
import cn.nukkit.item.customitem.CustomItemDefinition;
import cn.nukkit.item.customitem.ItemCustom;
import cn.nukkit.item.customitem.data.ItemCreativeCategory;
import cn.nukkit.item.customitem.data.ItemCreativeGroup;
import cn.nukkit.item.customitem.data.RenderOffsets;
public class CandyCaneSword extends ItemCustom {
    private static String spacenameId = "nukkit:candy_cane_sword";
    private static String textureName = "candy_cane_sword";
    private static String name = null;
    public CandyCaneSword() {
        super(spacenameId, name, textureName);
    }
    public int scaleOffset() {
        return 32; // 需要是16的倍数,如 32、64、128
    }
    /**
     * 该方法设置自定义物品的定义
     */
    @Override
    public CustomItemDefinition getDefinition() {
        return CustomItemDefinition
                .simpleBuilder(this, ItemCreativeCategory.EQUIPMENT)
                .creativeGroup(ItemCreativeGroup.SWORD)
                .allowOffHand(true)
                .handEquipped(true)
                .renderOffsets(RenderOffsets.scaleOffset(scaleOffset()))
                .build();
    }
    @Override
    public int getMaxDurability() {
        return 500;
    }
    @Override
    public int getMaxStackSize() {
        return 1;
    }
    @Override
    public int getAttackDamage() {
        return 4;
    }
    @Override
    public boolean isSword() {
        return true;
    }
}
ItemCustom 主要方法
引自 cn.nukkit.item.customitem.ItemCustom
我们需要用 @Override 来重写下面的方法。
- 
getMaxStackSize()方法用于设置自定义物品的最大堆叠数量。 - 
scaleOffset()方法用于设置自定义物品的贴图大小,返回值需要是16的倍数,如 32、64、128。 - 
getDefinition()返回一个 CustomItemDefinition 类概括了该物品的基本属性,如 是否允许副手、在创造栏的分类、附魔效果。 
CustomItemDefinition 主要方法
引自 cn.nukkit.item.customitem.CustomItemDefinition
常用构建器
- 
customBuilder自定义物品的定义构造器 - 
simpleBuilder简单物品的构建器 (默认使用这个) - 
toolBuilder工具物品的构建器 - 
armorBuilder盔甲物品的构建器 - 
edibleBuilder食品的构建器 
先使用构建器返回一个 CustomItemDefinition 类
CustomItemDefinition.simpleBuilder(ItemCustom item, ItemCreativeCategory creativeCategory);
常用方法
由于方法直接返回 this 因此我们可以使用扁平化的写法。
- 
allowOffHand(boolean allowOffHand)是否允许副手持有。 - 
handEquipped(boolean handEquipped)控制第三人称手持物品的显示方式。 - 
foil(boolean foil)是否有附魔光辉效果,比如附魔书就有。 - 
creativeGroup(ItemCreativeGroup creativeGroup)控制自定义物品在创造栏的分组,例如所有的附魔书都是ItemCreativeGroup.ENCHANTED_BOOK组。 - 
canDestroyInCreative(boolean value)控制拿该物品的玩家是否可以在创造模式挖掘方块。 
ItemCreativeGroup 主要方法
引自 cn.nukkit.item.customitem.data.ItemCreativeGroup
注册物品
最后,在插件入口类的 onEnable 方法中注册物品
import cn.nukkit.item.Item;
import cn.nukkitmot.exampleplugin.custom.item.CandyCaneSword;
public class ExamplePlugin extends PluginBase {
    @Override
    public void onEnable() {
        Item.registerCustomItem(CandyCaneSword.class);
    }
}
制作资源包
教程将指导您如何制作资源包,确保正确指向物品贴图,以在游戏内正确显自定义物品。
如果在游戏内创造物品栏中仍然看到空白格子,请检查资源包配置和物品贴图路径是否正确。
详细步骤包括:
- 定义资源包的 UUID 和信息。
 - 在资源包中定义物品贴图路径。
 - 打包资源包并放入服务器的 resource_packs 文件夹。
 
资源包目录构成
资源包目录应该包含以下文件:
manifest.json
参考 Bedrock Wiki 对 RP Manifest 的讲解。
{
    "format_version": 2,
    "header": {
        "description": "BY.nukkit-mot",
        "name": "§7测试资源包",
        "uuid": "00000000-0000-0000-0000-000020160300",
        "version": [1, 1, 6],
        "min_engine_version": [1, 14, 0]
    },
    "modules": [
        {
            "type": "resources",
            "uuid": "dde211f9-e1a6-435e-9a84-06fa9242f63e",
            "version": [1, 0, 0]
        }
    ]
}
item_texture.json
{
    "resource_pack_name": "nukkit-mot",
    "texture_name": "atlas.items",
    "texture_data": {
        "candy_cane_sword": {
            "textures": "textures/items/candy_cane_sword"
        }
    }
}
打包好资源包放入服务器的 resource_packs 文件夹。
进入服务器,正常情况下您会看见资源包正常显示。
很抱歉听到这个消息,但请保持冷静。并按下列情形进行排查:
- 若非插件内置资源包,资源包打包时是以根目录压缩的吗?
 
也就是点开压缩包就能看到
manifest.json文件。
- 资源包后缀是否从 
.zip改为.mcpack后缀? 
非 zip 格式的压缩算法客户端不支持。
- 更新资源包后,是否有清理客户端缓存?
 
在
设置 → 存储 → 资源包存有资源包缓存时,客户端不会向服务器请求更新的资源包。
深入了解
插件内置资源包
得益于 Nukkit-MOT 的设计,我们可以在插件开发中非常方便的编辑资源包。
只需要在插件的 resources 创建 assets/resource_pack 文件夹,并在其中放置资源包。
示例插件就是这么做的。
示例插件的 resources 目录结构
与 Bedrock Wiki 的对照关系
如果 CustomItemDefinition 中没有您想要的封装方法,该怎么做呢?
我们可以直接参照 Bedrock Wiki 的 ItemComponents 文档!
以允许副手举例。
在 Nukkit-MOT 的 CustomItemDefinition 类中的内容如下
public class CustomItemDefinition {
    public static class SimpleBuilder {
        /**
         * 是否允许副手持有
         */
        public SimpleBuilder allowOffHand(boolean allowOffHand) {
            this.nbt.getCompound("components")
                    .getCompound("item_properties")
                    .putBoolean("allow_off_hand", allowOffHand);
            return this;
        }
    }
}
而 Bedrock Wiki 中是这么介绍 Allow Off Hand 的:
## Allow Off Hand
Determines whether an item can be placed in the off-hand slot of the inventory.
"minecraft:allow_off_hand": {
    "value": true
}
SimpleBuilder#allowOffHand 方法中的 this.nbt 在构建器中被创建,可参考 Nukkit-MOT 的 CustomItemDefinition.java#L208
    public static class SimpleBuilder {
        /**
         * 是否允许副手持有
         */
        public SimpleBuilder allowOffHand(boolean allowOffHand) {
            this.nbt.getCompound("components")
                    .getCompound("item_properties")
                    .putBoolean("allow_off_hand", allowOffHand);
            return this;
        }
    }
并不是所有方法都能通过简单的添加 nbt 就达成其效果的,譬如 minecraft:cooldown 就需要服务器来处理 PlayerStartItemCoolDownPacket 以实现物品的使用冷却行为。
可以在 cn.nukkit.network.protocol.ProtocolInfo 中查看协议支持的所有数据包。
RenderOffsets 渲染偏移
渲染偏移的知识较为庞大、复杂,当前也没有可视化工具所以需要一定的空间想象能力。
引用:
通过观察下面的高亮部分代码可知,需要向 RenderOffsets 类传入 4 个 Offset 对象。
    @Override
    public CustomItemDefinition getDefinition() {
        return CustomItemDefinition
                .toolBuilder(this, ItemCreativeCategory.EQUIPMENT)
                .addRepairItems(List.of(Item.fromString("minecraft:amethyst_shard")), 100)
                .addRepairItems(List.of(Item.fromString("yes:amethyst_spear")), 400)
                .renderOffsets(new RenderOffsets(
                                Offset.builder()
                                        .position(0.48f, -0.128f, -0.946f)
                                        .rotation(11.696f, -64.536f, 79.413f)
                                        .scale(0.038f, 0.037f, 0.038f),
                                Offset.builder()
                                        .position(0.258f, 0.979f, -0.541f)
                                        .rotation(-63.268f, -43.969f, 144.041f)
                                        .scale(0.094f, 0.094f, 0.094f),
                                Offset.builder()
                                        .position(-1.053f, 0.136f, -0.803f)
                                        .rotation(27.273f, 67.731f, -64.494f)
                                        .scale(0.063f, 0.063f, 0.063f),
                                Offset.builder()
                                        .position(0.258f, 0.979f, -0.541f)
                                        .rotation(-63.268f, -43.969f, 144.041f)
                                        .scale(0.094f, 0.094f, 0.094f)
                        )
                )
                .creativeGroup("itemGroup.name.sword")
                .allowOffHand(false)
                .handEquipped(true)
                .customBuild(nbt -> {
                    nbt.getCompound("components")
                            .putCompound("minecraft:cooldown", new CompoundTag()
                                    .putString("category", "amethyst_spear")
                                    .putFloat("duration", 3f))
                            .getCompound("item_properties").putBoolean("animates_in_toolbar", true)
                            .getCompound("item_properties").putInt("use_duration", 640);
                });
    }
在 RenderOffsets 类的定义中
public RenderOffsets(@Nullable Offset mainHandFirstPerson, @Nullable Offset mainHandThirdPerson, @Nullable Offset offHandFirstPerson, @Nullable Offset offHandThirdPerson) {}
可以知道四个参数分别为:
- mainHandFirstPerson: 主手第一人称
 - mainHandThirdPerson: 主手第三人称
 - offHandFirstPerson: 副手第一人称
 - offHandThirdPerson: 副手第三人称
 
需要更多的发现...