tdiant
本帖最后由 tdiant 于 2023-3-20 19:26 编辑

内容具有主观输出的成分,非客观讲述。
下面的内容,默认你已经从其他教程看过怎么做箱子GUI了。

一、啥是Inventory

首先我们要解决一个前置问题,究竟啥是Inventory。
你可能觉得我问的这个问题有点**,但是你先别急,让我们来查查这个单词的意思。



我们很明显能发现,inventory这个单词的意思是库存的意思。
实际上,我发现有很多教程或者开发者都把Inventory叫做背包或者容器,我认为这是具有极强的误导性质的,根本就不该是这样理解的。
我觉得应该这样理解:Inventory就是用来存储ItemStack的仓库而已。Inventory类实际上有很多子类,比如PlayerInventory类,它用来表示玩家的背包对应的“仓库”。
在通常的教程里,我们经常关注Inventory的title和size。这两位都是老朋友了,它们究竟是什么含义,就不一一介绍了。

这里的size很有意思,因为可能会有人告诉你,他的值必须是9的倍数,还只能是9到1至6倍,因为相对于箱子而言,一行有9个物品格,意味着它只能是9的倍数,而最多只能有6行,意味着最多也就只能设置成6*9=54这么大。
然而Inventory是个通融性很强的类,诸如熔炉、酿造台之类的方块也能存储物品啊,也需要一个所谓的“仓库”,我们显然能发现这些物品的GUI里物品格并不是9的倍数。
所以我们可以知道,这里9的倍数的概念并不是绝对的。必须是9的倍数的原因,是因为我们创建的GUI往往是箱子GUI。你要是想自己整个酿造台GUI的话,那你就应该创建对应size的Inventory才行。


如果你真的自己创建过一个Inventory对象,肯定会知道它应该怎么创建。嗯,用 Bukkit.createInventory方法创建。
我们通常要创建的GUI是箱子的Inventory,所以我们经常使用到这样的方法:


注:注意这里使用这个方法的原因,是因为我要使用的是箱子的Inventory,所以选择了这个方法。如果你要使用别的Inventory,可以选择其他带有InventortyType参数的createInventory。

后面两个参数一个是size,一个是title,我没啥好说的。
但是通常教程会让你把第一个参数设置成null,这实际上是有问题的。不知道有没有人告诉过你,InventoryHolder是什么。
这篇文章的主要目的,其实就是想给你介绍InventoryHolder这个东西。

二、啥是InventoryHolder

如果你仔细看了上面那个createInventory方法的参数名的话,你会发现第一个参数叫owner。owner是啥意思呢?


是的,owner的意思是主人的意思。意思就是说,每个Inventory对象都可以设置一个主人。
举个例子,Player类有个方法叫getInventory,返回的是一个PlayerInventory对象,表示玩家背包这个物品“仓库”,毫无疑问,它的主人显然是这个Player对象。
实际上如果你查一下的话,你会发现Player类本来就实现了InventoryHolder接口。


这意味着,所有Player对象都可以做其他Inventory的主人。
能做Inventory主人的类,都会实现这个InventoryHolder接口。Holder的意思是什么呢?

嗯,holder的意思就是持有人,意思就是实现了InventoryHolder接口,就能去持有某个Inventory的意思。

我再举一个例子,比如地上有个箱子,打开不就会弹出来一个箱子的GUI嘛,那个Inventory的owner是谁呢?
答案是这个箱子对应的Chest对象。我们查查JavaDoc也能发现,它也实现了InventoryHolder接口。


所以,你大概应该能明白InventoryHolder接口的含义了吧。


三、我说的这些东西,有啥用呢?

也许你会觉得,我说的好,但是似乎“可以,但没必要”。

我只想对你说NO,这玩意儿不仅可以,还很有必要。

咱们不是准备自己搞GUI嘛,我们就讨论箱子GUI的情形。假设咱们这个GUI叫做DemoGUI,每一个GUI界面都对应一个DemoGUI对象,那我们显然可以创建一个类,名字就叫DemoGUI。所以你得到了这样的一个东西:

  1. public class DemoGUI {
  2. }
复制代码

这没啥好说的,这代码看起来暂时还人畜无害,我就只是创建了一个类而已。
那么,每一个DemoGUI对象都应该对应一个创建的Inventory对象,玩家打开GUI,就给他新建一个DemoGUI对象,这个DemoGUI对象还对应了一个Inventory对象。所以你整出来一个这样的代码:

  1. public class DemoGUI {
  2.     private Inventory inv = Bukkit.createInventory(null, 3*9, "Demo GUI Test");

  3.     public DemoGUI() {
  4.         inv.setItem(1, new ItemStack(Material.STONE));
  5.     }

  6. }
复制代码

截止到目前还是人畜无害的。但是我这里新建这个inv的时候,你看我干了啥事。我把owner设置成了null!
实际上这样做从道理上来讲是说不通的,你想过没想过一个这样的事情,这个inv要是非得说的话,究竟属于谁呢?
答案很显然,当然属于这个DemoGUI对象。DemoGUI对象也应该能做某个Inventory的主人才对啊,对吧。
那我们要不试试看,给这个DemoGUI类实现一个InventoryHolder接口?

