本帖最后由 kakagou12 于 2019-7-1 18:52 编辑


-------命令进阶-------
第四章 记分板

本文首发于命令进阶教程全集
使用CC BY-SA-NC 4.0协议,转载请注明本页面链接


    记分板(Scoreboard,简写为scb)是命令系统里其中一个较大的体系,其中包含变量(Objective)、队伍(Team)及标签(Tag)。

    其中,变量用作储存对象分数,对数据计算、实体状态标记等非常有用,也是记分板体系内最常用的一个。(scb这简称一般是指记分板变量)
至于队伍,则更多牵涉在玩家间的组队竞技,即PVP(玩家间对战)或PVE(玩家与怪物对战)的游戏中。这是因为队伍系统提供比较多有关战斗的设置。我们会在之后的章节更详细的讨论队伍的设置及用处。
    最后,标签的主要用途是根据数据标签来标记实体,然而在具体应用中,tag可以非常方便地标记多个其它的tag,反选操作也比变量方便一些。我们会在标签一章进行详细讨论。

    本文将会详细介绍记分板各体系的概念及用法,之后才介绍命令。
    最后,我们将会以几个记分板系统实例作结尾,希望让各位读者在实例中掌握记分板的用法,更深入理解记分板系统设计及原理。

作为移植页面,格式可能会有偏差,请到教程全集中获得完整体验。
本页面可能随着版本推进而过期,内容以全集为准。


记分板变量
本文采用以下译名:
  • Objective->变量
  • Criteria->判据

在本文中,对象指的是玩家名称(真实玩家及假名)与UUID
同时,本文前部以概念介绍为主,具体的命令与应用在后部介绍。

变量 是记分板系统的基本组成部分。记分板系统上可以同时存在多个变量。

一个变量包含以下内容:
  • 名称(Name)。名称是一个变量在系统内部使用的名称。不同变量之间的名称不能重复,且名称区分大小写。也就是说,名称为"name"的变量和名称为"NAME"的变量是不同的。名称最长可为16个字符。为了便于他人阅读命令,我们建议用 CamelCase原则 命名,即单词第一个字母大写。(当然,这不是游戏的必须要求)
  • 显示名称(Display name)。显示名称是一个变量用于展示的名称。与名称相反,不同变量之间的显示名称可以重复。
  • 判据(Criterion)。判据是一个变量的分数改变方式。不同变量之间的判据可以重复。
  • 追踪的对象与其分数(Target and Score)。变量可以同时追踪多个对象,并记录对象的相应分数。


下面让我们具体了解变量的各个组成部分。

判据


判据作为变量分数的改变方式,一般是针对于玩家而言的。当玩家以外的实体等对象与变量关联起来时,判据效果都与 dummy 等同,即只能通过命令修改。

判据分为简单判据和复合判据。简单判据只有一个单词,复合判据则是在基础判据之上加入次级判据。下面先介绍简单判据。

在简单判据中,有一种判据称为只读型判据。它们是根据一些系统内部的数值来确定相关分数的,因而不可修改。同时只读型判据与玩家信息相关,因此玩家之外的对象不适用只读型判据。在只读型判据外,其它类型的判据只是在条件满足时增加分数,不对应内部数值,所以都能通过命令修改分数。复合判据没有只读型,即只读型判据都是简单判据。
在下文中,粗体的判据就是只读型判据。

以下是简单判据略表:
  • dummy: 虚拟型,只能由命令修改
  • trigger: 触发型,配合/trigger命令使用
  • deathCount: 统计死亡次数
  • playerKillCount: 统计你的击杀次数
  • totalKillCount: 统计你的杀怪次数
  • health: 对应你的生命值
  • xp: 对应你的经验值
  • level: 对应你的等级
  • food: 对应你的饱食度
  • air: 对应你潜水时的空气值
  • armor: 对应你的盔甲值

相比于简单判据,复合判据(Compound criteria)能够更精细地检测玩家行为。复合判据的结构为:基础判据.次级判据,例如 stat.craftItem.minecraft.stone 。不同部分之间以   .   分隔。所有的复合判据都可以通过命令进行分数的修改。

以下是复合判据可用的基础判据略表:

  • stat: 当玩家完成指定的统计项目时,加上对应的分数。不会从内部的统计中拉取数据
  • stat.craftItem: 玩家合成指定的物品时分数+1
  • stat.useItem: 玩家使用指定物品时分数+1
  • stat.breakItem: 玩家因耐久度耗尽而破坏指定物品时分数+1
  • stat.mineBlock: 玩家挖掘指定方块时分数+1
  • stat.killEntity: 玩家杀死指定生物时分数+1
  • stat.entityKilledBy: 玩家指定生物杀死时分数+1
  • stat.drop: 玩家丢出指定物品时分数+1
  • stat.pickup: 玩家拾取指定物品时分数+1
  • teamkill: 玩家杀死指定队伍的成员时分数+1
  • killedByTeam: 玩家指定队伍的成员杀死时分数+1

全部的次级准则请参见Wiki的Criteria部分

使用复合判据时应当注意:
  • 在基础准则后加ID名时,需要添加.minecraft前缀。例如:stat.craftItem.minecraft.stone。而
  • 使用物品有着很详细的规则。一般来说,与生物的互动都不属于使用,例如驯服、喂养、繁殖、挤奶、剪羊毛等。

显示位置
记分板的分数是可以在游戏画面中显示出来的。显示位置共有三处:名称下方,侧边栏,在线玩家菜单。在显示变量名称时,使用的是显示名称(若添加变量时未填写显示名称,则默认使用名称)。一个显示位置只能显示一个目标,但一个目标可以显示在多个位置上。不同的显示位置,分数的显示方式是不同的。各个显示位置的具体介绍如下:
  • list: 分数显示于在线玩家菜单中玩家名称的后面,颜色为黄色。这个显示位置不显示变量名。
  • sidebar: 在侧边栏显示变量名、对象名称(UUID/玩家名),及对象对应的分数。
    • sidebar.team.队伍颜色: 在指定队伍成员的侧边栏显示分数,形式如上。
  • belowName: 在玩家名称牌的下方显示变量名与分数。分数在变量名之前。
在下面这张图片中, listbelownamesidebar 在这里是玩家的名称,同时也对应显示位置的名称; 03 是不带变量名的分数; 1 是分数值; MortsScore 是变量名。


