Bukkit开发:如何创建带AI、移动到指定位置后无操作的NPC
Bukkit实现仅定向移动的AI NPC方案
核心逻辑是生成带寻路能力的生物实体,屏蔽所有原生无关AI行为,仅触发定向寻路,到达目标点后清空寻路路径、固定实体状态即可,不需要引入第三方NPC插件也能实现。
1. 生成基础NPC实体
选择自带原生寻路能力的Creature类型实体(比如村民、僵尸、流浪者这类,不要选盔甲架、掉落物这类无移动能力的实体),生成后添加专属标签方便后续识别,避免和原生生物混淆。
// 替换成你需要的NPC生成位置 Location spawnLocation = new Location(目标世界, 生成X, 生成Y, 生成Z); // 这里以村民为例,可替换为其他Creature子类对应的EntityType Creature moveNpc = (Creature) spawnLocation.getWorld().spawnEntity(spawnLocation, EntityType.VILLAGER); // 添加专属识别标签 moveNpc.addScoreboardTag("single_move_npc"); // 可选:设置NPC名称 moveNpc.setCustomName("指引村民"); moveNpc.setCustomNameVisible(true);
2. 屏蔽原生多余AI行为
不要直接调用setAI(false),该方法会完全关闭实体寻路能力,导致后续移动逻辑失效。优先通过事件拦截所有和定向移动无关的行为:
- 拦截NPC锁定实体、攻击、闲逛的目标事件
- 拦截NPC受到的所有伤害,避免被攻击、爆炸击飞偏离路径
- 拦截NPC的自然消失逻辑,防止距离玩家过远被服务端清除
// 把这些监听器注册到你的插件中即可 @EventHandler public void onNpcTarget(EntityTargetEvent event) { if (event.getEntity().getScoreboardTags().contains("single_move_npc")) { event.setCancelled(true); } } @EventHandler public void onNpcDamage(EntityDamageEvent event) { if (event.getEntity().getScoreboardTags().contains("single_move_npc")) { event.setCancelled(true); } } @EventHandler public void onNpcDespawn(EntityUnleashEvent event) { if (event.getEntity().getScoreboardTags().contains("single_move_npc")) { event.setCancelled(true); } }
如果对性能要求更高,可以通过NMS清除实体原生的所有PathfinderGoal,仅保留移动相关的底层寻路逻辑,适配对应服务端版本即可。
3. 触发定向寻路
调用实体自带的寻路接口,设置目标位置和移动速度即可让NPC自动通过原生寻路绕开障碍移动到目标点:
// 替换成你需要NPC到达的目标位置 Location targetLocation = new Location(目标世界, 目标X, 目标Y, 目标Z); // 移动速度建议设置在0.2-0.4区间,对应普通生物的步行速度,可根据需求调整 double moveSpeed = 0.3; // 启动寻路 moveNpc.getPathfinder().moveTo(targetLocation, moveSpeed);
4. 到达目标后的静止处理
添加定时检测任务,判断NPC是否到达目标位置,到达后立即清空寻路,避免NPC继续闲逛或者偏移位置:
// 启动主线程定时检测,每2游戏tick(约100毫秒)检测一次 BukkitTask moveCheckTask = Bukkit.getScheduler().runTaskTimer(你的插件实例, () -> { // 实体失效时直接取消任务,避免空指针报错 if (moveNpc.isDead() || !moveNpc.isValid()) { this.cancel(); return; } Location npcCurrentLoc = moveNpc.getLocation(); // 计算和目标点的水平距离平方(开平方运算耗性能,直接用平方值判断) double horizontalDistanceSq = Math.pow(npcCurrentLoc.getX() - targetLocation.getX(), 2) + Math.pow(npcCurrentLoc.getZ() - targetLocation.getZ(), 2); // 水平距离小于0.5格时判定为到达目标点 if (horizontalDistanceSq < 0.25) { // 清空所有寻路路径 moveNpc.getPathfinder().stopPathfinding(); // 可选:关闭碰撞,避免被其他实体、水流推离位置 moveNpc.setCollidable(false); // 不需要浮空就保留重力,需要完全固定位置可设置setGravity(false) // 取消检测任务 this.cancel(); } }, 0L, 2L);
注意事项
- 目标位置必须处于已加载区块内,跨长距离移动时建议逐段设置路径点,提前加载对应区块,否则寻路会直接失效
- 不要高频重复调用
moveTo方法,会导致服务端寻路计算压力过大,出现NPC卡顿、抽搐的问题 - 如果需要NPC上下楼梯、跨越台阶,目标点的Y轴坐标尽量和路径终点的地面高度对齐,避免出现NPC卡墙、跳不动的问题
内容的提问来源于stack exchange,提问作者Silvan
相关产品推荐
相关产品推荐