所以你大概应该整出来一个这样的代码:

  1. public class DemoGUI implements InventoryHolder {
  2.     private Inventory inv = Bukkit.createInventory(this, 3*9, "Demo GUI Test");

  3.     public DemoGUI() {
  4.         inv.setItem(1, new ItemStack(Material.STONE));
  5.     }

  6.     public Inventory getInventory() {
  7.         return inv;
  8.     }

  9. }
复制代码


这里的getInventory方法是InventoryHolder接口要求我们实现的方法,表示这个InventoryHolder到底持有了哪个Inventory。
在构造器里,我们可以适当写写GUI的按钮初始化逻辑之类的。

这样我们就达到目的了!

你可能会问,嗯,你整得这个活到现在还是人畜无害的,但是我还是没什么头猪。这有啥用?
你写个对InventoryClickEvent的监听器,试试就懂了,你看我操作。
比如下面这个Listener。

  1. @EventHandler
  2. public void onClick(InventoryClickEvent e){
  3.     if(e.getClickedInventory()==null || e.getClickedInventory().getHolder()==null)
  4.         return;
  5.     if(e.getClickedInventory().getHolder() instanceof DemoGUI) {
  6.         // 这里做.爱做的事情
  7.     }
  8. }
复制代码


看,我这里这是个按钮监听对吧。
第一个if是保险排一下null,没啥说的,往下看。
第二个if,我判断了这个被点击的inv的getHolder的类型。这个方**返回来被点击的Inventory对应的owner的InventoryHolder对象,如果它的类型是DemoGUI,很明显,这就是DemoGUI这个GUI的Inventory啊!

所以最开始的问题就迎刃而解了!
你这不就在注释的那个地方写对应的代码逻辑不就完事了,这就能判断这个Inventory到底是哪个GUI的了,对吧。


四、就这?没别的了?

可能你看完以后还觉得意犹未尽,甚至觉得如果你用title判断也不是不能写。我整得这个前摇这么大,就没点别的活了?
不不不,别的活还是有的。你可以使用这样的方法传递一些信息

比如说咱们整得这个GUI带翻页效果,那你可以搞个类属性当page,就像这样:

  1. public class DemoGUI implements InventoryHolder {

  2.     private Inventory inv = Bukkit.createInventory(this, 3*9, "Demo GUI Test");

  3.     private int page = 1; //哼哼!看这里喵!我这里可是整了一个page变量喔!

  4.     public DemoGUI() {
  5.         //我偷懒我就不写对应的上下页按钮了,就当有翻页按钮
  6.         inv.setItem(1, new ItemStack(Material.STONE));
  7.     }

  8.     public DemoGUI(int page) {
  9.         // 这里每个页码肯定页面是不一样的,我也懒得写了,你就当我写了
  10.         this.page =  page;
  11.     }

  12.     public Inventory getInventory() {
  13.         return inv;
  14.     }

  15.     public int getPage() { return page; }
  16.     public void setPage(int page) { this.page = page; }

  17. }
复制代码


那你在处理按钮点击的时候,你就可以这样拿信息:


  1. @EventHandler
  2. public void onClick(InventoryClickEvent e){
  3.     if(e.getClickedInventory()==null || e.getClickedInventory().getHolder()==null)
  4.         return;
  5.     if(e.getClickedInventory().getHolder() instanceof DemoGUI) {
  6.         DemoGUI gui = (DemoGUI) e.getClickedInventory().getHolder();
  7.         int page = gui.getPage();
  8.         // 这里写点处理代码之类的!
  9.     }
  10. }
复制代码


看见我的这个操作了没,我直接就获得了当前点击的这个页面的页码!
那你都有页码了,如果识别到点击到翻页按钮了,怎么做翻页,是不是会变简单很多!

携带其他信息也同理,玩法还是很多的。


其他

实际上我并不是第一个写这个内容的,论坛里还有一篇帖子也是讲述这个问题的。
https://www.mcbbs.net/thread-897931-1-1.html
但是他这个写法...他整的这个CustomInventoryHolder的意思是,所有的GUI都用这个对象......那就没有办法用咱们这篇文章里非常帅气的第四节内容来携带信息了。这两种方案各有优劣。

如果创建好DemoGUI对象以后,可以直接player.openInventory(gui.getInventory); 让玩家打开GUI。
我一般喜欢在DemoGUI类里创建一个静态方法来打开对应的GUI,感觉会方便点,其实都一样,个人喜好问题。

另外还有一个需要注意的地方,不要只监听InventoryClickEvent,不管诸如InventoryDragEvent之类的方法,否则你会发现cancel了InventoryClickEvent,玩家还能用拖拽和快捷键等各种操作把物品卡出来。监听事件要处理全面



二白丶
在群里窥屏了整个事件
受益很大,蟹蟹
另外说自己写的教程没看过真的笑死我了哈哈哈

穆色
受益匪浅

泠绮芜
在群里截图了整件事件,感觉一脸懵

LCOCCPA
直呼看不懂看不懂

听雨不是UB
刚好要用!!!!太感谢了!!

第一页 上一页 下一页 最后一页