对象、分数与检测
前面已经说到,对象指的是玩家名称或UUID。UUID是记录实体的,而玩家名就是记录玩家的。玩家不需要在线,甚至不需要存在,也可以在记分板上有分数。如果使用了不存在的玩家名,我们就把这种玩家名称为 假名(Fake name)
一般我们会使用假名来显示分数。如果是记录分数作探测之用,一般我们会使用实体marker,因为我们能够以选择器选择实体marker,但不能选择假名。
使用假名时,只需要按照一般的方法在命令中输入假名即可,不需要进行声明操作。例如,设置分数的命令为:
  1. scoreboard players set <玩家名> <变量的名称> <分数值> [数据标签]
复制代码

现在有一个虚拟型变量obj,给一个叫Faker的假名设置分数为10,则命令为:
  1. scoreboard players set Faker obj 10
复制代码

如果不希望假名出现在侧边栏,则可以在假名前添加#前缀:
  1. scoreboard players set #Faker obj 10
复制代码
不过玩家名的长度也是有限制的,不能长于40个字符。



在填写玩家名的参数中,可以用   *   代表所有对象(所有被记分板系统追踪的对象)。不过此写法只适用于记分板命令



如果实体有分数,那么可以通过目标选择器,利用相关参数进行检测。具体的参数有两个,分别是 score_namescore_name_min 。其中 name 是变量名称。前者代表大于等于,后者代表小于等于。例如,设变量 deaths 的判据为 deathCount ,则如果要选出至少死亡一次,至多死亡5次的玩家,则目标选择器的写法为 @p[score_deaths=5,score_deaths_min=1]

除了分数,我们也能使用记分板的 标签(tag) 对实体进行标记,这会在之后的章节讲解。

记分板分数有限制的,范围为-2147483648到2147483647。
如果分数在加减的过程中超出范围则会溢出,这方面请自行搜寻,不建议使用这个特性。

记分板中分数为0没有分数是两个概念:前者能被检测分数,而后者不能被检测分数,即不在记分板系统上。(然而分数+0 -0也能自动把没有分数变为0分。)
我们可以使用 score_obj_min=-2147483648 或者 score_obj=2147483647 检查有没有分数。由于分数范围的限制,没有分数则必然不会被此参数选中。


记分板队伍
队伍可以为玩家与生物分组。这项功能主要用于玩家间的组队竞技,同时队伍系统中的一些独特功能也可以用来实现其它意想不到的效果。

注意,每个玩家最多只能加入一个队伍。如果已加入队伍的玩家试图加入新的队伍,这个玩家就会自动离开原队伍。
生物之间在同一队的时候就不会互相攻击,并且怪物不会攻击同队玩家,即便friendlyfire是true时也是如此。

下面介绍一下队伍的组成元素:
  • 名称:队伍的名称,用于命令操作。区分大小写,不可重复。
  • 显示名称:在显示时用到的名称。可以重复。
  • 前缀与后缀(Prefix and Suffix):应用于队伍成员名字的显示。需要利用外部编辑器,无法在游戏内修改。可以通过NBT编辑软件编辑
    (路径见下图。修改Suffix的同时记得在最后加上§r,避免前方的样式影响后方的文字。)
      
  • 队伍设置:见下。

生物生成时可以通过NBT指定其队伍,NBT为:
  1. Mob
  2. └── Team: 队伍名称。(string)
复制代码

生成一只在队伍test的僵尸:
  1. summon zombie ~ ~ ~ {Team:"test"}
复制代码

队伍设置
设置选项用途
颜色(color)具体颜色对应名称参考JSON
reset,清除颜色,变为白色
决定队伍颜色。一个颜色可以应用于多个队伍。队伍颜色对记分板很多设置都有用途,如显示位置的sidebar.team.颜色、判据中和队伍有关的部分,以及控制发光效果颜色等
友军火力(friendlyfire)true, false默认为true,即队友间可以互相伤害。控制队友间能不能互相直接伤害,但对药水、TNT等一类非直接伤害无效。
队友隐身可见(seeFriendlyInvisibles)true, false默认为false,即不可以看到隐身队友。如果为true则能看见隐身队友(半透明)。
名称牌可见(nametagVisibility)never, hideForOtherTeams, hideForOwnTeam, always决定给谁看到自己的名牌。由于名牌能在短距离内通过方块看到,因此对竞技十分重要。
never就是没人看到,hideForOtherTeams就是只有队友能看见自己的名牌,hideForOwnTeam就是只有对手才能看到自己名牌,默认always就是谁也能看到名牌。
死亡信息可见(deathMessageVisibility)同上决定谁能看到自己的死亡信息。选项用途类似上方名称牌
碰撞规则(collisionRule)always, never, pushOwnTeam, pushOtherTeams默认always。
即队员对哪些实体展现自己的碰撞箱。always就是对着所有实体都会展现碰撞箱,never就是永远都不会展现碰撞箱,pushOwnTeam就是只对着自己队伍的人展现碰撞箱,pushOtherTeams就是只对其他队伍的人展现碰撞箱。
当两个实体接触的时候都有展现碰撞箱就可碰撞(相互推动)。故never的永远不会被人撞到,pushOwnTeam的不会被别的队伍撞到,pushOtherTeams不会被队友撞到,always未必会被人撞到




有关队伍的具体的使用方法将在后文的实例中介绍。


记分板标签
标签(tag),本质上就是NBT 字串的List。
每个实体(不是对象,假名无法拥有tag)可以有一组标签,而那些标签不需要事先定义(这是和变量最大的分别之一)。
每个实体都有两种状态:有某个标签和没有某个标签。

标签一般是当做布尔值使用,也就是有没有做过一件事情/符合某个要求。
比如是否已经被系统处理过,或者某个NBT标签符合条件,等等。

变量 能够储存分数,也就是说我们能用分数当作状态。
如我要记录一名玩家出发的状态,我们可以以0代表还没出发,1代表在a段路上,2代表在b段路上,如此类推直到最后一个数值代表完成路程。
但是标签,就无法方便地做到这项功能,因为只能判断在与不在。然而,如果要判断一些能够同时存在的东西并且只有两种可能(是与不是)的时候,则可以使用标签。比如说判断玩家在沙漠,和判断玩家在沙漠神殿,两者可能同时出现(沙漠神殿自然在沙漠)并且只有两种可能(在那地方和不在那地方),那么我们就可以使用tag了。

