第 9 章:实体 API
第 9 章:实体 API
深入理解实体系统,掌握自定义生物、AI 装备和实体管理。
9.1 实体类型层级
Bukkit 的实体类遵循严格的继承层级:
Entity(最基础)
├── Projectile(弹射物)
│ ├── Arrow
│ ├── Snowball
│ └── Fireball
├── LivingEntity(活体实体)
│ ├── Player
│ ├── Mob(生物)
│ │ ├── Monster(怪物)
│ │ │ ├── Zombie
│ │ │ ├── Skeleton
│ │ │ ├── Creeper
│ │ │ └── Enderman
│ │ ├── Animals(动物)
│ │ │ ├── Cow
│ │ │ ├── Pig
│ │ │ └── Sheep
│ │ └── WaterMob(水生生物)
│ └── ArmorStand(盔甲架)
└── Item(掉落物)
常用实体类型表
| 类型 | 实体 | 典型用途 |
|---|---|---|
EntityType.ZOMBIE | 僵尸 | PVE 怪物 |
EntityType.SKELETON | 骷髅 | 远程怪物 |
EntityType.CREEPER | 苦力怕 | 自爆怪物 |
EntityType.VILLAGER | 村民 | 商店 NPC |
EntityType.ARMOR_STAND | 盔甲架 | 展示/装饰 |
EntityType.ITEM_FRAME | 物品展示框 | 展示物品 |
EntityType.MARKER | 标记器 | 无形体标记 |
EntityType.TEXT_DISPLAY | 文本展示 | 浮动文字 |
9.2 实体属性操作
生命值管理
// 获取实体
LivingEntity entity = ...;
// 获取/设置生命值
double health = entity.getHealth();
entity.setHealth(30.0); // 设置为 15 颗心
// 获取最大生命值
double maxHealth = entity.getMaxHealth();
// 设置最大生命值(通过 Attribute)
entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(100.0);
entity.setHealth(100.0); // 同步设置当前值
通用属性
| Attribute | 说明 | 默认值 |
|---|---|---|
MAX_HEALTH | 最大生命值 | 20.0 |
FOLLOW_RANGE | 跟踪范围 | 32.0 |
KNOCKBACK_RESISTANCE | 击退抗性 | 0.0 |
MOVEMENT_SPEED | 移动速度 | 0.7 |
ATTACK_DAMAGE | 攻击伤害 | 2.0 |
ARMOR | 护甲值 | 0.0 |
ARMOR_TOUGHNESS | 护甲韧性 | 0.0 |
ATTACK_KNOCKBACK | 攻击击退 | 0.0 |
FLYING_SPEED | 飞行速度 | 0.4 |
修改属性
// 修改攻击力
AttributeInstance attackAttr = entity.getAttribute(Attribute.ATTACK_DAMAGE);
if (attackAttr != null) {
attackAttr.setBaseValue(10.0); // 基础攻击力设为 10
}
// 添加修饰符(Modifier)
attackAttr.addModifier(new AttributeModifier(
NamespacedKey.minecraft("bonus_damage"),
5.0, // 增加 5 点
AttributeModifier.Operation.ADD_NUMBER // 直接相加
));
// 检查修饰符
if (attackAttr.getModifier(NamespacedKey.minecraft("bonus_damage")) != null) {
// 已有加成
}
9.3 生物装备系统
设置装备
public class MobEquipment {
/**
* 给生物设置装备
*/
public static void equipMob(LivingEntity mob, Material helmet,
Material chestplate, Material leggings,
Material boots, Material weapon) {
EntityEquipment equipment = mob.getEquipment();
if (helmet != null) equipment.setHelmet(new ItemStack(helmet));
if (chestplate != null) equipment.setChestplate(new ItemStack(chestplate));
if (leggings != null) equipment.setLeggings(new ItemStack(leggings));
if (boots != null) equipment.setBoots(new ItemStack(boots));
if (weapon != null) equipment.setItemInMainHand(new ItemStack(weapon));
// 设置装备掉落率(0.0 = 不掉落,1.0 = 100%掉落)
equipment.setHelmetDropChance(0.0f);
equipment.setChestplateDropChance(0.1f);
equipment.setItemInMainHandDropChance(0.5f);
}
/**
* 装备的 NBT 自定义
*/
public static void equipWithCustomWeapon(LivingEntity mob) {
ItemStack sword = new ItemStack(Material.DIAMOND_SWORD);
ItemMeta meta = sword.getItemMeta();
if (meta != null) {
meta.displayName(Component.text("§c暗黑之刃"));
meta.addEnchant(Enchantment.SHARPNESS, 5, true);
meta.lore(List.of(
Component.text("§7骷髅将军的武器", NamedTextColor.GRAY)
));
// 自定义属性
meta.addAttributeModifier(Attribute.ATTACK_DAMAGE,
new AttributeModifier(
NamespacedKey.minecraft("attack_damage"),
15.0,
AttributeModifier.Operation.ADD_NUMBER,
EquipmentSlotGroup.MAINHAND
));
sword.setItemMeta(meta);
}
mob.getEquipment().setItemInMainHand(sword);
}
}
装备掉落事件
@EventHandler
public void onEntityDeath(EntityDeathEvent event) {
LivingEntity entity = event.getEntity();
if (entity instanceof Zombie zombie) {
// 自定义掉落逻辑
event.getDrops().clear(); // 清除默认掉落
// 自定义掉落
if (zombie.getEquipment().getHelmet() != null) {
event.getDrops().add(new ItemStack(Material.DIAMOND, 2));
}
// 设置经验
event.setDroppedExp(50);
}
}
9.4 生物 AI 系统
Paper 的目标选择器(Goal System)
Paper 1.20+ 提供了原生的生物目标(Goal)API:
import io.papermc.paper.entity.ai.Goal;
import io.papermc.paper.entity.ai.GoalKey;
import io.papermc.paper.entity.ai.GoalType;
import io.papermc.paper.entity.ai.MobGoals;
// 获取 MobGoals API
MobGoals mobGoals = Bukkit.getMobGoals();
// 移除所有 AI 目标
mobGoals.removeAllGoals(zombie);
// 添加自定义目标
Goal<Zombie> patrolGoal = new PatrolGoal(zombie);
mobGoals.addGoal(zombie, 1, patrolGoal); // 优先级 1
自定义巡逻目标
import org.bukkit.entity.Zombie;
import org.jetbrains.annotations.NotNull;
import io.papermc.paper.entity.ai.Goal;
import io.papermc.paper.entity.ai.GoalKey;
import io.papermc.paper.entity.ai.GoalType;
import java.util.EnumSet;
import java.util.List;
public class PatrolGoal implements Goal<Zombie> {
private final Zombie zombie;
private final List<Location> patrolPoints;
private int currentPoint = 0;
private long lastMoveTime = 0;
public PatrolGoal(Zombie zombie) {
this.zombie = zombie;
this.patrolPoints = getPatrolRoute();
}
@Override
public boolean shouldActivate() {
return !patrolPoints.isEmpty();
}
@Override
public void tick() {
long now = System.currentTimeMillis();
if (now - lastMoveTime < 3000) return; // 每 3 秒移动一次
Location target = patrolPoints.get(currentPoint);
zombie.getPathfinder().moveTo(target, 1.0);
if (zombie.getLocation().distance(target) < 2.0) {
currentPoint = (currentPoint + 1) % patrolPoints.size();
}
lastMoveTime = now;
}
@Override
public @NotNull GoalKey<Zombie> getKey() {
return GoalKey.of(Zombie.class,
NamespacedKey.fromString("myplugin:patrol"));
}
@Override
public @NotNull EnumSet<GoalType> getTypes() {
return EnumSet.of(GoalType.MOVE);
}
}
禁用特定 AI
// 移除攻击目标
mobGoals.removeGoalOfType(zombie, GoalType.TARGET);
// 让僵尸不攻击玩家
mobGoals.addGoal(zombie, 0, new Goal<Zombie>() {
@Override
public boolean shouldActivate() {
return false; // 永不激活 = 禁用此目标
}
@Override
public void tick() {}
@Override
public @NotNull GoalKey<Zombie> getKey() {
return GoalKey.of(Zombie.class,
new NamespacedKey("myplugin", "no_target"));
}
@Override
public @NotNull EnumSet<GoalType> getTypes() {
return EnumSet.of(GoalType.TARGET);
}
});
9.5 盔甲架(ArmorStand)应用
盔甲架是服务端开发中非常重要的实体,常用于 GUI 展示和装饰。
创建展示 NPC
public class DisplayNPC {
/**
* 创建一个展示用的盔甲架 NPC
*/
public static ArmorStand createNPC(Location loc, String name) {
ArmorStand stand = loc.getWorld().spawn(loc, ArmorStand.class, as -> {
as.customName(Component.text(name));
as.customNameVisible(true);
// 不可见/不可交互
as.setVisible(false);
as.setGravity(false);
as.setBasePlate(false);
// 设置为小型
as.setSmall(false);
// 设置装备
as.getEquipment().setHelmet(new ItemStack(Material.ZOMBIE_HEAD));
as.getEquipment().setChestplate(new ItemStack(Material.DIAMOND_CHESTPLATE));
as.getEquipment().setItemInMainHand(new ItemStack(Material.DIAMOND_SWORD));
});
return stand;
}
/**
* 创建小型展示架
*/
public static ArmorStand createMiniDisplay(Location loc, ItemStack displayItem) {
return loc.getWorld().spawn(loc, ArmorStand.class, as -> {
as.setVisible(false);
as.setGravity(false);
as.setSmall(true);
as.setBasePlate(false);
as.setMarker(true); // 不可交互
as.getEquipment().setHelmet(displayItem);
});
}
}
9.6 文本展示与展示实体
Paper 1.19.4+ 支持新的展示实体,比盔甲架性能更好:
import org.bukkit.entity.TextDisplay;
import org.bukkit.entity.ItemDisplay;
import org.bukkit.entity.BlockDisplay;
// 文本展示
TextDisplay textDisplay = world.spawn(loc, TextDisplay.class, td -> {
td.text(Component.text("§6欢迎来到服务器!")
.append(Component.newline())
.append(Component.text("§7享受你的冒险吧!")));
td.setBillboard(Display.Billboard.CENTER); // 始终面向玩家
td.setLineWidth(200);
td.setBackgroundColor(Color.fromARGB(128, 0, 0, 0)); // 半透明黑色背景
td.setShadowed(true);
});
// 物品展示
ItemDisplay itemDisplay = world.spawn(loc, ItemDisplay.class, id -> {
id.setItemStack(new ItemStack(Material.DIAMOND_SWORD));
id.setBillboard(Display.Billboard.FIXED);
id.setItemDisplayTransform(ItemDisplay.ItemDisplayTransform.GUI);
});
// 方块展示
BlockDisplay blockDisplay = world.spawn(loc, BlockDisplay.class, bd -> {
bd.setBlock(Material.DIAMOND_BLOCK.createBlockData());
bd.setTransformation(new Transformation(
new Vector3f(0, 0, 0), // 平移
new AxisAngle4f(0, 0, 1, 0), // 旋转
new Vector3f(2, 2, 2), // 缩放(放大 2 倍)
new AxisAngle4f(0, 0, 1, 0)
));
});
9.7 村民 NPC 系统
创建自定义村民
public class VillagerNPC {
/**
* 创建商店村民
*/
public static Villager createShopNPC(Location loc, String shopName) {
return loc.getWorld().spawn(loc, Villager.class, v -> {
v.customName(Component.text("§6" + shopName));
v.customNameVisible(true);
// 设置职业
v.setProfession(Villager.Profession.WEAPONSMITH);
// 设置外观
v.setVillagerType(Villager.PlainsType.PLAINS);
// 设置等级
v.setVillagerLevel(5);
// 不可被攻击
v.setInvulnerable(true);
// 不会消失
v.setRemoveWhenFarAway(false);
// 禁止 AI(不会乱走)
v.setAI(false);
// 设置交易(通过 API)
List<MerchantRecipe> recipes = new ArrayList<>();
recipes.add(createRecipe(
new ItemStack(Material.EMERALD, 5),
new ItemStack(Material.DIAMOND_SWORD),
10 // 最大使用次数
));
v.setRecipes(recipes);
});
}
private static MerchantRecipe createRecipe(ItemStack cost, ItemStack result,
int maxUses) {
MerchantRecipe recipe = new MerchantRecipe(result, maxUses);
recipe.addIngredient(cost);
return recipe;
}
}
村民交易事件
@EventHandler
public void onVillagerTrade(VillagerTradeEvent event) {
Villager villager = event.getEntity();
Player player = (Player) event.getPlayer();
ItemStack result = event.getResult();
// 记录交易日志
getLogger().info(player.getName() + " 与 " + villager.getName()
+ " 交易了 " + result.getType());
// 自定义逻辑:VIP 折扣
if (player.hasPermission("myplugin.vip")) {
// 给予额外奖励
player.sendMessage("§6VIP 折扣!额外获得经验!");
player.giveExp(10);
}
}
9.8 实体管理与清理
实体计数与限制
public class EntityManager {
/**
* 获取指定区域内的实体数量
*/
public static int countEntities(World world, int radius, EntityType type) {
Location center = world.getSpawnLocation();
return (int) world.getNearbyEntities(center, radius, radius, radius).stream()
.filter(e -> e.getType() == type)
.count();
}
/**
* 清理多余实体
*/
public static int cleanupEntities(World world, EntityType type, int maxCount) {
List<Entity> entities = world.getEntities().stream()
.filter(e -> e.getType() == type)
.filter(e -> !(e instanceof Player))
.collect(Collectors.toList());
if (entities.size() <= maxCount) return 0;
int removed = 0;
int toRemove = entities.size() - maxCount;
// 按距离排序,移除距离玩家最远的
entities.sort(Comparator.comparingDouble(e ->
e.getNearbyEntities(100, 100, 100).stream()
.filter(n -> n instanceof Player)
.mapToDouble(n -> n.getLocation().distanceSquared(e.getLocation()))
.min().orElse(Double.MAX_VALUE)));
for (int i = entities.size() - 1; i >= 0 && removed < toRemove; i--) {
entities.get(i).remove();
removed++;
}
return removed;
}
}
实体自定义名称与可见性
// 设置自定义名称
entity.customName(Component.text("§c精英僵尸"));
entity.customNameVisible(true); // 始终显示名称
// Paper 的自定义名称样式
entity.customName(Component.text("BOSS")
.color(NamedTextColor.RED)
.decorate(TextDecoration.BOLD));
// 设置发光效果
entity.setGlowing(true);
9.9 实体传送与跟随
// 实体传送
entity.teleport(location);
// 让实体面向目标
Location lookAt = target.getLocation();
entity.teleport(entity.getLocation().setDirection(
lookAt.toVector().subtract(entity.getLocation().toVector())
));
// 实体跟随玩家(使用 Pathfinder)
if (entity instanceof Mob mob) {
mob.getPathfinder().moveTo(player, 1.5); // 速度 1.5
}
9.10 业务场景:BOSS 系统
public class BossManager {
private static final String BOSS_NAME = "§4§l暗黑龙王";
/**
* 生成 BOSS
*/
public static EnderDragon spawnBoss(Location loc) {
return loc.getWorld().spawn(loc, EnderDragon.class, dragon -> {
dragon.customName(Component.text(BOSS_NAME));
dragon.customNameVisible(true);
// 设置属性
dragon.getAttribute(Attribute.MAX_HEALTH).setBaseValue(200.0);
dragon.setHealth(200.0);
dragon.getAttribute(Attribute.ATTACK_DAMAGE).setBaseValue(15.0);
// 不会自然消失
dragon.setRemoveWhenFarAway(false);
// 发光
dragon.setGlowing(true);
});
}
/**
* BOSS 血条管理
*/
public static void updateBossBar(BossBar bossBar, EnderDragon dragon) {
double progress = dragon.getHealth() / dragon.getMaxHealth();
bossBar.progress(Math.max(0, Math.min(1, progress)));
if (progress > 0.6) {
bossBar.color(BarColor.GREEN);
} else if (progress > 0.3) {
bossBar.color(BarColor.YELLOW);
} else {
bossBar.color(BarColor.RED);
}
}
}
9.11 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 实体 AI 不生效 | setAI(false) 会禁用所有 AI | 只移除特定目标 |
| 实体穿墙 | 碰撞箱问题 | 使用 setCollidable() |
| 盔甲架消失 | 被攻击致死 | setInvulnerable(true) |
| 实体过多导致卡顿 | 未设置清理机制 | 定时清理 + 限制生成数量 |
| 自定义名称不显示 | customNameVisible 为 false | 设为 true |
9.12 扩展阅读
9.13 本章小结
| 要点 | 内容 |
|---|---|
| 实体层级 | Entity → LivingEntity → Mob → 具体类型 |
| 属性系统 | Attribute + AttributeModifier 控制战斗属性 |
| 装备系统 | EntityEquipment 设置装备和掉落率 |
| AI 系统 | Paper MobGoals API,自定义 Goal |
| 展示实体 | TextDisplay / ItemDisplay 比盔甲架性能更好 |
| 实体管理 | 定时清理、限制数量、分批处理 |
下一章: 第 10 章:计分板 — 学习计分板、侧边栏、标签和 Teams 系统。