Minecraft中ScoreBoard的底层实现与扩展应用
ScoreBoard计分板专题
本文章着重整理了Bukkit插件开发中计分板的底层实现与工作原理,是作者个人经验积累,之后会慢慢补充。
一、Bukkit对计分板的底层实现
1、概述:
(1)管理计分板
Bukkit中提供了一个用于管理计分板的类,总之要对计分板进行操作就要先获取这个类。
ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
(2)计分板分类
要对计分板进行操作,首先应该先了解一下底层是怎么对计分板进行架构的,其实很简单,服务器中的有两个“巨头”,一个是主计分板,一个是玩家的个人计分板。
主计分板
可以理解为一个全局共用的信息,一个服务器中所有人共用的信息就存在主计分板上,尤其是起床战争这种团队游戏就要经常和它打交道。
获取主计分板:
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
个人计分板
就是针对每个玩家的统计信息,例如在一些PVP服务器中,每个玩家在侧边栏看到自己的击杀数都不一样,实际上就是因为每个玩家都有自己的一块计分板,是玩家“私有或者特有”的。
获取个人计分板,通常一个玩家拥有一个计分板即可
新建计分板如下:
Scoreboard scoreboard = scoreboardManager.getNewScoreboard();
获取已有计分板如下:
Scoreboard scoreboard = player.getScoreboard();
这里注意:个人计分板还可分为三个部分,分别是侧边栏、名字下方、玩家列表,这三个其实都是同属于一个计分板,只是他们的显示位置不同,后面我们会详细介绍。
2、个人计分板的编辑
在创建了个人计分板之后就需要对计分板进行一些修饰,本节介绍如何选择三个不同显示位置的计分板并修改他们显示的内容。
(1)Objective类
简单来讲,侧边栏、名字下方、玩家列表这三个其实就是同属于个人计分板下的三个不同的Objective类。
如果已有这些类,那就通过下面这条语句获取:
Objective objective = scoreboard.getObjective(<DisplaySlot>)
这里的DisplaySlot有三种,分别对应侧边栏、名字下方、玩家列表
如果没有事先创建,那么应该创建一个
Objective objective = scoreboard.registerNewObjective(<name>,<criteria>");
<name>是标识名称,可以自己取,<criteria>是显示准则,这里详细讲一下:
<criteria>参数有这些,顾名思义,就不再解释每个参数的含义;
需要注意的是,这些参数只有当显示在名字下方时这些参数才生效。
objective.setDisplaySlot(DisplaySlot.BELOW_NAME);
此外还能设置计分板的标题栏内容
objective.setDisplayName(<标题内容>);
其他的有关objective的操作函数放在这里,都是顾名思义的
❓可见,bukkit似乎没有提供一个函数用来操作玩家列表计分板的footer,该内容有待探究
这里要提到.isModifiable(),将其设置为false之后其他插件就无法对计分板进行修改,在后续讲到的TAB插件中,其生成的计分板就是不可被覆写的,这导致其他插件在TAB插件生效的情况下可能无法对计分板进行操作。如何将这些插件进行兼容,我们后续探讨。
所以我们一般<criteria>会填入"dummy",可以理解为空准则。
(2)分数Score
分数项是对侧边栏(DisplaySlot.SIDEBAR)计分板有效,下面先讲讲分数显示原理。
计分板除标题以外每一行的内容为文本+分数,而决定每一行显示在哪是由分数决定的,计分板默认分数较高者排在最上面,即离标题最近的地方。
例如,有两行apple : 10 和orange : 1,那么明显apple的分数较大,它将显示在orange的上面
利用这个原理,我们可以自定义每一行的分数从而来实现文本的显示顺序
通常为计分板添加积分项可以写一个封装函数,便于之后调用
private static void addScore(Objective objective, String text, int score) {
Score scoreboardScore = objective.getScore(text);
scoreboardScore.setScore(score);
}
(3)计分板的推送与注销
- 计分板的推送
在通过上面的步骤创建完计分板之后,玩家其实还是不能看见你为他们创建的计分板,所以还要调用计分板推送指令。
player.setScoreboard(scoreboard);
//这里的scoreboard就是定义好的个人计分板
注意:这里就不得不说到计分板刷新率问题,bukkit插件开发中,计分板上面的内容并不会自动刷新,也就是每调用上面的那条推送语句,玩家的计分板内容才被刷新。但是通常情况下,我们要实现的是计分板能够根据玩家的统计数据(如击杀数、破坏方块数量)来进行动态更新,这就要求我们在合适的时机调用推送指令。也有的插件如TAB插件干脆直接使用定时任务来周期性推送计分板,于是就有了计分板刷新率的说法。
虽然周期性刷新计分板可以实现一些炫酷的变色、闪烁等效果,但是过高的刷新率将会给服务器和数据库产生较高的压力,例如在计分板上面使用了一些需要读取数据库的内容(或者是解析了某个占位符),当刷新率较高时,占位符的解析(或者数据库的请求)任务将会变得很重,给服务器带来一些负担。
面对这种情况,还是要结合实际,选择合适的刷新办法(或者刷新率),对于计分板实时性没有太高要求的计分板,我们可以采用条件性的手动刷新;而对于实时性要求较高的计分板,我们可以设置缓存区,来减少数据库交互压力。
- 计分板的注销
在卸载插件的时候,为了安全,通常需要卸载掉本插件注册的计分板,以确保下次插件加载时不会重定义等错误。对于个人计分板,其注销方法较为简单:
objective.unregister();//选中要注销的objective计分板进行注销即可
3、主计分板
前面已经介绍过主计分板的获取方法与其应用的情景。这里再补充几点主机分板与个人计分板之间的区别,这些特点务必记住。
- 主计分板全服玩家共有
- 通常无显示界面
- 常用于团队分配
- 对于队伍变更信息,主计分板会自动推送变更
起床战争中不同队伍的玩家衣服的颜色、名字的颜色和前缀都不一样,而且同队伍玩家不能互相攻击,不同队伍的玩家可以互相攻击,这些内容其实就是通过主计分板的Team来实现,可以理解为主计分板就是一个存放全服队伍信息的容器。
由于上面的第四条特点,自适应队伍变更为我们编辑主计分板提供了很大的便利,通常只需要把玩家添加或移除某个队伍,就可以实现简单的分组
(1)分配团队 Team
先看主计分板有哪些有关于Team的操作
常用的就是这四个,其他大可不必关心(其实是懒得写,以后慢慢补充)
这四个分别就是获取所有团队、通过团队名获取团队、获取某个生物的团队、注册一个新的团队。
补充:
移除团队不在这里面,而是直接对team对象执行.remove()语句,后续会讲
team.unregister(); //通常卸载插件时也需要手动注销一下插件创建的团队,以免产生错误!! //很重要,别问我怎么知道的
从团队移除玩家也是对team直接进行操作
team.removeEntry(player.getName());
看到这几个函数读者想必已经非常清楚,所以下面只给出一些常用的代码。
- 队伍创建
// 检查是否已存在该队伍,如果不存在则创建新的队伍
Team team = scoreboard.getTeam(teamName);
if (team == null) {
team = scoreboard.registerNewTeam(teamName);
}
- 检查玩家是否拥有队伍
//在这个函数的基础上可以与Placeholder结合,注册出占位符
//进一步来说,甚至可以通过配置文件来进行动态管理占位符名称(不过也没啥必要)
public boolean CheckIn(Player player){
ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
for (Team team : scoreboard.getTeams()){
if(team.hasEntry(player.getName())){
return true;
}
}
return false;
}
- 统计某个队伍的人数
(由于BedWar1058没有提供队伍人数变量,所以这里添加以下代码实现,以后可以添加插件补丁)
public int NumberOfPeople(String teamName){
int nop= 0;
ScoreboardManager scoreboardManager = Bukkit.getScoreboardManager();
Scoreboard scoreboard = scoreboardManager.getMainScoreboard();
Team team = scoreboard.getTeam(teamName);
nop = team.getSize();
return nop;
}
(2)定义团队特征
前面已经实现了把玩家分配进队伍中,接下来肯定就是要为不同的队伍添加特色了
先来看Team的操作函数
常用函数就这几个,当玩家被加入某个队伍后,其名称也会立即呈现这个队伍的特征。
(3)其他操作函数的研究结果
待补充...
二、扩展应用TAB插件及其兼容性问题
文档地址:TAB插件官方文档
待补充...
三、游戏内自带/scoreboard指令解读
待补充...
四、内容补充
待补充...