此外,标签也能在实体生成时加上,十分方便。NBT为: Tags:["标签1"...] 。例子:
  1. summon armor_stand ~ ~ ~ {Tags:[tag1]}
复制代码
如果要检查多个标签的话,我们需要execute。如我们要检查有tag1、tag2、tag3的实体,让它们 say hi
  1. execute @e[tag=tag1] ~ ~ ~ execute @s[tag=tag2] ~ ~ ~ execute @s[tag=tag3] ~ ~ ~ say hi<code></code>
复制代码
标签有一点比变量好的地方:不用初始化。对象没有分数是没法检测的,而没有标签直接 tag=! 标签就能检测,省却了初始化的步骤(对象需要+0分来初始化分数)

    注意: 很重要的一点是,玩家死了之后标签还是不会改变的,就像分数一样

然而有时候我们会选择使用记分板,就是当玩家可能下线并且我们要对该玩家的状态进行操作时。本教程最后的例子中的皇冠系统就有这情况了。
使用tag的例子可以参见 下雨检测。里面展示了怎么以tag标记有特定NBT的实体。



记分板命令
本章将简略地介绍记分板的相关命令以及使用实例。注意,除了operation相关的命令有特殊情况,其它命令中关于玩家的参数,都可以使用*代指记分板上的所有玩家。

变量相关命令

  1. scoreboard objectives add <name> <criteria> [display name...]
复制代码
创建一个新的变量,name为变量名,criteria为判据,display name为显示名称。

实例:加入一个名为a,判据为dummy,显示名称为scb的变量:
  1. scoreboard objectives add a dummy scb
复制代码

  1. scoreboard objectives list
复制代码
列出当前的变量及其类型。

实例:直接输入该命令即可。效果如下:



  1. scoreboard objectives remove <name>
复制代码
移除一个变量。name为变量名。

实例:移除一个名为a的变量:
  1. scoreboard objectives remove a
复制代码

  1. scoreboard objectives setdisplay <slot> [objective]
复制代码
设置变量显示位置。slot为位置,objective为变量名。

实例:设置变量a显示在侧边栏(sidebar):
  1. scoreboard objectives setdisplay sidebar a
复制代码
显示效果及可用位置见4.1 记分板变量部分。

变量对象相关命令
  1. scoreboard players list [对象名称]
复制代码
列出记分板上的对象。对象名称填写则显示对象名称在具体变量上的分数。

实例1:不填写参数,直接输入时:


实例2:输入: scoreboard players list KakagouLT
显示:

可以看到显示的格式为-显示名称:分数(名称)。


  1. scoreboard players set <对象> <变量> <分数> [NBT]
复制代码
设置对象在变量上的分数。NBT用于指定符合条件的实体。(Base tag为entity)注意,在set分数之前,玩家是不存在分数的。

实例:给玩家A在b上的分数设置为10:
  1. scoreboard players set A b 10
复制代码

  1. scoreboard players add <对象> <变量> <数值> [NBT]
  2. scoreboard players remove <对象> <变量> <数值> [NBT]
复制代码
给对象的指定变量加/减分,<数值>为修改量。NBT用于指定符合条件的实体。(Base tag为entity)

实例1:给玩家B在b上的分数加5(原来必须存在分数,否则功能与set相同):
  1. scoreboard players add B b 5
复制代码
实例2:给玩家B在b上的分数减5(原来必须存在分数,否则功能与set相同且设置的分数为负分):
  1. scoreboard players remove B b 5
复制代码
特例1:给玩家初始化在变量A上的分数。如果原先没有分数则设置为0,否则则不改变分数:
  1. scoreboard players add @a A 0
复制代码
应当指出,加分减分以及分数设置命令不适用于判据为只读型的变量。

  1. scoreboard players reset <对象> [变量]
复制代码
将对象从记分板上移除。这项操作不是把分数设为0,而是不再追踪对象。如果填写了变量,则只将对象从这个指定的变量中移除。这个命令也可以让只读型变量不再追踪真实玩家。

实例:将玩家A从变量b中移除:
  1. scoreboard players reset A b
复制代码

  1. scoreboard players enable <对象> <trigger变量>
复制代码
这是一个与触发器相关的命令,可以给予玩家修改某个判据为trigger的变量的权限。我们将在下文trigger部分详细介绍这个命令的用法。


  1. scoreboard players test <对象> <变量> <min> [max]
复制代码
检测玩家在变量上的分数是否在min(最小值,大于等于)和max(最大值,小于等于)之间。如果不填max,则默认max为2,147,483,647。如果min为*,则视为min是-2,147,483,648。

当检测成立,效果和testfor命令检测成立时相同。

实例1:检测玩家A在b上的分数是否处于200到300之间:
  1. scoreboard players test A b 200 300
复制代码
实例2:检测玩家E在b上的分数是否大于等于100,即在100到2147483647的范围内:
  1. scoreboard players test E b 100
复制代码

  1. scoreboard players operation <targetName> <targetObjective> <operation> <selector> <objective>
复制代码
进行分数的数**算。运算方式为operation。将被运算的分数分别是:targetName在targetObjective的分数,和selector在objective上的分数。以下简称这两个分数为tarsel

对于+=,-=,*=,/=,%=五个运算符来说,相关的操作为在tar的基础上加/减/乘/除/求余sel。

另外,除法结果若有小数则丢弃小数位的数据。除/求余0的操作不会改变分数。负数的处理方式与数**算方式相同。

=的作用是将tar设为sel。

对于<,>两个运算符来说,相关的操作为:如果不满足tar小于/大于sel,则将tar设为sel。

><的作用是交换两个数的值。

实例:下面的表格展示了各个操作的对应结果:
原分数tar(左)原分数sel(右)运算符结果分数tar(左)结果分数sel(右)注释
32+=52-
32-=12-
32*=62-
32/=12丢弃小数位的数据
42/=22-
30/=30除0不会报错而是不会运作
-32/=-12按照数学方法处理负号
83%=23-
8-3%=2-3tar符号不变
-83%=-23tar符号不变
-8-3%=-23tar符号不变
30%=30除0不会报错而是不会运作
32=22-
32>32-
23>33-
32<22-
23<23-
56><65唯一一个会修改右边分数的运算

