美文网首页
[Tutorial][Bukkit]可能是最简单的Scorebo

[Tutorial][Bukkit]可能是最简单的Scorebo

作者: Zoyn_ | 来源:发表于2020-03-18 21:20 被阅读0次

    目录:

    • 导读
    • Scoreboard的基本概念
    • 使用Scoreboard进行展示数据给玩家
    • 制作无闪计分板
    • 如何使用Minecraft自带的Team

    导读

    好久没有更新教程了,woc昨天就发过教程(锡兰梗

    本教程使用的 PaperSpigot1.15.2-R0.1-SNAPSHOT 核心
    在阅读之前请确保你具有Java基础的知识(别问,问就挨打)

    To 读者: 本教程适合所有年龄向的人,因为我们是面向剪切板编程

    Scoreboard的基本概念

    在教程开始前我们来了解一下Scoreboard

    • 记分板(Scoreboard)系统是一套通过命令操纵的复杂游戏机制。主要为地图作者与服务器运营者准备,记分板可用多种形式追踪、设置并列出玩家及实体的分数。
    • 记分项(Objective)由名称(name)、显示名称(display name)、准则(criteria)以及每位玩家(及实体UUID)所对应的整数数据组成。分数的范围为-2,147,483,6482,147,483,647没有小数。
    • 准则(criteria)
    • 显示位置(DisplaySlot)

    若读者有制作CB的经验,那么上方的基本概念可以跳过

    在有了上方对记分板的基本概念后,我们来查询一下Bukkit API中对Scoreboard的包装

    1.12.2版本 | 1.13+版本
    Spigot最新版本

    我吹爆中文BukkitAPI

    可以在上方的Javadoc的查询知道,在Bukkit当中,所有关于Scoreboard的操作都被放到 org.bukkit.scoreboard 包下了,之后我们就来看一下,要怎么正确的使用一个记分板吧

    在阅读教程之前我强烈建议先看一看中文MinecraftWiki再来阅读本文

    使用Scoreboard进行展示数据给玩家

    接下来我们就来看看如何进行操作
    首先我们需要的是ScoreboardManager这个接口的对象,怎么获取呢?我可以通过下方代码实现

    ScoreboardManager manager = Bukkit.getScoreboardManager();
    

    在Bukkit这个静态类中,BukkitAPI已经帮我们造好了轮子,我们可以直接使用
    接下来我们就需要得到一个叫 Scoreboard 接口的对象,我们可以通过manager里的方法来进行获得

    Scoreboard scoreboard = manager.getNewScoreboard();
    

    为什么要用 getNewScoreboard() 呢?
    因为这样我们只会新建出一个Scoreboard,这个Scoreboard是不会受原版指令的限制的Scoreboard

    之后我们需要新建一个计分项,也就是Objective,接下来看我的操作

    Objective objective = scoreboard.registerNewObjective("内部名字", "dummy", "§a我是展示名~~");
    

    首先我们看上面的三个参数,name,criteria,displayName,那么对应过来的就是计分项的内部名字和准则与展示名

    • 内部名字: 用于scoreboard.getObjective()时填入, 可直接获取Objective
    • 准则: 此 Objective 的准则,表示只能通过插件修改分数
    • 展示名: 也就是计分板头上的那个标题,比如下图的 Scoreboard 就是展示名


      Scoreboard.png

    为什么要写 dummy 呢?
    因为 dummy 型的准则更适合于插件开发, 并且它不会被玩家死亡或击杀变动

    之后我们给Objective设置显示的位置

    objective.setDisplaySlot(DisplaySlot.SIDEBAR);
    

    我们来解释一下这个显示位置的问题:
    DisplaySlot这个枚举列举了所有Objective可以存在的地方

    • PLAYER_LIST (玩家Tab里)
    • SIDEBAR(侧边栏)
    • BELOW_NAME (玩家头上NameTag的下面)

    那么接下来我们就要往 Objective 里添加Score了

    Score score = objective.getScore("内容");
    score.setScore(12345);
    

    之后我们就可以给玩家设置上我们的Scoreboard
    (如果没有做这一步,并且事先也未给玩家设置Scoreboard的话,会导致无法显示与使用!)

    Player player = 我也不知道这个player要从哪引用;
    player.setScoreboard(scoreboard);
    

    完整代码

    ScoreboardManager manager = Bukkit.getScoreboardManager();
    // 建立新Scoreboard
    Scoreboard scoreboard = manager.getNewScoreboard();
    // 注册新的记分项
    Objective objective = scoreboard.registerNewObjective("内部名字", "dummy", "§a我是展示名~~");
    // 设置记分项展示位置
    objective.setDisplaySlot(DisplaySlot.SIDEBAR);
    // 给记分项增加 内容与对应的分数
    Score score = objective.getScore("内容");
    score.setScore(12345);
    // 设置计分板
    Player player = 我也不知道这个player要从哪引用;
    player.setScoreboard(scoreboard);
    

    具体效果:


    啦啦啦

    制作无闪计分板

    问题引入:
    那么在经过了上面的实例之后我相信,大部分人都已经学会了如何简易的给玩家设置计分板,但是当我们在做一些动态的计分板的时候,会出现闪烁的问题,那么这是怎么出现的呢?
    就拿我们刚才的代码来说,如果我们想更改计分板的内容,我们只能通过

    scoreboard.resetScores("内容");
    

    这样的方式才能删除一个Score,那么就有人说了

    1. 诶呀为什么不直接clear或者reset呢?
      我也想啊,只可惜Minecraft的计分板就是这么设计的,所以我们如果想更换内容就得先 resetScores() 之后再 objective.getScore() 才能进行更换

    2. 然后这时候又有人说了,那我重新的getNewScoreboard不就好了吗?
      诶呀,你自己看看我们上面所写的代码,我们还要注册个新的Objective,之后设置一大堆东西,然后才能开始设置Score,这里面的实现早已就产生了上百的ms,所以这个方法会导致闪烁的问题

    3. 然后这个时候lz就说了,诶呀为什么不用resetScores填入内容之后,再getScore来设置呢?
      诶呀,这样的话其实还是会导致在 resetScores 的时候出现部分闪屏的内容,属于假无闪!,具体思路是:
      1. 首先我们在 objective.getScore 时顺便将内容存入一个作为cache的List中
      2. 在下一次我们想要修改时,遍历这个 cache 的List,之后resetScores
      3. 最后再使用 objective.getScore 添加数据

    那么这时候我们就会出现一个问题,既然我们不能用 resetScores,那么我们应当怎么写呢?
    这里我要感谢 #6楼 提示给我的方法,所以这里我对解决方案进行更改

    那么这里是我的解决方案:
    我们通过使用Team的特性来写,Team这个东西其实是,在下面的一部分,但是为了做出无闪的效果,这里提前说一下思路就行

    1. 在Team中有setPrefix和setSuffix的方法,通过这两个方法我们可以直接修改前后缀
    2. 如果我们新建15个队伍,然后给每个队伍只addEntry(name),我们为了做出name不显示的效果,我们可以使用颜色代码 §X 的形式做出不显示的效果来实现
    3. 之后我们给每个队伍设置不同的prefix和suffix,这样就可以达到不通过resetScore来设置内容

    我们来看下面的实例

    实例:制作一个实时显示时间的计分板
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.scheduler.BukkitTask;
    import org.bukkit.scoreboard.DisplaySlot;
    import org.bukkit.scoreboard.Objective;
    import org.bukkit.scoreboard.Scoreboard;
    import org.bukkit.scoreboard.Team;
    
    import com.google.common.collect.Lists;
    
    public class MyScoreboard {
    
        private Scoreboard scoreboard;
        private Objective objective;
        private String title;
        private Player player;
        private boolean isRun;
        private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        private SimpleDateFormat format2 = new SimpleDateFormat("HH:mm:ss");
        // 用作runnable的主类实例
        private Plugin plugin;
        /**
         * 用于保存所有的Team
         */
        private List<Team> timers;
        private BukkitTask task;
    
        public MyScoreboard(Plugin plugin, Player player, String title) {
            this.scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
            this.title = title;
            this.objective = scoreboard.registerNewObjective(player.getName(), "dummy", this.title.replace("&", "§"));
            objective.setDisplaySlot(DisplaySlot.SIDEBAR);
    
            this.player = player;
            this.isRun = false;
            this.plugin = plugin;
            timers = Lists.newArrayList();
        }
    
        public void startShowing() {
            // 判断是否已经在运行
            if (isRun) {
                return;
            }
            if (player == null || !player.isOnline()) {
                return;
            }
            isRun = true;
            player.setScoreboard(scoreboard);
    
            // 用于保存前15位的内容
            List<String> tempList = Lists.newArrayList();
            for (int i = 0; i <= 15; i++) {
                tempList.add("§" + ChatColor.values()[i].getChar());
            }
    
            for (int i = 0; i <= 15; i++) {
                // 注册Team时使用 数字的形式就行
                Team timer = scoreboard.registerNewTeam("" + i);
                // addEntry只是作为一个标识符, 用于getScore时的识别
                timer.addEntry(tempList.get(i));
                // getScore 刚才的标识符
                objective.getScore(tempList.get(i)).setScore(i);
    
                timers.add(timer);
            }
    
            task = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
                if (!isRun) {
                    return;
                }
    
                for (int i = 0; i < timers.size(); i++) {
                    Team timer = timers.get(i); // 获取每个Team
                    Date date = new Date();
                    // 设置前缀
                    timer.setPrefix(tempList.get(i) + format.format(date));
                    // 设置后缀
                    timer.setSuffix(tempList.get(i) + " " + format2.format(date));
                }
                
            }, 0L, 20L);
        }
    
        public void turnOff() {
            isRun = false;
            task.cancel();
        }
    }
    
    具体效果: 无闪计分板

    请不要在意时间,是星空的测试机的锅,我是早睡早起的四好青年

    如何使用Minecraft自带的Team

    Team这个东西其实是比较适合Minecraft的(不然干嘛是Mojang自己做的),因为这个东西你可以设置很多内容,比如说

    • COLLISION_RULE(体积碰撞):你可以设置相同队伍可以没有体积碰撞
    • DEATH_MESSAGE_VISIBILITY(死亡信息可见性):你可以设置相同队伍才可以看见玩家的死亡信息
    • NAME_TAG_VISIBILITY(玩家头顶名字可见性):你可以设置相同队伍才可以看见头顶名字
    • CanSeeFriendlyInvisibles(是否可以看到自己队伍的人隐身
    • AllowFriendlyFire(是否可以友军开火
      再也不需要EntityDamageByEntityEvent了!
    • Color 队伍颜色
    • Prefix 队伍前缀,可以直接设置到玩家的NameTag上
    • Suffix 队伍后缀,可以直接设置到玩家的NameTag上

    版本变换:其实在1.13的版本之后Team就更改了一下,主要的就是更改了Prefix和Suffix字符还有DisplayName的长度的限制,因为1.13以后的版本,这些内容改用json来储存

    1.13以前 设置Prefix和Suffix只能在16个字符以内
    而在1.13以后,设置Prefix和Suffix可以在64个字符以内了
    并且DisplayName也从32个字符长度增长到128个字符

    此外对于Team就没有更多的API更新了

    那么接下来我们就来看看Team是如何使用的
    首先我们需要建立一个新的Scoreboard

    Scoreboard teamScoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
    

    之后我们来新建两个队伍,红队蓝队

    Team redTeam = teamScoreboard.registerNewTeam("RED");
    Team blueTeam = teamScoreboard.registerNewTeam("BLUE");
    

    在上面的代码我们要注意,RED和BLUE其实是队伍的内部名字,不做显示用

    之后我们来给它设置别的内容

    // 设置显示名
    redTeam.setDisplayName("红队");
    blueTeam.setDisplayName("蓝队");
                    
    // 设置队伍颜色
    redTeam.setColor(ChatColor.RED);
    blueTeam.setColor(ChatColor.BLUE);
    
    // 对于自己的队伍进行NameTag显示, 而对其他队伍关闭 -> 制作出类似吃鸡队友的感觉
    // 这里的FOR_OTHER_TEAM表示的意思是只对其他队伍 关闭
    redTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
    blueTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
    
    // 对于自己的队伍开启防碰撞体积, 而对其他队伍开启体积碰撞
    // 这里的FOR_OWN_TEAM表示的意思是只对本队 关闭
    redTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
    blueTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
                    
    // 设置同队可看见隐身
    redTeam.setCanSeeFriendlyInvisibles(true);
    blueTeam.setCanSeeFriendlyInvisibles(true);
                    
    // 取消队伤
    redTeam.setAllowFriendlyFire(false);
    blueTeam.setAllowFriendlyFire(false);
    
    // 设置前缀
    redTeam.setPrefix("§c红队 - ");
    blueTeam.setPrefix("§8蓝队 - ");
    

    之后我们就建立了两个Team,之后我们得需要给他们增加玩家

    redTeam.addEntry("Zoyn");
    blueTeam.addEntry("Alex");
    

    我们在上方的代码中,
    红队添加了一名队员, Zoyn
    蓝队添加了一名队员, Alex

    这里要注意的是,给队伍增加队员不是调用 addPlayer(OfflinePlayer player) 这个已经弃用的方法,因为你放在Team里的可以不只是Player,所以我们只用放入玩家名就好

    之后我们给这两个队员设置好Scoreboard(如果没有做这个操作,可能会导致不显示!

    Player zoyn = ?
    Player alex = ?
    zoyn.setScoreboard(teamScoreboard);
    alex.setScoreboard(teamScoreboard);
    

    完整代码:

    Scoreboard teamScoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
    Team redTeam = teamScoreboard.registerNewTeam("RED");
    Team blueTeam = teamScoreboard.registerNewTeam("BLUE");
    
    // 设置显示名
    redTeam.setDisplayName("红队");
    blueTeam.setDisplayName("蓝队");
                    
    // 设置队伍颜色
    redTeam.setColor(ChatColor.RED);
    blueTeam.setColor(ChatColor.BLUE);
    
    // 对于自己的队伍进行NameTag显示, 而对其他队伍关闭 -> 制作出类似吃鸡队友的感觉
    // 这里的FOR_OTHER_TEAM表示的意思是只对其他队伍 关闭
    redTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
    blueTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
    
    // 对于自己的队伍开启防碰撞体积, 而对其他队伍开启体积碰撞
    // 这里的FOR_OWN_TEAM表示的意思是只对本队 关闭
    redTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
    blueTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
                    
    // 设置同队可看见隐身
    redTeam.setCanSeeFriendlyInvisibles(true);
    blueTeam.setCanSeeFriendlyInvisibles(true);
                    
    // 取消队伤
    redTeam.setAllowFriendlyFire(false);
    blueTeam.setAllowFriendlyFire(false);
    
    // 设置前缀
    redTeam.setPrefix("§c红队-");
    blueTeam.setPrefix("§9蓝队-");
    
    redTeam.addEntry("Zoyn");
    blueTeam.addEntry("Alex");
    
    Player zoyn = ?
    Player alex = ?
    zoyn.setScoreboard(teamScoreboard);
    alex.setScoreboard(teamScoreboard);
    

    实际效果:
    Zoyn视角:

    Zoyn
    Alex视角: Alex 当他们两者在同一队伍时 同一队伍

    之后为了方便读者测试,我写了一个测试类来给读者测试

    使用方法,
    1.注册指令 teams (当然你也可以自己改)
    2.reload之后第一次输入请输入 /teams init 进行队伍初始化
    3.在指令中的队伍名,只有 RED 和 BLUE

    实例:一个使用Scoreboard#Team的内容来写一个组队系统

    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.command.Command;
    import org.bukkit.command.CommandExecutor;
    import org.bukkit.command.CommandSender;
    import org.bukkit.entity.Player;
    import org.bukkit.scoreboard.Scoreboard;
    import org.bukkit.scoreboard.Team;
    import org.bukkit.scoreboard.Team.Option;
    import org.bukkit.scoreboard.Team.OptionStatus;
    
    public class TeamCommand implements CommandExecutor {
    
        private Scoreboard teamScoreboard;
    
        @Override
        public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
            if (cmd.getName().equalsIgnoreCase("teams")) {
                if (args.length == 0) {
                    sender.sendMessage("/teams init 初始化");
                    sender.sendMessage("/teams list 列出所有队伍");
                    sender.sendMessage("/teams set <玩家名> <队伍名> 将玩家的队伍进行设置");
                    sender.sendMessage("/teams prefix <队伍名> <前缀名> 将玩家的队伍进行前缀的设置");
                    sender.sendMessage("/teams suffix <队伍名> <前缀名> 将玩家的队伍进行前缀的设置");
                    return true;
                }
    
                if (args[0].equalsIgnoreCase("init")) {
                    teamScoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
                    Team redTeam = teamScoreboard.registerNewTeam("RED");
                    Team blueTeam = teamScoreboard.registerNewTeam("BLUE");
    
                    // 设置显示名
                    redTeam.setDisplayName("红队");
                    blueTeam.setDisplayName("蓝队");
                    
                    // 设置队伍颜色
                    redTeam.setColor(ChatColor.RED);
                    blueTeam.setColor(ChatColor.BLUE);
    
                    // 对于自己的队伍进行NameTag显示, 而对其他队伍关闭 -> 制作出类似吃鸡队友的感觉
                    // 这里的FOR_OTHER_TEAM表示的意思是只对其他队伍 关闭
                    redTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
                    blueTeam.setOption(Option.NAME_TAG_VISIBILITY, OptionStatus.FOR_OTHER_TEAMS);
    
                    // 对于自己的队伍开启防碰撞体积, 而对其他队伍开启体积碰撞
                    // 这里的FOR_OWN_TEAM表示的意思是只对本队 关闭
                    redTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
                    blueTeam.setOption(Option.COLLISION_RULE, OptionStatus.FOR_OWN_TEAM);
    
                    // 由于只做演示, 所以这里的sender我直接强转得到
                    Player player = (Player) sender;
                    player.setScoreboard(teamScoreboard);
                    sender.sendMessage("§a操作成功!");
                    return true;
                }
    
                if (args[0].equalsIgnoreCase("list")) {
                    teamScoreboard.getTeams().forEach(team -> {
                        sender.sendMessage("名字: " + team.getName());
                        sender.sendMessage("展示名: " + team.getDisplayName());
                        sender.sendMessage("已有队员: ");
                        team.getEntries().forEach(player -> {
                            sender.sendMessage(" - " + player);
                        });
                        sender.sendMessage("=====================");
                    });
                    sender.sendMessage("§a操作成功!");
                    return true;
                }
    
                if (args[0].equalsIgnoreCase("set")) {
                    Player entry = Bukkit.getPlayer(args[1]);
                    if (entry == null || !entry.isOnline()) {
                        sender.sendMessage("玩家不在线!");
                        return true;
                    }
                    Team playerTeam = teamScoreboard.getEntryTeam(entry.getName());
                    Team team = teamScoreboard.getTeam(args[2]);
                    
                    if (playerTeam != null) {
                        // 将玩家离开之前的队伍
                        playerTeam.removeEntry(args[1]);
                    }
    
                    // 将玩家加入选定的队伍
                    team.addEntry(args[1]);
    
                    // 对选中的人设置计分板, 不然会导致无法显示的问题
                    entry.setScoreboard(teamScoreboard);
                    sender.sendMessage("§a操作成功!");
                    return true;
                }
    
                if (args[0].equalsIgnoreCase("prefix")) {
                    Team team = teamScoreboard.getTeam(args[1]);
                    team.setPrefix(ChatColor.translateAlternateColorCodes('&', args[2]));
                    sender.sendMessage("§a操作成功!");
                    return true;
                }
    
                if (args[0].equalsIgnoreCase("suffix")) {
                    Team team = teamScoreboard.getTeam(args[1]);
                    team.setSuffix(ChatColor.translateAlternateColorCodes('&', args[2]));
                    sender.sendMessage("§a操作成功!");
                    return true;
                }
            }
            return true;
        }
    

    这就是BukkitAPI中对 org.bukkit.scoreboard 包的内的所有内容,如果你有相关问题可以回复,一起交流 —— 一个本科人

    相关文章

      网友评论

          本文标题:[Tutorial][Bukkit]可能是最简单的Scorebo

          本文链接:https://www.haomeiwen.com/subject/rjmcyhtx.html