本帖最后由 HuaiShu. 于 2022-9-24 19:19 编辑
我把我的世界玩家人头用数字来代替,怎么写才能使用玩家人头这个数字来打开第二页GUI面板.
编程使用的版本的我的世界:1.12
编程用软件是:idea
我把我的世界玩家人头用数字来代替,怎么写才能使用玩家人头这个数字来打开第二页GUI面板.
编程使用的版本的我的世界:1.12
编程用软件是:idea
本帖最后由 美年达呀 于 2022-9-25 11:53 编辑
标题和内容意义不明
大概理解为在一个Gui窗体中,点击一个物品达到跳转Gui的效果
考虑 ClickInventoryEvent 事件中判断一下打开的窗体的是否为当前你的这个Gui的窗体,同时对相关Gui的Slot做一下监听!
如何达到跳转页面也很简单,在ClickInventoryEvent 在判断点击的窗体的格子是否是你这个玩家头的格子!如果是则通过Player.openInventory(Inventory)打开即可!
标题和内容意义不明
大概理解为在一个Gui窗体中,点击一个物品达到跳转Gui的效果
考虑 ClickInventoryEvent 事件中判断一下打开的窗体的是否为当前你的这个Gui的窗体,同时对相关Gui的Slot做一下监听!
校验方法是否是你这个人头窗体很简单:低版本直接GetTitle比对一下就行
如何达到跳转页面也很简单,在ClickInventoryEvent 在判断点击的窗体的格子是否是你这个玩家头的格子!如果是则通过Player.openInventory(Inventory)打开即可!
美年达呀 发表于 2022-9-25 11:52
标题和内容意义不明
大概理解为在一个Gui窗体中,点击一个物品达到跳转Gui的效果
考虑 ClickInventoryEvent ...
这个要怎么写呀,大佬可以教一下吗
我初步的看了一下您的要求,似乎不是很清楚那么接下来我将以我所理解的进行回答
我将初步理解为您想要在一个已有的GUI内跳转到其他的GUI
首先,我们需要监听一个事件,监听玩家点击背包的事件 (InventoryClickEvent)
接下来 我们需要一些安全措施 来防止意外情况
接下来,我们要获取玩家所点击的插槽 (slot)
接下来,我们需要进行判断,判断玩家所点击的插槽,是否为你需要的点击位置
若玩家所点击的插槽为1 那么将打开其他的inv
一些建议:
1. 在gui内跳转到另一个gui时可以不关闭玩家的背包,这将会有更加丝滑的效果,即玩家的鼠标位置不会重置
2. 如果在旧gui中有计划任务,如物品更新等,那么请使用一些手段来判断玩家是否不在旧gui中,若不在了则对此计划任务进行cancel 否则此任务将一直运行
我知道这个回答可能非常迟, 希望对您有所帮助 :)
我将初步理解为您想要在一个已有的GUI内跳转到其他的GUI
首先,我们需要监听一个事件,监听玩家点击背包的事件 (InventoryClickEvent)
接下来 我们需要一些安全措施 来防止意外情况
if (event.getCurrentItem() == null || event.getInventory() == null || event.getWhoClicked() == null || event.getClick() == null) return;
接下来,我们要获取玩家所点击的插槽 (slot)
int rSlot = event.getRawSlot();
接下来,我们需要进行判断,判断玩家所点击的插槽,是否为你需要的点击位置
if (rSlot == 1) player.openInventory();
若玩家所点击的插槽为1 那么将打开其他的inv
一些建议:
1. 在gui内跳转到另一个gui时可以不关闭玩家的背包,这将会有更加丝滑的效果,即玩家的鼠标位置不会重置
2. 如果在旧gui中有计划任务,如物品更新等,那么请使用一些手段来判断玩家是否不在旧gui中,若不在了则对此计划任务进行cancel 否则此任务将一直运行
我知道这个回答可能非常迟, 希望对您有所帮助 :)
2000000 发表于 2022-10-24 15:10
我初步的看了一下您的要求,似乎不是很清楚那么接下来我将以我所理解的进行回答
我将初步理解为您想要在一 ...
你好,你的第二点建议中的手段指哪些,能举一些例子吗
本帖最后由 2000000 于 2022-10-25 13:42 编辑
好的。
接下来我会以我的想法回答您的问题,我会尽量避免代码投喂,而是让您明白您在写什么东西
三英战吕布 发表于 2022-10-24 20:33
你好,你的第二点建议中的手段指哪些,能举一些例子吗
好的。
接下来我会以我的想法回答您的问题,我会尽量避免代码投喂,而是让您明白您在写什么东西
首先,让我们制作两个 menu 类,以及两个打开 menu 的 command.
接下来,我将称呼这两个 menu 为 menu1 与 menu2, cmd1 与 cmd2
cmd1 将打开 menu1 菜单, cmd2 将打开 menu2 菜单。
首先,让我们写完这些 menu 以及 commands.
menu:
public void openMenu1(Player player) {Inventory menu1 = Bukkit.createInventory(player, 9, "menu1");ItemStack item1 = new ItemStack(Material.DIAMOND_SWORD,1);ItemMeta meta = item1.getItemMeta();new BukkitRunnable() {int i = 0;@Overridepublic void run() {meta.setDisplayName(String.valueOf(i));item1.setItemMeta(meta);menu1.setItem(1, item1);Bukkit.getLogger().info("[menu1] set done");i ++;}}.runTaskTimerAsynchronously(main.instance, 0, 20);player.openInventory(menu1);}public void openMenu2(Player player) {Inventory menu2 = Bukkit.createInventory(player, 9, "menu2");ItemStack item1 = new ItemStack(Material.WOOD_SWORD,1);ItemMeta meta = item1.getItemMeta();new BukkitRunnable() {int i = 0;@Overridepublic void run() {meta.setDisplayName("menu2 " + i);item1.setItemMeta(meta);menu2.setItem(1, item1);Bukkit.getLogger().info("[menu2] set done");i ++;}}.runTaskTimerAsynchronously(main.instance, 0, 20);player.openInventory(menu2);}
注册指令:
getCommand("cmd1").setExecutor(new cmd1());
getCommand("cmd2").setExecutor(new cmd2());
首先我们需要避免一个问题,那就是尽量的不使用 sout (System.out.println) 输出文本,而是使用 Bukkit.getLogger 进行输出
System.out.println 是一个同步方法,在高并发的情况下,会严重影响性能
详细请自行查看源码 源码中含有 synchronized 同步锁
让我们来理解一下上面所写的代码:
我们创建了一个 Inventory 名为 menu1 大小为 9 格 标题为 menu1
随后 我们创建了一个 ItemStack 名为 item1 为钻石剑 数量为 1
我们获取了 item1 的 ItemMeta 名为 meta
随后,我们创建了一个计划任务,首先我们定义了 int 变量 i 为 0
此任务执行的内容为: 设置 meta 的名字为 i (setDisplayName 需要传入 String 作为名字,但是 i 为 int 类型,所以我们需要使用 String.valueOf(i) 转换为 String 类型)
随后,我们设置了 item1 的 ItemMeta 为 meta 并且将 menu1 的 1 插槽设置为了物品 item1
设置完成后,我们使用 Bukkit.getLogger().info 向控制台输出信息 并且将 i 的数值增加 每20ticks执行一次 (20ticks = 1s)
随后打开背包
menu2 与 menu1 相似,只不过物品不同,与信息内容不同
接下来让我们构建出来 并在游戏内进行测试。
可以看到 物品的更新是没有任何问题的
控制台的消息也没有任何问题
接下来,让我们关闭背包,我们可以发现,这个计划任务依然在进行,它依然在控制台不停的输出消息。哪怕我们已经离线,此计划任务依然在继续运行。
如果玩家进行多次这样的操作,那么这将会是一个比较严重的效率问题,接下来我们使用 cancel 来尝试解决在这个问题
首先我们需要判断此玩家是否离线,如果离线的话,则直接 cancel 此计划任务
if (!player.isOnline()) {
cancel();
return;
}
当玩家处于离线状态,那么我们 cancel 此计划任务
并且使用 return 来避免剩余的代码被执行 (若只是cancel 依然会执行剩余代码)
接下来,我们需要创建一个 HashMap 来保存其玩家的状态 (是否开启或关闭背包) 并在计划任务内对玩家的状态进行判断 具体如下
我们先创建一个 InventoryManager 类 并创建一个 HashMap 来存储, 并写两个方法来获取以及设置玩家状态 如下:
public class InventoryManager {
private static final Map<UUID, Boolean> closedInv = new HashMap<>();
public static boolean getClosedInv(Player player) {
return closedInv.getOrDefault(player.getUniqueId(), true); // getOrDefault 意思就是当Map中有这个key时 就查找key对应的value值 如果没有就使用默认值 即 true
}
public static void setClosedInv(Player player, boolean flag, Plugin plugin) {
// 由于hashmap 线程不是安全的 所以我们需要同步执行put
Bukkit.getScheduler().runTask(plugin, () -> closedInv.put(player.getUniqueId(), flag)); // put 插入 注: 一个key所对应的value值只会有一个,所以如果玩家在put之前已经拥有value 则会直接覆盖 不用先remove
}
}
HashMap 内存储玩家的UUID 以及 布尔值
请注意: HashMap不是线程安全的
接下来,我们就需要在玩家开启带有 计划任务 的 gui 时,将该玩家的 closedInv 设置为 false
随后,当此玩家关闭背包时 (InventoryCloseEvent) 我们需要设置此玩家的 closedInv 为true
并且在计划任务内,对此玩家的 closedInv 状态进行判断,若为 true 则 cancel 并 return
这看起来非常简单,让我们自己动手写一下
当玩家关闭 Inventory 的时候,我们赋予 true 如下
@EventHandler
public void onPlayerCloseInventory(InventoryCloseEvent event) {
InventoryManager.setClosedInv((Player) event.getPlayer(), true, main.instance);
}
计划任务则可以这样写:
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (!player.isOnline() || InventoryManager.getClosedInv(player)) {
cancel();
return;
}
meta.setDisplayName(String.valueOf(i));
item1.setItemMeta(meta);
menu1.setItem(1, item1);
Bukkit.getLogger().info("[menu1] set done");
i ++;
}
}.runTaskTimerAsynchronously(main.instance, 20, 20); // 这里第二个参数为等待多少ticks 开始执行这个任务 第三个为间隔
为什么要等待20ticks才执行这个计划任务?
(我们假设,有一个玩家A 打开了带有计划任务的gui 当玩家在关闭此gui的时候 我们put了一个true)
(由于此计划任务在设置为false 的前面,也就是说如果不延迟,那么这个计划任务检查到玩家A的 closedInv 则是false 这将意味着这个gui还没打开 但是计划任务就已经结束了)
接下来,我们要在玩家开启带有计划任务的gui时 给予一个false
player.openInventory(menu1);
InventoryManager.setClosedInv(player, false, main.instance);
接下来 我们已经完成了 menu1 的完整代码如下:
public void openMenu1(Player player) {
Inventory menu1 = Bukkit.createInventory(player, 9, "menu1");
ItemStack item1 = new ItemStack(Material.DIAMOND_SWORD,1);
ItemMeta meta = item1.getItemMeta();
new BukkitRunnable() {
int i = 0;
@Override
public void run() {
if (!player.isOnline() || InventoryManager.getClosedInv(player)) {
cancel();
return;
}
meta.setDisplayName(String.valueOf(i));
item1.setItemMeta(meta);
menu1.setItem(1, item1);
Bukkit.getLogger().info("[menu1] set done");
i ++;
}
}.runTaskTimerAsynchronously(main.instance, 20, 20);
player.openInventory(menu1);
InventoryManager.setClosedInv(player, false, main.instance);
}
因为我写这个回复的时候脑袋不是非常清醒,可能有一些地方不够细致,望谅解
希望能够帮到您 :)
简单看了下 我理解为你需要做一个可以翻页的gui
如果是 则可以通过自定义InventoryHolder来实现你所说的功能
具体代码手机打起来太费劲 这里放几个比较关键的点
首先新建类并继承InventoryHolder
然后声明int page作为页数标记
然后在构建函数中初始化page为1或者传入的参数
最后在创建一个新的inventory是使用该自定义holder 比如
Bukkit.createInventory(new MyHolder(),27,title)
当要判定当前页数时就先获取holder
inv.getHolder().getPage()
P.S. 建议构造函数传入page 当需要其他参数时最好写方法来修改参数
如果是 则可以通过自定义InventoryHolder来实现你所说的功能
具体代码手机打起来太费劲 这里放几个比较关键的点
首先新建类并继承InventoryHolder
然后声明int page作为页数标记
然后在构建函数中初始化page为1或者传入的参数
最后在创建一个新的inventory是使用该自定义holder 比如
Bukkit.createInventory(new MyHolder(),27,title)
当要判定当前页数时就先获取holder
inv.getHolder().getPage()
P.S. 建议构造函数传入page 当需要其他参数时最好写方法来修改参数
2000000 发表于 2022-10-25 13:41
好的。
接下来我会以我的想法回答您的问题,我会尽量避免代码投喂,而是让您明白您在写什么东西
感谢dalao,明白了