标签相关命令

  1. scoreboard players tag <实体> add <标签> [NBT]
  2. scoreboard players tag <实体> remove <标签> [NBT]
  3. scoreboard players tag <实体> list
复制代码
第一条命令就是,当指定实体符合NBT要求(如无NBT则不用符合)则加上标签。(无法重复加上标签)

第二条命令就是,当指定实体符合NBT要求(如无NBT则不用符合)则移除指定标签。

第三条命令就是,列出指定实体的标签。

标签无法运用于假名。

实例:
  1. # 给所有玩家加上online标签
  2. scoreboard players tag @a add online

  3. # 从移除了AI的实体身上删掉标签
  4. smartscoreboard players tag @e remove smart {NoAI:1b}

  5. # 列出所有实体的标签
  6. scoreboard players tag @e list
复制代码
list命令效果如下:


队伍相关命令
  1. scoreboard teams list [teamname]
复制代码
列出存在的队伍。指定teamname时显示队伍内的实体。

实例1:不填写参数,直接输入:

显示格式为-名称:'显示名称'有*个玩家

实例2:填写参数,输入: scoreboard teams list teamA
显示:



  1. scoreboard teams add <name> [display name...]
复制代码
创建一个新的队伍。队伍名称为name,显示名称为display name。

实例:创建一个名称为teamB,显示名称为tB的队伍:
  1. scoreboard teams add teamB tB
复制代码

  1. scoreboard teams remove <name>
复制代码
移除一个队伍。name为队伍名称。

实例:移除队伍teamA:
  1. scoreboard teams remove teamA
复制代码

  1. scoreboard teams empty <name>
复制代码
清空一个队伍里的成员。

实例:清空队伍teamB的成员:
  1. scoreboard teams empty teamB
复制代码

  1. scoreboard teams join <team> [实体...]
复制代码
让实体加入队伍team。

实例:让玩家A、B、C加入队伍teamA:
  1. scoreboard teams join teamA A B C
复制代码

  1. scoreboard teams leave [players...]
复制代码
将玩家players从队伍中移除。因为每个玩家只能加入一个队伍,所以不必指定队伍名称。

实例:将A、B、C移出队伍:
  1. scoreboard teams leave A B C
复制代码

  1. scoreboard teams option <team> color <value>
  2. scoreboard teams option <team> friendlyfire <true|false>
  3. scoreboard teams option <team> seeFriendlyInvisibles <true|false>
  4. scoreboard teams option <team> nametagVisibility <never|hideForOtherTeams|hideForOwnTeam|always>
  5. scoreboard teams option <team> deathMessageVisibility <never|hideForOtherTeams|hideForOwnTeam|always>
  6. scoreboard teams option <team> collisionRule <always|never|pushOwnTeam|pushOtherTeams>
复制代码
设置队伍相关的属性。team是队伍名称。
  • color 指定队伍颜色,可用的颜色为 black ,  dark_blue ,  dark_green ,  dark_aqua ,  dark_red ,  dark_purple ,  gold ,  gray ,  dark_gray ,  blue ,  green ,  aqua ,  red , light_purple ,  yellowwhite 。填写 reset 则清空颜色
  • friendlyfire 决定是否开启友军伤害。true为开启,false为关闭。
  • seeFriendlyInvisibles 决定是否开启队友隐身可见。参数相关意义同上。
  • nametagVisibility 决定名称牌是否可见。never为所有人(包括队伍系统外的玩家)不可见,hideForOtherTeams为其他队伍玩家不可见,hideForOwnTeam为本队玩家不可见,always为所有人可见。
  • deathMessageVisibility 决定死亡信息是否可见。参数相关意义同上。
  • collisionRule 决定碰撞规则。always为队员对所有人(因为碰撞规则而无法推动的除外)都可相互推动,never为队员可以穿过任何实体而不发生推动,pushOwnTeam为只保留队员之间的推动,pushOtherTeams为取消队员之间的推动。

有关详情请参见队伍

trigger相关命令
在前面的介绍中我们提到了 scoreboard players enable <player> <trigger> 。这个章节将介绍触发器(trigger)相关的命令和使用方法。
触发器,就是判据为trigger的变量。创建一个名称为trig的触发器:
  1. scoreboard objectives add trig trigger
复制代码
如果要让玩家修改触发器的数值,需要先给玩家设置修改权限,即使用enable命令:
  1. scoreboard players enable @p trig
复制代码
这里的@p也能修改成具体玩家的名称,或者能指定玩家的目标选择器。

当玩家获得权限后,就可以使用以下的trigger命令:
  1. trigger <objective> <add|set> <value>
复制代码
objective是触发器名称,add为增加分数,set为设置分数,value为一个分数值。

例如:
  1. trigger trig add 3
复制代码
可以使trig的分数+3(原先没有分数则设置为3)
  1. trigger trig set 5
复制代码
直接将trig的分数设置为5。

当玩家使用过trigger命令后,他就失去了继续对同一触发器使用trigger的权限。所以每次使用后都需要重新使用enable命令才能让玩家再次能使用trigger。

stats命令
Stats其实就是把命令执行统计(参见命令执行统计)绑定到一个对象的变量分数,从而获得命令执行统计并进行计算。

我们一般会用stats来:
  • 检查(指定)方块数量(使用AffectedBlocks)
  • 检查(指定)物品数量(使用AffectedItems)
  • 获得某些数据,如当前时间(使用QueryResult及某些特殊查询类命令)
  • 命令执行成功与否(使用SuccessCount)

如果是影响实体数(AffectedEntities),我们一般都能够直接使用execute命令逐个地为实体加分,故此我们大部分时间也不需要使用这项stats。而且这项统计对于大部分命令也是1(无论目标实体数量是多少),故此获得实体数量没太大意义。

绑定stats的流程:
  • 初始化对象的变量分数:一般为0。如果一开始已经有分数则不必初始化。
  • 执行stats命令,其格式将会在下方仔细描述。

其中初始化是十分重要的,没初始化导致错误是新手经常发生的错误。

命令格式
Stats命令格式(实体)
  1. stats entity <目标实体> clear <命令执行统计>
  2. stats entity <目标实体> set <命令执行统计> <对象> <变量>
复制代码
前一条命令是用作解除绑定,后者为绑定。

