You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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); 
            } 
        } 
    } 
}

问题根源

  1. taskID变量覆盖:代码中taskID是局部变量,每次调用scheduleSyncDelayedTask都会覆盖之前的任务ID,导致无法追踪所有任务,更无法针对单个玩家管理任务。
  2. 无任务隔离机制:没有为每个玩家维护独立的任务集合,不同玩家的任务没有区分,当新玩家触发动画时,旧玩家的任务没有被正确管理,导致冲突。
  3. 缺少重复触发处理:玩家重复触发活动时,旧的任务会继续运行,和新任务同时修改玩家装备,导致动画中断。

修复方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 21:07:46