如何修复Bukkit Scheduler双玩家触发时的动画中断问题
Bukkit调度器多玩家动画冲突问题修复
问题描述
开发Minecraft插件时,使用Bukkit Scheduler实现玩家动画效果,当两名玩家同时触发带有调度器的活动时,先触发的玩家动画会中断,仅最后触发的玩家能正常运行动画。尝试过为玩家分配数据库随机ID关联调度器,但问题依旧。
问题复现代码
public class AutoNick implements Listener { Player player; @EventHandler public void handlePlayerNick(PlayerInteractEvent event) { Player player = event.getPlayer(); int taskID = CreateDataPlayer.getPlayerID(player.getUniqueId()); if (event.getAction() == Action.RIGHT_CLICK_BLOCK || event.getAction() == Action.RIGHT_CLICK_AIR) { if (event.getItem().getItemMeta().getDisplayName().equals(StringList.inv_player_i1)) { taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), new Runnable() { @Override public void run() { player.getInventory().setBoots(new LootBuilder(Material.GOLD_BOOTS, 1).createDisplayName("§6Aktiviere AutoNick...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); } }, 20L); taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), new Runnable() { @Override public void run() { player.getInventory().setLeggings(new LootBuilder(Material.GOLD_LEGGINGS, 1).createDisplayName("§6Lade Datenbank...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); } }, 40L); taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), new Runnable() { @Override public void run() { player.getInventory().setChestplate(new LootBuilder(Material.GOLD_CHESTPLATE, 1).createDisplayName("§6Lade Skin...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); } }, 60L); taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), new Runnable() { @Override public void run() { player.getInventory().setHelmet(new LootBuilder(Material.GOLD_HELMET, 1).createDisplayName("§6Übertrage Namen...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); } }, 80L); int finalTaskID = taskID; taskID = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), new Runnable() { @Override public void run() { player.playSound(player.getLocation(), Sound.LEVEL_UP, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); player.getInventory().setHelmet(new LootBuilder(Material.SNOW_BALL, 1).createDisplayName("§fNo animation..").build()); player.getInventory().setChestplate(new LootBuilder(Material.SNOW_BALL, 1).createDisplayName("§fNo animation..").build()); player.getInventory().setLeggings(new LootBuilder(Material.SNOW_BALL, 1).createDisplayName("§fNo animation..").build()); player.getInventory().setBoots(new LootBuilder(Material.SNOW_BALL, 1).createDisplayName("§fNo animation..").build()); player.sendMessage(StringList.nicknames_prefix + "§cKonnte keine Namen/Skins aus Datenbank laden"); } }, 100L); } } } }
问题根源
- taskID变量覆盖:代码中
taskID是局部变量,每次调用scheduleSyncDelayedTask都会覆盖之前的任务ID,导致无法追踪所有任务,更无法针对单个玩家管理任务。 - 无任务隔离机制:没有为每个玩家维护独立的任务集合,不同玩家的任务没有区分,当新玩家触发动画时,旧玩家的任务没有被正确管理,导致冲突。
- 缺少重复触发处理:玩家重复触发活动时,旧的任务会继续运行,和新任务同时修改玩家装备,导致动画中断。
修复方案
1. 为每个玩家维护独立任务集合
使用HashMap<UUID, List<Integer>>存储每个玩家的所有调度任务ID,确保任务按玩家隔离。
2. 触发新动画前取消旧任务
在玩家触发活动时,先取消该玩家所有未完成的任务,避免新旧任务冲突。
3. 追踪所有任务ID
每个调度任务的ID都添加到对应玩家的任务列表中,不再覆盖同一个变量。
4. 添加空指针安全检查
避免event.getItem()或itemMeta为null时抛出异常。
修改后的代码
import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; public class AutoNick implements Listener { // 存储每个玩家的所有调度任务ID,按UUID隔离 private static final HashMap<UUID, List<Integer>> playerTasks = new HashMap<>(); @EventHandler public void handlePlayerNick(PlayerInteractEvent event) { Player player = event.getPlayer(); UUID playerUUID = player.getUniqueId(); // 取消该玩家之前的所有未完成任务,避免冲突 if (playerTasks.containsKey(playerUUID)) { for (int taskId : playerTasks.get(playerUUID)) { Bukkit.getScheduler().cancelTask(taskId); } playerTasks.remove(playerUUID); } // 空指针检查:确保物品和物品元数据不为null if ((event.getAction() == PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK || event.getAction() == PlayerInteractEvent.Action.RIGHT_CLICK_AIR) && event.getItem() != null && event.getItem().getItemMeta() != null) { if (event.getItem().getItemMeta().getDisplayName().equals(StringList.inv_player_i1)) { List<Integer> tasks = new ArrayList<>(); // 任务1:20tick后设置靴子 int task1 = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), () -> { player.getInventory().setBoots(new LootBuilder(Material.GOLD_BOOTS, 1) .createDisplayName("§6Aktiviere AutoNick...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); }, 20L); tasks.add(task1); // 任务2:40tick后设置护腿 int task2 = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), () -> { player.getInventory().setLeggings(new LootBuilder(Material.GOLD_LEGGINGS, 1) .createDisplayName("§6Lade Datenbank...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); }, 40L); tasks.add(task2); // 任务3:60tick后设置胸甲 int task3 = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), () -> { player.getInventory().setChestplate(new LootBuilder(Material.GOLD_CHESTPLATE, 1) .createDisplayName("§6Lade Skin...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); }, 60L); tasks.add(task3); // 任务4:80tick后设置头盔 int task4 = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), () -> { player.getInventory().setHelmet(new LootBuilder(Material.GOLD_HELMET, 1) .createDisplayName("§6Übertrage Namen...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); }, 80L); tasks.add(task4); // 任务5:100tick后清理装备并提示 int task5 = Bukkit.getScheduler().scheduleSyncDelayedTask(LobbySystem.getInstance(), () -> { player.playSound(player.getLocation(), Sound.LEVEL_UP, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); ItemStack placeholder = new LootBuilder(Material.SNOW_BALL, 1) .createDisplayName("§fNo animation..").build(); player.getInventory().setHelmet(placeholder); player.getInventory().setChestplate(placeholder); player.getInventory().setLeggings(placeholder); player.getInventory().setBoots(placeholder); player.sendMessage(StringList.nicknames_prefix + "§cKonnte keine Namen/Skins aus Datenbank laden"); // 任务完成后移除玩家的任务列表,释放内存 playerTasks.remove(playerUUID); }, 100L); tasks.add(task5); // 将该玩家的任务列表存入Map playerTasks.put(playerUUID, tasks); } } } }
额外优化建议
可以使用BukkitRunnable替代直接调用scheduleSyncDelayedTask,代码可读性和任务管理更方便:
new BukkitRunnable() { @Override public void run() { // 任务逻辑 player.getInventory().setBoots(new LootBuilder(Material.GOLD_BOOTS, 1) .createDisplayName("§6Aktiviere AutoNick...").build()); player.playSound(player.getLocation(), Sound.EXPLODE, 0.5F, 1.2F); player.playEffect(player.getLocation(), Effect.EXPLOSION_LARGE, 255); } }.runTaskLater(LobbySystem.getInstance(), 20L);
这种方式可以直接调用cancel()方法取消单个任务,也可以将BukkitRunnable实例存储到玩家的任务列表中,管理更灵活。
内容的提问来源于stack exchange,提问作者ALTF99
相关产品推荐
相关产品推荐