目标实体为我们需要获得命令执行统计的实体。
命令执行统计就是我们需要绑定的统计,参见命令执行里的命令执行统计。
对象就是我们绑定到的对象,变量就是储存分数的变量。

简单来说就是,把目标实体的命令执行统计储存在对象的变量分数里。

至于方块(只是对命令方块有效),就是把entity <目标实体> 替换为block <x> <y> <z>,而xyz就是目标命令方块的坐标。

注意
stats的对象可能会改变,因为是直接储存选择器(如果是使用选择器),所以可能会赋值给未知的实体(因为每次赋值前都会找实体),需要小心
我们可以使用@s来代表目标实体自身,这样就避免了可能选择到错误实体的情况了。(大部分情况下也是赋值给自身)

其次,命令执行统计的结果会覆盖而不会累加,只取最后一次统计的结果。命令对所有实体执行完毕后才开始统计结果。(详细将会在之后execute命令部分详细说明)

最后,如果需要同时对一堆实体设置相同的stats给他们自己的变量,并不需要使用execute,因为stats是直接把对象写进该实体的NBT里的。直接把对象设置为@s就好,详情看下方例子。

实例——检查物品栏物品数量
通过NBT的Inventory我们也能检测物品数量,然而有以下限制:
  • 不能检测超过1组物品(同一种物品被分别放在不同格子),因为NBT的list不能检测重复
  • 需要穷举,比如64个物品就得穷举64次...

所以我们一般是使用clear命令的,格式:
  1. clear [玩家] [物品id] [伤害值] [数量] [NBT]
复制代码
  • 玩家为需要删除/检测物品的玩家,不选择玩家则默认自己。
  • 物品id为需要删除/检测的物品的id。
  • 伤害值为需要删除/检测的物品的伤害值,-1或不填写为不检测伤害值。
  • 数量为最多删除的物品数量,0则不删除。
  • NBT为删除/检测的物品所需要符合的NBT,base tag 为 tag。

这命令的AffectedItems为删除物品数量,然而如果数量为0(即不删除),则返回符合条件的物品数量。

比如我们要检测玩家背包里的石头数量,我们可以如此检测:
  1. # 假设有一个记分板变量叫stats
  2. stats entity @a set AffectedItems @s stats

  3. # 让每个玩家自己分别检测自己的石头数
  4. # 这样每个玩家都是命令执行者,执行着 检测自己的石头数 的命令
  5. execute @a ~ ~ ~ clear @s minecraft:stone -1 0   
复制代码


记分板实战教学
在先前的章节中,我们已经了解了记分板相关的所有命令。然而如果你无法记下这么多命令的话——没关系。接下来的教程将会通过实战的方式向你展示这些命令的用法,我们将在这一过程中重新复习记分板命令。
  • 4.5.1 循环电路——记分板的基础操作
  • 4.5.2 自定义命令——stats与记分板
  • 4.5.3 击杀率——operation与准则
  • 4.5.4 漂亮的排名——显示与队伍
  • 4.5.5 简单登入系统——function与综合运用


实例1:循环电路——记分板的基础操作
在将来使用命令的过程中,会有很多使用到循环电路的地方:时钟、刷新事件、做迷宫。接下来会详细地介绍记分板的基础操作,并在这个过程中实现循环电路的制作。

准备工作
首先,你需要设立一个虚拟型(dummy)变量,名称为loop,用它来记录分数加减:
  1. scoreboard objectives add loop dummy
复制代码

为了直观地看到分数的变化,把这个变量设在侧边栏(sidebar)显示:
  1. scoreboard objectives setdisplay sidebar loop
复制代码

由于loop上还没有任何的分数,所以你应该不会看到屏幕上的任何变化。别急,你现在需要往这个变量添加一个玩家。但现在我们不准备把你添加上去,因此我们设一个虚拟的玩家(也就是之前提到的假名),名字为Loopman
  1. scoreboard players set Loopman loop 0
复制代码

好了,现在你应该会看到你的屏幕旁边出现了一个侧边栏,标题是目标名loop,列表中的是目标正在追踪的玩家:Loopman,其分数为0——这就是我们要的东西。


初次实现
作为一个循环结构,我们的流程大概是这个样子:

那么我们试着按这个流程做一个200刻间隔的“雷电发生器”。

  • 设置一个加分的循环型命令方块,输入命令:给变量loop中的玩家Loopman分数加1
    scoreboard players add Loopman loop 1
  • 先别急着激活,在它的指向处放一个连锁型命令方块,输入命令:检测 变量loopLoopman的分数是否处于200到201之间
    scoreboard players test Loopman loop 200 201
    你也可以不填后面的201,那么这个命令就会检测分数是否在200到2147483647之间。
  • 再连接两个连锁方块,第一个负责将Loopmanloop上的分数减去200以将分数归零:
    scoreboard players remove Loopman loop 200
    你也可以直接将分数设为0
    scoreboard players set Loopman loop 0
    第二个执行对任意非玩家生物雷击的操作:
    execute @e[type=!Player] ~ ~ ~ summon LightningBolt
    然后把以上两个命令方块调到条件制约和保持开启模式。

好了,现在你的命令方块电路应该长这样:

然后往那个循环方块的旁边放红石块来激活(也可以调成保持开启模式),大功告成!

再次实现
事实上,在刚才的例子中,还可以有更简便的制作方法:把你放上记分板。但这样在你把地图分发给伙伴时可能会出问题,这就要看你的意愿了。

如果要这么做的话:
  • 在准备工作中要把假名换成你的名字。
    scoreboard players set 你的名字 loop 0
  • 从第二步的检测开始,把命令改为:
    execute @e[score_loop_min=200] ~ ~ ~ scoreboard players set 你的名字 loop 0
这样你就能省去那个归零分数的命令方块了。


循环电路能做的当然不止这些。想象一下,你现在需要复制出一块很大的地盘,但是clone命令最多只能同时复制32767个方块,这时候一个一个地去复制的话就太麻烦了点。使用一下嵌套循环的小技巧,你就可以在几分钟内完成这项工作。

准备工作也是需要设置目标、假名、侧边栏,但是你还需要再设置一个新的假名。相信你可以自己完成这项添加工作。我们设这个假名为Tpman。

准备工作完成,接下来干什么?想象一栋小房子,坐标为0 4 0到4 8 4,我们要把这座房子复制成一座小区。流程图如下:

