第 6 章:物品 API
第 6 章:物品 API
掌握 Minecraft 物品系统,学会创建自定义物品、操作 NBT 数据和自定义模型。
6.1 ItemStack 基础
ItemStack 是 Bukkit 中物品的核心类,表示一个物品堆。
创建基础物品
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
// 简单创建
ItemStack diamond = new ItemStack(Material.DIAMOND);
// 指定数量
ItemStack diamonds = new ItemStack(Material.DIAMOND, 64);
// 空物品(AIR)
ItemStack empty = ItemStack.empty();
物品不可变性
在 Paper 1.20.5+ 中,ItemStack 引入了不可变(Immutable)概念:
// 可变物品栈
ItemStack item = new ItemStack(Material.DIAMOND_SWORD);
// 不可变视图(防止意外修改)
ItemStack immutable = item.asOne(); // 返回数量为 1 的不可变副本
ItemStack mutable = item.asQuantity(5); // 返回新栈,数量为 5
6.2 ItemMeta:物品元数据
ItemMeta 控制物品的显示名称、Lore(描述)、附魔等。
设置显示名称
ItemStack item = new ItemStack(Material.DIAMOND_SWORD);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
// 使用 Adventure API 设置名称(Paper 推荐)
meta.displayName(Component.text("§6传说之剑", NamedTextColor.GOLD));
// 设置 Lore(描述文本)
meta.lore(List.of(
Component.text("§7一把传说中的武器", NamedTextColor.GRAY),
Component.empty(),
Component.text("§c⚔ 攻击力: +100", NamedTextColor.RED),
Component.text("§b✦ 速度: +20%", NamedTextColor.AQUA)
));
item.setItemMeta(meta);
}
不可破坏与自定义模型数据
if (meta != null) {
// 设置为不可破坏
meta.setUnbreakable(true);
// 设置自定义模型数据(用于资源包自定义材质)
meta.setCustomModelData(1001);
item.setItemMeta(meta);
}
附魔系统
import org.bukkit.enchantments.Enchantment;
if (meta != null) {
// 添加附魔
meta.addEnchant(Enchantment.SHARPNESS, 5, true); // 锋利 V,true 忽略限制
meta.addEnchant(Enchantment.FIRE_ASPECT, 2, true);
// 移除附魔
meta.removeEnchant(Enchantment.FIRE_ASPECT);
// 检查附魔
if (meta.hasEnchant(Enchantment.SHARPNESS)) {
int level = meta.getEnchantLevel(Enchantment.SHARPNESS);
}
item.setItemMeta(meta);
}
属性修饰符(Attribute Modifiers)
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.inventory.EquipmentSlotGroup;
if (meta != null) {
// 增加攻击力
meta.addAttributeModifier(
Attribute.ATTACK_DAMAGE,
new AttributeModifier(
NamespacedKey.minecraft("attack_damage"),
10.0, // 增加 10 点
AttributeModifier.Operation.ADD_NUMBER,
EquipmentSlotGroup.MAINHAND
)
);
// 增加移动速度
meta.addAttributeModifier(
Attribute.MOVEMENT_SPEED,
new AttributeModifier(
NamespacedKey.minecraft("movement_speed"),
0.1,
AttributeModifier.Operation.ADD_SCALAR,
EquipmentSlotGroup.MAINHAND
)
);
item.setItemMeta(meta);
}
6.3 自定义物品工厂
在实际项目中,通常使用工厂模式创建自定义物品:
package com.example.myplugin.items;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import java.util.List;
public final class CustomItemFactory {
private static NamespacedKey ITEM_ID_KEY;
public static void init(MyPlugin plugin) {
ITEM_ID_KEY = new NamespacedKey(plugin, "custom_item_id");
}
/**
* 创建魔法苹果
*/
public static ItemStack createMagicApple() {
ItemStack item = new ItemStack(Material.GOLDEN_APPLE);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("✦ 魔法苹果", NamedTextColor.LIGHT_PURPLE)
.decoration(TextDecoration.ITALIC, false));
meta.lore(List.of(
Component.text("§7散发着神秘的光芒", NamedTextColor.GRAY)
.decoration(TextDecoration.ITALIC, false),
Component.empty(),
Component.text("§a❤ 恢复全部生命值", NamedTextColor.GREEN)
.decoration(TextDecoration.ITALIC, false),
Component.text("§b✦ 获得 30 秒生命恢复", NamedTextColor.AQUA)
.decoration(TextDecoration.ITALIC, false)
));
meta.setCustomModelData(2001);
// 存储自定义 ID(用于识别物品)
meta.getPersistentDataContainer().set(
ITEM_ID_KEY,
PersistentDataType.STRING,
"magic_apple"
);
item.setItemMeta(meta);
}
return item;
}
/**
* 创建传送卷轴
*/
public static ItemStack createTeleportScroll() {
ItemStack item = new ItemStack(Material.PAPER);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§b传送卷轴"));
meta.lore(List.of(
Component.text("§7右键使用,传送到"),
Component.text("§7设定的地标位置")
));
meta.setCustomModelData(3001);
meta.getPersistentDataContainer().set(
ITEM_ID_KEY,
PersistentDataType.STRING,
"teleport_scroll"
);
item.setItemMeta(meta);
}
return item;
}
/**
* 检查物品是否是自定义物品
*/
public static boolean isCustomItem(ItemStack item, String customId) {
if (item == null || item.getType() == Material.AIR) return false;
ItemMeta meta = item.getItemMeta();
if (meta == null) return false;
String id = meta.getPersistentDataContainer().get(
ITEM_ID_KEY, PersistentDataType.STRING
);
return customId.equals(id);
}
}
6.4 NBT 数据操作
Paper 提供了 PersistentDataContainer API 来存储自定义数据,无需依赖 NMS。
基本 NBT 读写
NamespacedKey key = new NamespacedKey(plugin, "my_custom_data");
// 写入数据
meta.getPersistentDataContainer().set(key, PersistentDataType.STRING, "hello");
meta.getPersistentDataContainer().set(key, PersistentDataType.INTEGER, 42);
meta.getPersistentDataContainer().set(key, PersistentDataType.DOUBLE, 3.14);
// 读取数据
String str = meta.getPersistentDataContainer().get(key, PersistentDataType.STRING);
Integer num = meta.getPersistentDataContainer().get(key, PersistentDataType.INTEGER);
// 检查是否存在
boolean hasKey = meta.getPersistentDataContainer().has(key, PersistentDataType.STRING);
// 删除数据
meta.getPersistentDataContainer().remove(key);
支持的数据类型
| PersistentDataType | Java 类型 | 说明 |
|---|---|---|
BYTE | Byte | 字节 |
SHORT | Short | 短整数 |
INTEGER | Integer | 整数 |
LONG | Long | 长整数 |
FLOAT | Float | 单精度浮点 |
DOUBLE | Double | 双精度浮点 |
STRING | String | 字符串 |
BYTE_ARRAY | byte[] | 字节数组 |
INTEGER_ARRAY | int[] | 整数数组 |
LONG_ARRAY | long[] | 长整数数组 |
TAG_CONTAINER | PersistentDataContainer | 嵌套容器 |
自定义复合数据
// 存储嵌套数据
NamespacedKey rootKey = new NamespacedKey(plugin, "item_data");
PersistentDataContainer container = meta.getPersistentDataContainer()
.getAdapterContext()
.newPersistentDataContainer();
container.set(new NamespacedKey(plugin, "damage"), PersistentDataType.DOUBLE, 15.0);
container.set(new NamespacedKey(plugin, "level"), PersistentDataType.INTEGER, 5);
meta.getPersistentDataContainer().set(rootKey, PersistentDataType.TAG_CONTAINER, container);
6.5 Paper 数据组件(Data Components)
Paper 1.20.5+ 引入了新的数据组件系统,替代了传统的部分 ItemMeta 方法:
import io.papermc.paper.datacomponent.DataComponentTypes;
import io.papermc.paper.datacomponent.item.CustomModelData;
// 使用数据组件设置自定义模型数据
item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData(1001));
// 读取
CustomModelData cmd = item.getData(DataComponentTypes.CUSTOM_MODEL_DATA);
注意: 数据组件 API 在 Paper 中仍在快速迭代,具体可用的组件类型请参考最新文档。
6.6 烟火物品示例
创建自定义烟火
import org.bukkit.FireworkEffect;
import org.bukkit.inventory.meta.FireworkMeta;
public static ItemStack createPartyFirework() {
ItemStack item = new ItemStack(Material.FIREWORK_ROCKET);
FireworkMeta meta = (FireworkMeta) item.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§6派对烟花"));
meta.addEffect(FireworkEffect.builder()
.with(FireworkEffect.Type.STAR)
.withColor(Color.RED, Color.BLUE, Color.GREEN)
.withFade(Color.YELLOW, Color.PURPLE)
.trail(true)
.flicker(true)
.build());
meta.setPower(2); // 飞行高度
item.setItemMeta(meta);
}
return item;
}
6.7 物品比较与识别
物品相等性检查
// 不可靠:比较对象引用
if (item1 == item2) { ... }
// 可靠:比较物品内容
if (item1.isSimilar(item2)) { ... } // 材料、元数据都相同
if (item1.equals(item2)) { ... } // isSimilar + 数量相同
识别自定义物品
// 方法一:PersistentDataContainer(推荐)
public static String getCustomId(ItemStack item) {
if (item == null || !item.hasItemMeta()) return null;
return item.getItemMeta().getPersistentDataContainer()
.get(ITEM_ID_KEY, PersistentDataType.STRING);
}
// 使用
String id = getCustomId(item);
if ("magic_apple".equals(id)) {
// 是魔法苹果
}
// 方法二:CustomModelData
public static boolean isSpecialItem(ItemStack item, int modelData) {
if (item == null || !item.hasItemMeta()) return false;
ItemMeta meta = item.getItemMeta();
return meta.hasCustomModelData() && meta.getCustomModelData() == modelData;
}
6.8 业务场景:武器升级系统
public class WeaponUpgradeSystem {
private static final NamespacedKey LEVEL_KEY = new NamespacedKey(plugin, "weapon_level");
private static final NamespacedKey XP_KEY = new NamespacedKey(plugin, "weapon_xp");
/**
* 给武器添加经验
*/
public static boolean addWeaponXP(ItemStack weapon, int xp) {
if (!isWeapon(weapon)) return false;
ItemMeta meta = weapon.getItemMeta();
if (meta == null) return false;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
int currentXP = pdc.getOrDefault(XP_KEY, PersistentDataType.INTEGER, 0);
int currentLevel = pdc.getOrDefault(LEVEL_KEY, PersistentDataType.INTEGER, 1);
int newXP = currentXP + xp;
int xpNeeded = getXPForLevel(currentLevel);
// 检查是否升级
if (newXP >= xpNeeded) {
newXP -= xpNeeded;
currentLevel++;
pdc.set(LEVEL_KEY, PersistentDataType.INTEGER, currentLevel);
// 更新 Lore
updateWeaponLore(meta, currentLevel, newXP);
weapon.setItemMeta(meta);
return true; // 升级了
}
pdc.set(XP_KEY, PersistentDataType.INTEGER, newXP);
updateWeaponLore(meta, currentLevel, newXP);
weapon.setItemMeta(meta);
return false;
}
private static int getXPForLevel(int level) {
return 100 + (level * 50); // 等级越高需要越多经验
}
private static void updateWeaponLore(ItemMeta meta, int level, int xp) {
int needed = getXPForLevel(level);
meta.lore(List.of(
Component.text("§7等级: §e" + level, NamedTextColor.GRAY),
Component.text("§7经验: §a" + xp + "§7/§a" + needed, NamedTextColor.GRAY),
Component.empty(),
Component.text("§7击杀怪物获得经验", NamedTextColor.GRAY)
));
}
}
6.9 物品序列化与反序列化
Base64 编码存储
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import java.io.*;
import java.util.Base64;
public final class ItemSerializer {
/**
* 物品序列化为 Base64
*/
public static String toBase64(ItemStack item) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
dataOutput.writeObject(item);
dataOutput.close();
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
/**
* Base64 反序列化为物品
*/
public static ItemStack fromBase64(String base64) throws IOException, ClassNotFoundException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(
Base64.getDecoder().decode(base64)
);
BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream);
ItemStack item = (ItemStack) dataInput.readObject();
dataInput.close();
return item;
}
}
背包序列化
/**
* 背包序列化为 Base64
*/
public static String inventoryToBase64(PlayerInventory inventory) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream);
// 写入物品数组长度
dataOutput.writeInt(inventory.getSize());
// 写入每个物品
for (int i = 0; i < inventory.getSize(); i++) {
dataOutput.writeObject(inventory.getItem(i));
}
dataOutput.close();
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
6.10 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| ItemMeta 为 null | AIR 物品没有 Meta | 先检查 item.getType() != AIR |
| 附魔不显示 | 未设置 glint | 添加附魔或使用 meta.addItemFlags |
| Lore 不更新 | 物品引用未更新 | item.setItemMeta(meta) 后使用新引用 |
| 自定义模型不生效 | 资源包未加载 | 确保客户端已应用资源包 |
| 物品丢失 | 物品栈被意外覆盖 | 克隆物品栈 item.clone() |
6.11 扩展阅读
6.12 本章小结
| 要点 | 内容 |
|---|---|
| ItemStack | 物品栈,包含材料类型和数量 |
| ItemMeta | 物品元数据,控制名称、Lore、附魔等 |
| PersistentDataContainer | Paper 推荐的自定义数据存储方式 |
| 自定义物品 | 使用工厂模式 + PDC 识别和创建 |
| 序列化 | Base64 编码存储物品数据 |
| Data Components | Paper 1.20.5+ 新 API,逐步替代 ItemMeta 部分功能 |
下一章: 第 7 章:背包与 GUI — 学习创建自定义 GUI 菜单、处理背包交互事件。