Bukkit PlayerMoveEvent如何实现玩家碰水时每20tick执行一次代码
问题原因
你的代码逻辑存在两个核心错误,直接导致执行频率不符合预期:
PlayerMoveEvent会在玩家位置发生任何变化时触发,玩家在水中移动时,单游戏刻内可能触发十几次甚至几十次该事件。你每次触发事件只要检测到玩家在水里,就会新建一个20游戏刻后执行的延时任务,短时间内会堆积海量重复的延时任务。- 没有做任何任务去重、冷却校验、资源清理逻辑:20游戏刻后,之前堆积的所有延时任务会陆续触发,最终变成每游戏刻都执行业务代码,甚至玩家离开水、退出服务器后,残留的任务还会继续运行。
修正方案
优先推荐第一种方案,性能开销最低,不会产生额外定时任务,逻辑最简单可控。
方案1:基于游戏刻的冷却标记实现
核心逻辑:用哈希表存储每个玩家上次执行水中业务逻辑的服务器游戏刻,每次移动事件触发时先校验冷却时间,只有间隔满20游戏刻才执行业务,不会创建多余定时任务。
修正后的完整代码:
package me.pgk.Listeners; import me.pgk.PGK; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; import java.util.HashMap; import java.util.UUID; public class WaterLavaDamage implements Listener { private final PGK plugin; // 存储玩家上次执行水中逻辑的游戏刻 private final HashMap<UUID, Long> lastExecuteTickMap = new HashMap<>(); private static final long INTERVAL_TICK = 20L; public WaterLavaDamage(PGK plugin) { this.plugin = plugin; plugin.getServer().getPluginManager().registerEvents(this, plugin); } @EventHandler public void onWaterMove(PlayerMoveEvent e) { Player player = e.getPlayer(); UUID uuid = player.getUniqueId(); long currentTick = plugin.getServer().getCurrentTick(); // 玩家不在水中,清理缓存直接返回 if (player.getLocation().getBlock().getType() != Material.WATER) { lastExecuteTickMap.remove(uuid); return; } // 首次进入水中初始化上次执行时间,等待20刻后第一次触发 long lastExecuteTick = lastExecuteTickMap.getOrDefault(uuid, currentTick); // 未到执行间隔直接跳过 if (currentTick - lastExecuteTick < INTERVAL_TICK) { return; } // 间隔满足,执行业务逻辑 player.sendMessage("Water Damage"); // 更新上次执行时间 lastExecuteTickMap.put(uuid, currentTick); } // 玩家退出时清理缓存,避免内存泄漏 @EventHandler public void onPlayerQuit(PlayerQuitEvent e) { lastExecuteTickMap.remove(e.getPlayer().getUniqueId()); } }
方案2:单例重复任务实现(适合重业务场景)
如果你的水中业务逻辑比较重,不适合绑定在高频触发的移动事件上,可以用单任务模式:
- 玩家第一次进入水中时,先判断是否已经为该玩家创建了运行中的水中逻辑任务,没有则创建一个每20游戏刻执行一次的重复任务,将任务ID存入哈希表
- 玩家离开水、退出服务器时,根据存储的任务ID取消对应定时任务,清理缓存
注意:该方案必须严格管理任务ID,避免任务残留造成内存泄漏和无效逻辑执行。
原代码避坑点
- 禁止在高频触发的事件(如移动事件、伤害事件)中无节制创建定时任务,必须做去重判断
- 涉及玩家的缓存数据必须在玩家离开触发场景、退出服务器时及时清理
- 固定间隔触发的逻辑优先用时间/刻差值做冷却判断,比堆叠延时任务稳定性高很多
内容的提问来源于stack exchange,提问作者PGK
相关产品推荐
相关产品推荐