因为要确定复制的坐标,所以需要有一个标志物。大多数玩家选择盔甲架作为标志物,有时候一些玩家还会选择药水云。为了方便观察,我们这里使用盔甲架。把这个盔甲架放在[0 4 -1]的位置。

  • 第一个方块,循环方块中的加分命令同上面一样。
    scoreboard players add Loopman loop 1
  • 接着的命令方块是移动标志物与复制房子的命令:
    tp @e[type=minecraft:armor_stand] ~8 ~ ~
    execute @e[type=minecraft:armor_stand] ~ ~ ~ clone 0 4 0 4 8 4 ~ ~ ~1
  • 到了检测的时候,命令也是和上面的一样,稍微改一下数值就行了。
    scoreboard players test loop Loopman 20 20
  • 当条件成立,接着归零Loopman的命令与转行的移位命令:
    scoreboard players set loop Loopman 0
    tp @e[type=minecraft:armor_stand] ~-160 ~ ~8
  • 然后Tpman加分,检测Tpman分数:
    scoreboard players add loop Tpman 1
    scoreboard players test loop Tpman 20 20
  • 当条件成立,直接删除开启循环方块的红石块:
    setblock <x> <y> <z> air
    坐标由你自己决定。

大功告成!现在放下红石块吧!你的电路应该是这样的:


如果你发现出错,看看你有没有新建Tpman,或者有没有重置Loopman的分数。








实例2:自定义命令——stats与记分板
在制作地图的时候,你想让玩家随时随地都可以触发一段预设好的命令——比如在玩家所处的位置放一簇烟花。但你总不能让玩家带着一个命令方块,也不能一直提示玩家打一条放置红石块的setblock命令吧?下面就介绍一下如何制作一条“自定义命令”,让玩家轻松地开烟火大会。

初次实现
有了实例1的经验,这一节就应该轻松一些了。
之前在介绍命令时我们提到过,普通玩家不能使用scoreboard命令,但可以通过trigger命令修改触发器,所以我们先用触发器实现自定义命令。

先新建一个触发器,名字叫Firework:
scoreboard objectives set Firework trigger

接着往一个单独的循环方块中放入让所有玩家能够操作触发器的enable命令:
scoreboard players enable @a Firework

然后把这个方块调成保持开启模式。一旦玩家用trigger操作了触发器,要想再次操作,就得重新使用enable命令。这就是把enable放入循环方块的原因。

到这里准备工作就结束了,提示玩家使用trigger吧。提示方式随你选择,木牌或是say都可以。

最后的实现就是检测玩家的分数以执行相关操作了,同循环电路中的那样,检测,检测成立时复位,召唤烟火。

方块的模式设置也很简单,检测命令放入保持开启的循环方块,剩下的命令全部放入连接循环方块的条件制约连锁方块。别忘了开启连锁方块!

具体操作相信你能自己完成,不懂的话可以再看看第一个实例。

如果你需要放烟花的命令,可以看看社区的教程。

再次实现
如果你曾经接触过自定义命令,你会发现它们大多是用Json文本中的点击事件(clickEvent)或者gamerule制作的。因为使用trigger需要两个持续执行命令的循环方块,会占用一定的游戏资源。而gamerule只用到一个循环方块——持续地检测而已。至于点击事件,更是不需要循环方块。

这里介绍与记分板相关的gamerule实现,如果你对点击事件的实现方法有兴趣,可以看看2.1 JSON文本的介绍。

关于 stats
stats命令的功能是根据某个命令的执行情况,将有关数据推送到记分板上。你可以在1.5 命令执行统计以及4.4 记分板命令了解stats的更多信息。

/stats一共有五种模式,这次我们要用到的是QueryResult。这种模式将命令查询得到的返回值立即推送到记分板上。

举个例子,如果命令方块中的命令是time query daytime,使用/stats将记分板与命令方块绑定后,记分板上的分数会在命令执行时更改为当前的时间值。如果命令方块是循环型且开启中的,则相关分数在每一游戏刻都会改变。


关于 gamerule
gamerule命令可以用以管理世界的规则,比如是否进行日夜交替,生物是否生成等。

规则是可以自定义的,这正是gamerule可以用来制作自定义命令的原因。

自定义规则很简单,它和假名一样不必声明,直接输入 gamerule 自定义规则名 数值 进行设置即可。输入后游戏会自动创建新规则且将对应数值存入其中。
gamerule如果只填规则名,不填写将要修改的数值,就会返回这个规则当前的值。如果用stats加以联系,这个值就会存到记分板。太好了,着手制作电路吧!

注意,游戏规则的数值需要为 数字/true/false 才能返回分数。true为1,false为0。

制作系统
  • 首先新建一个虚拟型变量Firework。命令就由你自己去输入了。
    为了储存分数,还要新建一个假名,在这里设为Result。命令同样由你自己完成,不知道的话可以复习前一个实例。
  • 然后新建自定义一个名为HaveFun的规则,值设为0:
    gamerule HaveFun 0
  • 放置一个循环方块,输入查询HaveFun值的命令:
    gamerule HaveFun
  • 离开命令方块界面,用stats将这个循环方块与刚才的Result连接,使接下来命令查询到的值能推送到Result在Firework上的分数。
    stats block <x> <y> <z> set QueryResult Result Firework
    坐标的值就是命令方块的值了,如果你的准心指向命令方块,还可以用Tab键对命令自动补全。
到这里核心内容就结束了,有了记分板的值,你就可以按 检测->执行命令->对分数复位 的流程继续构造你的电路。

复位不能像循环电路那样直接修改记分板,应该使用gamerule HaveFun 0修改规则值!

另外,如果你想实现不同的值对应不同的效果,可以设置多个检测线路,这就留给你自己去尝试了。






实例3:击杀率——operation与准则
在和朋友们比赛射击的时候,有什么办法决定谁才是神射手呢?用击杀数来决定吗?这的确是一个办法,但是你不知道这个击杀数最多的人是真正的神射手,还是仅仅靠着无数乱箭夺得第一。要解决这个问题,我们可以试试用operation计算玩家的击杀率。

单人模式的实现
击杀率怎么计算呢?那自然是用击杀数除以射箭数了。


要得到射箭数,我们可以用的判据是stat.useItem.minecraft.bow(物品使用:弓),设这个变量为shootCount
  1. scoreboard objectives add shootCount stat.useItem.minecraft.bow
