在原生的服务器中,使用GUI界面操作可以减少玩家需要输入的指令数量,让玩家无需为太多指令需要记忆而发愁。 使用容器界面,同时使用容器内的物品作为按钮,监听容器的点击事件,便实现了容器界面。让玩家更多更GUI打交道而不是使用指令。
由于服务器时常容易出现不稳定的情况,在特定的条件下,玩家有可能将容器内的按钮取下,所以仅仅取消容器点击事件来限制玩家取出按钮是完全不够的。 所以本插件使用前置插件ProtocolLib用于发送虚拟的物品以显示按钮。
在添加界面时,要考虑到界面的用途以及界面的归属问题,比如该界面是属于插件的还是玩家的。 同时,还要考虑翻页的问题,页面的页码是否有限制。
本插件的页面实现简单来说就是通过一个导航ViewGuide
控制玩家打开界面、切换界面和翻页。
每一个界面View
可以包含多数的页面ViewPage
双向链表,其中每个页面都能通过viewPage.getNext()
来获取下一页或viewPage.getPre()
获取上一页。
这里列出一些常用界面类型,当然你可以自己挖掘其他用法
只有一页,而且界面的内容不由使用者的变化而变化。 通常界面的归属是某个插件,例如用来显示插件的信息,每个玩家打开看到都是一样的内容。
该类型下使用导航调用的指令viewGuide.openView(player, "view-key", "target-key")
中的target的作用就不是用来区分界面的使用者。
这种界面一般只有固定的几个target
。
大致结构如下
ViewGuide {
"view-key" → View {
"target-key" → { ViewPage ↔ ViewPage ↔ ViewPage ↔ ViewPage ↔ ... }
}
"view-key" → View {
"target-key" → ViewPage
"target-key" → ViewPage
}
more view...
}
对于不同的玩家打开容器,可以使用参数target
来获取对应玩家的专属页面。
这个界面的target
数量取决于使用容器的玩家数量,并非固定的
一般来说,使用一个Map<String, ViewPage>
来存储一个界面的首页,供玩家打开,其中数据的键为target
。
大致结构如下
ViewGuide {
"view-key" → View {
"target-key" → { ViewPage ↔ ViewPage ↔ ... }
"target-key" → { ViewPage ↔ ViewPage ↔ ViewPage ↔ ViewPage ↔ ... }
"target-key" → { ViewPage ↔ ViewPage ↔ ViewPage ↔ ... }
more page...
}
"view-key" → View {
"target-key" → ViewPage
"target-key" → ViewPage
"target-key" → ViewPage
more page...
}
more view...
}
GlobalMarket 插件的市场主界面就是单目标多页界面,每个玩家看到的都是同一个市场数据,界面归属是插件本体。
邮箱界面则是多目标单页界面,每个玩家都是一个目标,有自己专属的邮箱页面,
在玩家打开时,需要使用玩家的名称作为target
作为参数找到玩家的邮箱页面。
此外,数据键target
也不仅只使用玩家名称,交易界面使用商品的ID来作为数据键,使每个商品有专属的交易界面。
如果玩家打开一个界面,发现里面空空如也,那么他肯定有点诧异。
容器为了交互,就免不了在里面添加一些按钮。
为了更加方便地构建一个物品添加到界面内,插件提供了ViewItemBuilder
类。
ItemStack button = new ViewItemBuilder(Material.DIAMOND)
.name("§c按钮")
.lore("§f点我送钻石!")
.build();
当用户与界面中的物品交互时,插件需要获取玩家交互的物品,以及该物品对应的行为。
可以使用本插件提供的ItemUtils
获取物品的相关数据。
在新建物品时设置额外参数
ItemStack button = new ViewItemBuilder(Material.DIAMOND)
.name("§c按钮")
.lore("§f点我送钻石!")
.action(ViewItem.ACTION_CONSOLE_COMMAND)
.value("give %player% diamond 1")
.build();
在监听到玩家点击事件后通过ItemUtils.getItemAction(item)
获取按钮执行行为类型。
并使用ItemUtils.getItemValue(item)
获取行为的值,比如指令。
获取了物品的数据后,按照行为的类型执行相应的任务。
在plugin.yml文件下添加依赖,让GUI插件优先于你的插件加载。
depend: [ProtocolLib,CraftGUI]
在插件的onEnable()
方法内添加导航获取代码this.setupGuide();
。
在类内添加初始化方法如下
private static ViewGuide guide;
public void setupGuide() {
RegisteredServiceProvider<ViewGuide> rsp = Bukkit.getServer().getServicesManager().getRegistration(ViewGuide.class);
if (rsp == null) {
this.getLogger().warning("CraftGUI not found!");
return;
}
guide = rsp.getProvider();
...
}
新建一个类并实现View
接口,可以参考如下代码
public class CustomView implements View<CustomPage> {
// 插件名称用于在监听事件时判断点击的是否本插件的界面
protected final String pluginName;
// 存储各个target的页面
protected final Map<String, CustomPage> pageMap = new HashMap<>();
public CustomView(String pluginName) {
this.pluginName = pluginName;
}
/**
* 玩家打开该界面时显示的页面
* @param target 页面标签
* @return 页面
*/
@Override
public CustomPage getFirstPage(String target){
if (!pageMap.containsKey(target)){
pageMap.put(target, new CustomPage(pluginName, target, 0, 27));
}
return pageMap.get(target);
}
@Override
public void removePage(String target) {
pageMap.remove(target);
}
}
创建完界面后,创建相应的页面。可以新建一个类实现ViewPage
接口,当然也可以继承本插件提供的TemplatePage
,代码如下
public class CustomPage extends TemplatePage {
public CustomPage(String pluginName, String target, int page, int size) {
super(pluginName, target, page, size);
}
@Override
public @NotNull Map<Integer, ItemStack> getItemMap(){
crashMap.clear();
crashMap.putAll(itemMap);
// 这里是需要异步加载的按钮
for (int i = 0; i < 9; i++) {
ItemStack item = new ViewItemBuilder(Material.STONE)
.name(String.format("Button%s", i))
.build();
crashMap.put(i, item);
}
return crashMap;
}
@Override
public ViewPage getNext() {
// 自动创建下一页
if(next == null && page < 7){
next = new CustomPage(pluginName, target, page+1, size);
next.setPre(this);
}
return next;
}
@Override
public void refreshPage() {
// 关闭按钮
ItemStack closeButton = new ViewItemBuilder(Material.REDSTONE)
.name("§c关闭")
.lore("§f关闭界面")
.action(ViewItem.ACTION_CLOSE)
.build();
itemMap.put(26, closeButton);
}
}
这样就完成了一个简单的自定义页面。
自定义界面后,需要将界面添加到导航里面,才能让玩家打开。
首先为自定义界面起个名字,例如myplugin.custom
。即使用插件的名称和界面名称组成,避免和其他插件的界面重名。
public static final String CUSTOM_VIEW = "myplugin.custom";
设置了名称后,使用导航添加界面。 建议界面需要带有一些标记,在事件监听的时候能够判别该事件是否他人的界面。 例如我传入插件的名称作为容器的标题。
guide.addView(CUSTOM_VIEW, new CustomView("[CraftGUI]"));
需要玩家打开界面时,调用
guide.openView(player, CUSTOM_VIEW, "target");
插件提供三种界面操作,分别是界面点击ViewClickEvent
、界面物品放置ViewPlaceEvent
和数字键按钮操作ViewHotbarEvent
。
监听代码参考如下
@EventHandler
public void onViewClick(ViewClickEvent event) {
int action = event.getAction();
String view = event.getViewName(), value = event.getValue();
// 判断是否本插件相关的事件
if(view == null || !view.startsWith("craftgui")) return;
// 获取点击到的物品,一般来说是有物品
ItemStack item = event.getCurrentItem();
if(item == null) return;
// 获取点击的玩家
Player player = (Player)event.getWhoClicked();
// 根据行为做反应
if (action == ButtonAction.ACTION_PLUGIN) {
// do something
player.sendMessage("is " + value);
}
}
Fireflyest QQ: 746969484