复制代码
然后我们可以用playerKillCount(击杀玩家数)统计击杀数:
  1. scoreboard objectives add killCount playerKillCount
复制代码
除此之外,如果不只是计算击杀玩家的数量,还可以使用其它判据:
  1. scoreboard objectives add killCount totalKillCount #统计杀怪数量
  2. scoreboard objectives add killCount stat.killEntity.minecraft.pig #统计杀猪数量
  3. scoreboard objectives add killCount teamkill.red #统计击杀红队成员的数量
复制代码
接下来就是计算了!假设要计算的玩家叫P:
  1. scoreboard players operation P killCount /= P shootCount
复制代码
大功告……等会!好像有什么问题。

问题大大的有。还记得记分板的分数限制范围吗?那就是整数-2147483648到2147483647。这就意味着,不管你怎么除,结果都会是0。就算你再对分数乘上100,结果不会把“隐藏的小数位”显示出来,因为小数位已经被系统丢弃了。

因此,在计算击杀率之前,先要将击杀数乘100:
  1. # 先设置一个常量,用以乘100:
  2. scoreboard objectives add constant dummy
  3. scoreboard players set oneHundred constant 100

  4. # 要保存击杀数,可以另起一个变量:
  5. scoreboard objectives add killRate dummy 击杀率

  6. # 把击杀数复制到killRate
  7. scoreboard players operation P killRate = P killCount

  8. # 然后将killRate乘100:
  9. scoreboard players operation P killRate *= oneHundred constant

  10. # 最后进行计算。
  11. scoreboard players operation P killRate /= P shootCount

  12. # 最后这三步可以写成命令函数,这样就可以立刻显示击杀率而不会产生分数闪烁了。别忘了把分数显示出来哦!
复制代码

多人模式的实现
如果要在多人模式中实现击杀率的统计,就需要对命令进行一些微调——你总不可能穷举玩家名吧?

在1.12中加入了新的选择器@s,它的作用是选择命令执行者。接下来就会用到这个选择器

由于击杀数、射击数是自动统计的,所以最终要修改的就是最后三条命令:
  1. # 写成function,命令放入循环方块高频执行,即可实时统计击杀率!
  2. execute @a ~ ~ ~ scoreboard players operation @s killRate = @s killCount
  3. execute @a ~ ~ ~ scoreboard players operation @s killRate *= oneHundred constant
  4. execute @a ~ ~ ~ scoreboard players operation @s killRate /= @s shootCount
复制代码
当然,你可能会想统计命中率:根据射中的数量而不是击杀的数量决定玩家的射击能力强弱。但是这需要结合NBT标签、新的进度系统以及更多的命令,在多人模式下尤为复杂,不只是记分板知识能够解决的,这里就不作介绍了。




                                                                                                

实例4:漂亮的排名——显示与队伍
不得不说,玩家之间的竞技是Minecraft的一个重要组成部分。而在记分板系统的帮助下,组织这类竞赛将会更加方便,也更具观赏性。现在假设一个情境——在迷宫中进行的死亡竞赛。

准备工作
很遗憾,这个教程并不准备教你如何搭建一个迷宫。这个工作留给你自己。

现在来构思一下,除了迷宫本身,完成这个地图需要什么:
  • 一个变量,记录击杀得分
  • 侧边栏,将分数显示出来
  • 两个队伍,这两个队伍互相之间进行对抗

前两个任务在前面的实例中已经介绍过了,现在只需要回顾一下队伍的命令就可以了。

好,让我们开始吧!

初次实现
现在创建两个队伍,一个叫CT,一个叫T:
  1. scoreboard teams add CT
  2. scoreboard teams add T
复制代码


这两个队伍使用不同的颜色,那么就需要用修改color:
  1. scoreboard teams option CT color blue
  2. scoreboard teams option T color gold
复制代码
同时因为友军火力默认开启,我们又不希望友军之间误伤,所以要修改friendlyfire:
  1. scoreboard teams option CT friendlyfire false
  2. scoreboard teams option T friendlyfire false
复制代码
名称牌在迷宫中是个问题——它会带来透视一样的效果。所以要把名称牌隐藏起来:
  1. scoreboard teams option CT nametagVisibility hideForOtherTeams
  2. scoreboard teams option T nametagVisibility hideForOtherTeams
复制代码
当然也可以把hideForOtherTeams换成never,这个由你自己决定。

接下来是碰撞的问题。在这种娱乐竞赛中同队玩家一般不采用碰撞,但是和其他队伍之间应该保留碰撞。所以修改一下collisionRule:
  1. scoreboard teams option CT collisionRule pushOtherTeams
  2. scoreboard teams option T collisionRule pushOtherTeams
复制代码
好了,队伍的基本设置完成了。现在还差一样,修改前缀。但是option里并不提供前缀修改,怎么办呢?
这里提供一个NBT修改器的下载地址:
http://www.mcbbs.net/thread-306895-1-1.html

打开NBT Explorer,按照4.2 记分板队伍提供的路径,找到前缀并修改、保存。打开游戏,你就能看到类似这样的效果了:




(这里使用的前缀是[T]。这个例子只修改了前缀,如果你要添加后缀,务必记得在末尾加上§r。)

到这里队伍的设置就结束了。接下来是设置变量和显示在侧边栏:
  1. scoreboard objectives add dmscore playerKillCount

  2. #因为friendlyfire已经关闭,所以可以直接统计击杀玩家的次数
  3. scoreboard objectives setdisplay sidebar dmscore
复制代码
完成!要开始游戏,还得把小伙伴加入队伍中。
  1. scoreboard teams join T @p
  2. scoreboard teams join CT @p
复制代码
关于人数平衡的解决方法就请你自己探究了。
在这里提供一个人数平衡问题的解决思路:
  • 设置命令方块,附上按钮或者踏板,让玩家自己选择队伍
  • 每有一个玩家加入一个队伍则给对应变量加分
  • 如果分数达到限定值则更改加入队伍命令方块的命令,比如显示“队伍已满”
  • 还有很多种方法,你可以自己发挥想象!

你还可以给游戏计时,时间到了以后把所有玩家移出队伍并传送到指定位置。可以使用的选择器参数为team。

再次实现
实际上刚才的装置比较简陋,适合小伙伴之间的小型比赛,如果要拿来作为地图发布或许是拿不出手的。因此我们还可以对现有的系统进行进一步的优化。

给名字加上颜色
如果单单设置前缀为队伍名称可能有些单调,我们可以用样式代码修改前缀。例如,前缀 §6[T] 可以使队伍拥有如下效果:

有关样式代码的更多信息见2.2.2 NBT实战样式代码部分

只统计游戏参与者的击杀数
在上面的实例中使用了playerKillCount这个判据,然而如果在一个大服务器中运行这样的游戏,可能会出现无关人士上榜的现象。因此可以结合teamkill.判据和operation命令实现只统计游戏参与者的击杀数。具体命令如下:
  1. #新建变量
  2. scoreboard objectives add dmscoreT teamkill.blue
  3. scoreboard objectives add dmscoreCT teamkill.gold

  4. #高频运行赋分命令
  5. execute @e[team=T] ~ ~ ~ scoreboard players operation @s dmscore = @s dmscoreT
  6. execute @e[team=CT] ~ ~ ~ scoreboard players operation @s dmscore = @s dmscoreCT
复制代码

美化的侧边栏
侧边栏的功能并不只是显示玩家和分数,利用侧边栏,你甚至可以做出一个显示屏!现在我们利用侧边栏做一个加入队伍的信息提示。
首先要添加变量:
  1. scoreboard objectives add info dummy
复制代码
公告栏MC里不能直接输入样式代码,所以我们用function来制作带颜色的假名:
  1. # 以下命令使用function运行
  2. # 分数用来排序信息
  3. # 第四行输入两次§4是为了区分假名。reset命令清除一个带样式代码的假名时,也需要带上相同的样式代码
  4. scoreboard players set §4============ info 3
  5. scoreboard players set §6加入T队请到红区 info 2
  6. scoreboard players set §1加入CT队请到蓝区 info 1
  7. scoreboard players set §4§4============ info 0
复制代码
效果如下:



统计队伍总分
这个只需要每个队伍一条命令就可以了(但是此方法不适用于假名):
  1. scoreboard players operation TotalT dmscore += @e[team=T] dmscore
  2. scoreboard players operation TotalCT dmscore += @e[team=CT] dmscore
复制代码
如果在function中运行,你还可以顺便为这两个总分计数器加上颜色。






实例5:简单登入系统——function与综合运用
本例子将会使用function
代码块第一行如果为# xxx:xxx就代表是function

准备工作
首先我们来分析一下记分板登入系统需要什么:
  • 检测用户第一次出现以注册,我们会使用tag。
  • 注册系统,我们会使用trigger命令记录用户分数,分数即密码。
  • 检测登入,我们会使用stat.leaveGame判据。(玩家登出了再进来就是登入了)
  • 获取玩家输入,我们也会使用trigger命令记录输入的“密码”。
  • 比较密码是否正确,我们会使用scoreboard players operation。
变量:
  • password: trigger。记录用户密码
  • leaveGame: stat.leaveGame。记录玩家登出次数,登入密码正确后重置。(>=1就是登入了)
  • input: trigger。记录用户密码输入
标签:
  • registered: 记录玩家有没有注册过。
初次实现
首先我们先加上那堆变量
  1. # login:init
  2. scoreboard objectives add password trigger
  3. scoreboard objectives add leaveGame stat.leaveGame
  4. scoreboard objectives add input trigger
  5. # 高频执行高频function
  6. gamerule gameLoopFunction login:main
复制代码
然后我们编写注册系统。(将会高频执行)
  1. # login:register

  2. # 如果有了分数,那就是注册好了,因为默认没有分数
  3. # 显示**信息
  4. tellraw @a[tag=!registered,score_password_min=-2147483648] ["注册完毕!"]

  5. # 加上**的tag
  6. scoreboard players tag @a[tag=!registered,score_password_min=-2147483648] add registered

  7. # 让玩家可以注册
  8. # 放在前面的话,会让玩家重新注册
  9. # 放在这里的话就能保证注册过的玩家必定拥有标签,不会再次enable
  10. scoreboard players enable @a[tag=!registered] password
复制代码
接着到密码检测系统(假设是让还没登入,并且有input分数的玩家执行)
  1. # login:check_pw

  2. # 如果a=b,a-b=0
  3. scoreboard players operation @s input -= @s password

  4. function login:succ if @s[score_input=0,score_input_min=0]
  5. function login:failed unless @s[score_input=0,score_input_min=0]
复制代码
  1. # login:succ

  2. # 登入成功
  3. tellraw @s ["登入成功"]
  4. scoreboard players set @s leaveGame 0
  5. scoreboard players reset @s input
复制代码
  1. # login:failed

  2. # 登入失败
  3. tellraw @s ["登入失败,请再次尝试"]
  4. # 重置使玩家重新进入待输入密码状态
  5. scoreboard players reset @s input
复制代码

不使用execute的原因:
如果我们不分开function的话,我们需要不停execute来实现function if的功能,无疑是让命令可读性降低很多的;
而且我们需要在每条命令加上参数,每次都要重新选择实体,损害了命令的可读性之余更让命令的效率降低。(总之不是为了偷懒少写一些字符啦)
你可以试试写一个不用function的,相信会多了很多execute命令。

然后我们来写高频的系统了,大部分东西都在这里进行。
  1. # login:main

  2. function login:register

  3. # 如果leaveGame>0的话就代表这玩家重新登入了。(如果登入失败的话这分数还是>0所以能让他们再次尝试)
  4. scoreboard players enable @a[score_leaveGame_min=1] input

  5. # 如果leaveGame>0同时input有分数的话就代表那玩家输入了密码
  6. execute @a[score_leaveGame_min=1,score_input_min=-2147483648] ~ ~ ~ function login:check_pw
复制代码
然后,开始的时候运行一次以下命令,初始化系统
  1. function login:init
复制代码
大功告成!

再次实现
看着这系统是不是觉得很简单呢?没错,确实简单的过分——因为我们没加入限制未登入玩家的操作等功能(在简单的系统在,限制功能可以和leaveGame的分数联动)。让我们试试优化这系统吧!

但不是现在。由于难度提升,在之后实战章节将才会有一个完整版本的登录系统哦!不只是分数,而是能够输入一些英文字符,不容错过!
那个完整版只会展示输入和检查的部分,其他部分和这例子类同。



这么简单的系统可没有章末效果图哦