切換
舊版
前往
大廳
主題

【雪色】【指令】用指令做出排名系統、與了解排序執行的功用《難度極高!請慎入》

雪色 | 2017-12-09 23:31:13 | 巴幣 8 | 人氣 955

(此篇適用於java版MC 1.12以前的版本)

前提──排序執行的定義
排序執行的定義上是在同一個執行時間(同一個tick)中,
可以對多個實體有「順序」執行的效果:
可以做出類似多人適用的傳送箭矢(不會受到距離所影響)
'
(將所有目標進行分數的排序後,依序從實體1、2、3、4、...、8上執行)

或是單純的不同時的輪流施放技能(以一個座標點(位置)做為中心控制順序)

(以紅點為中心,先從實體1執行、再從實體2執行、最後從實體3執行)

而以下要先介紹如何給予實體「順序」,也就是給予分數進行排序:

實體分數排序方式與用途
是指將多個實體進行編號排列,
像是把10個實體的分數排列分數1~10之中的一個,
(有點類似於矩陣(Array)的做法:將10個物件各自順序的編號在同個矩陣中)
這很適合用在遊戲結束時的「結算排名」功能,
而我們會將這種分數排序分成兩種方式執行:
  1. 直接排序實體的分數(即是直接將多個實體進行分數排序)
  2. 間接排序實體的分數(會利用前提的距離控制順序的方式來處理)

一、直接排序實體分數

我們可以利用execute或是超頻執行(詳見文章:超頻執行指令)的方式,
將多個實體無視順序的排列各自的分數。
(由於execute用法會比較省資源與長度,所以在此只先介紹execute用法,
如果需要超頻執行運作直接排序分數的用法,可以在下方留言讓我知道。)

execute用法示範:
假設我們要將加入tag為getOrder的玩家進行記分板為order的各自分數排列(由小到大)

main/order.mcfunction文件裡面的內容:
execute @a[score_order_min=1] ~ ~ ~ scoreboard players add @p[x=0,y=0,z=0,tag=getOrder] order 1
scoreboard players add @p[x=0,y=0,z=0,tag=getOrder] order 1
scoreboard players tag @p[x=0,y=0,z=0,tag=getOrder] remove getOrder

假設有3個玩家A、B、C有tag為getOrder,運行這條指令:
execute @a[tag=getOrder] ~ ~ ~ function main:order
   最後會得到的結果為:A、B、Corder分數各自為1、2、3


原理:
我們先慢慢看過每條指令的執行狀況:
1. execute @a[tag=getOrder] ~ ~ ~ function main:order
從有getOrder標籤的各自玩家身上執行main:order
由於有3個人(A、B、C),所以已知會執行3次main:order

2.
execute @a[score_order_min=1] ~ ~ ~ scoreboard players add @p[x=0,y=0,z=0,tag=getOrder] order 1
order分數大於1的所有玩家給予離中心座標(0,0,0)最近的玩家order分數為1

3. scoreboard players add @p[x=0,y=0,z=0,tag=getOrder] order 1
給予離中心座標(0,0,0)最近的玩家order分數為1

4. scoreboard players tag @p[x=0,y=0,z=0,tag=getOrder] remove getOrder
移除離中心座標(0,0,0)最近的玩家的getOrder標籤

由上述執行狀況可以知道,第2、3、4步驟一共會跑過3次,
再來,你要知道Minecraft的execute和function指令皆存在著一種獨特性:

/execute後面的指令跑完前不會繼續跑下一個指令
/function所執行的函數檔案裏面的指令都跑過一次前不會繼續跑下一個指令

而利用這種特性,從A、B、C三者各自身上跑過一次main:order,
會是以
A跑過一次main:order後再換B跑過一次main:order後再換C跑過一次main:order

因此,當A跑過一次main:order時,A的order分數會變成1,
再換B跑過一次main:order時,最後B的order分數會變成2,
最後再換C跑過一次main:order時,而C的order分數會變成3。




而這種作法有一個很類似的作法可以做出反序排列(由大到小)

假設我們要將加入tag為getOrder的玩家進行記分板為order的各自分數排列

main/order.mcfunction文件裡面的內容:
execute @a[score_order_min=1] ~ ~ ~ scoreboard players add @s order 1
scoreboard players add @s order 1
scoreboard players tag @s remove getOrder

假設有3個玩家A、B、C有tag為getOrder,運行這條指令:
execute @a[tag=getOrder] ~ ~ ~ function main:order
   最後會得到的結果為:A、B、Corder分數各自為3、2、1

(在這裡感謝貓狗喵的修正)

二、間接排序實體分數

通常會用在將已經有分數的實體間進行「排名」,
但Minecraft並沒有辦法直接為有分數的多個實體進行分數比對後排名,
所以我們可以利用國中教過的「數線」的方式來解決

我們可以利用二分法(詳見文章:二分法)將實體的分數轉換成位置(數線上的座標)後,
利用前提所提到的「距離排序」來達成讓多個有分數的實體排名的動作,
在這裡同樣使用execute來處理,也是因為超頻執行會比較吃效能的關係
(如果你不想要tp玩家的話,你可以利用盔甲架來幫助你在數線上進行排名的動作,
如果需要這種利用盔甲架為玩家排名的話,可以下方留言讓我知道。)

數線排序法示範:
假設我們要將time分數為0以上的玩家進行記分板為order的各自分數排列

main/order.mcfunction文件裡面的內容:
tp @a[score_time_min=0] 0 70 0
tp @a[score_time_min=64] ~0.064 ~ ~
scoreboard players remove @a[score_time_min=64] time 64
tp @a[score_time_min=32] ~0.032 ~ ~
scoreboard players remove @a[score_time_min=32] time 32
tp @a[score_time_min=16] ~0.016 ~ ~
scoreboard players remove @a[score_time_min=16] time 16
tp @a[score_time_min=8] ~0.008 ~ ~
scoreboard players remove @a[score_time_min=8] time 8
tp @a[score_time_min=4] ~0.004 ~ ~
scoreboard players remove @a[score_time_min=4] time 4
tp @a[score_time_min=2] ~0.002 ~ ~
scoreboard players remove @a[score_time_min=2] time 2
tp @a[score_time_min=1] ~0.001 ~ ~
scoreboard players remove @a[score_time_min=1] time 1

execute @a[score_time_min=0] ~ ~ ~ function main:order2
main/order2.mcfunction文件裡面的內容:
execute @a[score_order_min=1] ~ ~ ~ scoreboard players add @p[x=0,y=70,z=0,score_time_min=0] order 1
scoreboard players add @p[x=0,y=70,z=0,score_time_min=0] order 1
scoreboard players reset @p[x=0,y=70,z=0,score_time_min=0] time


假設有3個玩家A、B、Ctime分數3、15、7,運行函數:function main:order
   最後會得到的結果為:A、B、Corder分數各自為1、3、2
(因為是由0,70,0(原點)開始偵測,所以結果會是以最低分往最高分排列)


原理:
我們先慢慢看過每條指令的執行狀況:
(一) main:
order內的指令目的是讓玩家傳送到數線上並排列好位置

1.
tp @a[score_time_min=0] 0 70 0
分數time>0將的玩家傳送到0,70,0(數線的原點)

2.tp @a[score_time_min=64] ~0.064 ~ ~
   scoreboard players remove @a[score_time_min=64] 64
   ...
   (即是所有灰色為底的指令部分)
進行二分法將實體的time分數轉換成座標x軸上,直到time分數變成0

3. execute @a[score_time_min=0] ~ ~ ~ function main:order2
time≥0的所有玩家身上執行main:order2
由於有3個人(A、B、C),所以已知會執行3次main:order2

(二) main:order2內的指令目的是將數線上的玩家依照距離遠近進行分數排序

4. execute @a[score_order_min=1] ~ ~ ~ scoreboard players add
@p[x=0,y=70,z=0,score_time_min=0] order 1
order分數大於1所有玩家給予離原點最近的玩家且time≥0order分數+1

5. scoreboard players add @p[x=0,y=70,z=0,score_time_min=0] order 1
給予離原點最近的玩家且time≥0order分數+1

6. scoreboard players reset @p[x=0,y=70,z=0,score_time_min=0] time
重置離原點最近的玩家且time≥0time分數

由上述執行狀況可以知道,第1到第3步是在將實體分布在一條數線上,
會讓
A被傳送到x=0.003、B被傳送到x=0.015、C被傳送到x=0.007

並且重複執行三次order2:第一次將
A的order分數為1、第二次會將C的order分數設為2
第三次則會將
B的order分數設為3
(order2內的運算原理請至剛才上方的介紹參閱)

以上是將實體的順序給排序成分數的方法,
以下要介紹如何將這些排序的分數從低到高的依序執行一次指令:


排序執行的處理方式
前提已經可以大致上的了解排序執行的功能在做什麼了,
接著你要知道execute指令超頻演算的排序執行處理方法:

execute指令將以排序分數的實體進行排序執行
A、B、C這三個玩家依次執行指令:say hi
(已經先設定A的order分數為1、B的order分數為2、C的order分數為3)

main/output.mcfunction文件裡面的內容:
execute @a[score_order_min=1,score_order=1] ~ ~ ~ say hi
scoreboard players remove @a[score_order_min=1] order 1
運行這條指令:
execute @a[score_order_min=1] ~ ~ ~ function main:output
    而最後會得到的三條訊息由上而下依次為:[A] hi[B] hi[C] hi

原理:
我們先慢慢看過每條指令的執行狀況:

1. execute @a[score_order_min=1] ~ ~ ~ function main:output
order≥1的玩家執行main:output裡的指令,
由於有3個人(A、B、C),所以已知會執行3次main:output
2. execute @a[score_order_min=1,score_order=1] ~ ~ ~ say hi
order=1的玩家執行say hi
3. scoreboard players remove @a[score_order_min=1] order 1
所有order≥1的玩家讓自己的order分數-1

上述執行狀況可以知道,利用execute來起步,是已經確定有order=1~3的實體,
並且把自己的order分數持續減1,直到為0,而當自己
order=1的時候就執行一次say hi

這種利用execute來處理排序執行的優點是在比較省資源,但會有缺點是:

如果其中的順序被打亂的話:假使
A玩家離開遊戲了,而剩下玩家BC
這會使
B只需1tick可以執行say hi,但C2tick才能執行say hi
是因為執行
main:output的次數由3次減少為2次
因此
1tick時的第一次執行output時會沒有人執行say hi,第二次執行B才成功say hi,
第二次執行完output後的Corder分數會是1,
要等到
下一個tick時再執行一次output才會讓C成功say hi。
以及一個共通性錯誤:如果有兩個以上實體分數一樣會導致錯誤


(老實說我並不推薦使用這種方法做排序執行,因為很容易就被打破循環)

超頻演算將以排序分數的實體進行排序執行:
A、B、C這三個玩家依次執行指令:say hi
(已經先設定A的order分數為1、B的order分數為2、C的order分數為3)

main/output.mcfunction文件裡面的內容:
execute @a[score_order_min=1,score_order=1] ~ ~ ~ say hi
scoreboard players remove @a[score_order_min=1] order 1
function main:output if @a[score_order_min=1]

運行函數:function main:output
    而最後會得到的三條訊息由上而下依次為:[A] hi[B] hi[C] hi


原理:
我們先慢慢看過每條指令的執行狀況:

1. execute @a[score_order_min=1,score_order=1] ~ ~ ~ say hi
order=1的玩家執行say hi
2. scoreboard players remove @a[score_order_min=1] order 1
所有order≥1的玩家讓自己的order分數-1
3. function main:output if @a[score_order_min=1]
如果有order≥1的玩家,則重複執行這個函數檔
直到所有玩家的order分數都
≥1為止

上述執行狀況可以知道,這種方式會執行到所有有order分數的玩家的order皆變成0為止,
過程中,當玩家的
order=1的時候就會輸出一次指令,
而這種方式不會因為中間缺少某個連貫的數字(像是1、2、5、6)而出錯,
如果有實體的
order最高是32的話就會超頻執行這個檔案32次
缺點也就是:
當order數值非常大的時候會執行非常多次(也是超頻執行的缺點)、
有兩個實體的order分數一樣的時候會導致處理上的錯誤
(同分的錯誤只能從排序分數的時候或是用其他方式來排除)


排序執行的使用例子
『傳送弓(可多人且不被距離所引響)
先創建一個偵測射出箭矢的記分板以及進行排序處理的記分板:
使用弓:/scoreboard objectives add useBow stat.useItem.minecraft.bow
紀錄你的排序分數:/scoreboard objectives add order dummy
執行你的排序分數:/scoreboard objectives add runorder dummy

以及取得一個傳送弓:
/give @p bow 1 0 {display:{Name:"傳送弓"},bowType:"teleportBow"}

接著持續執行下面這個函數檔內的指令(此函數檔可以隨意命名):
scoreboard players tag @a[score_useBow_min=1] add getOrder {SelectedItem:{id:"minecraft:bow",tag:{bowType:"teleportBow"}}}

execute @a[tag=getOrder] ~ ~ ~ function teleportBow:order

scoreboard players tag @e[tag=!inGround,score_order_min=1] add inGround {inGround:1b}

function teleportBow:output if @e[type=arrow,tag=inGround]
kill @e[tag=inGround,type=arrow]

scoreboard players reset @a[score_useBow_min=1] useBow

teleportBow:order函數檔案內的指令:
scoreboard players operation @s order >= @a[score_order_min=1] order

scoreboard players add @s order 1

execute @s ~ ~ scoreboard players operation @s order = @e[c=1,type=arrow] order

scoreboard players tag @s remove getOrder

teleportBow:output函數檔案內的指令:
execute @e[score_order_min=1] ~ ~ ~ scoreboard players operation @s runorder = @s order

function main:output

teleportBow:loop函數檔案內的指令:
execute @e[score_runorder_min=1,score_runorder=1,type=arrow,tag=inGround] ~ ~ ~ teleport @a[score_runorder_min=1,score_runorder=1] ~ ~ ~

execute @e[score_runorder_min=1,score_runorder=1,type=arrow,tag=inGround] ~ ~ ~ scoreboard players set @a[score_runorder_min=1,score_runorder=1,c=1] order 0

scoreboard players remove @e[score_runorder_min=1] runorder 1

function main:output if @e[score_runorder_min=1]

這個系統的一些小特點:
1.當你射出的箭矢並沒有落地的話,order分數會維持著,但並不引響下次系統的執行
  (我並沒有針對擊中生物的處理,所以這個特殊效果是故意的)

2.order函數檔的第一條指令與文章內講的方式不同(我不是以累加,而是取大於)
  (取大於是一個比用累加還有安全性的方法,可以排除一開始在分配分數時會有同分的問題)

3.使用runorder記分板來處理排序執行
  (每次有箭矢落地時必須要跑過每個order,並且會使order變成0,故箭矢落地時讓runorder
  order的數值並代替上述的執行效果)

4.當你很快速的射出兩支箭矢時,你會被傳送到第二支箭矢的位置,而不是第一支
  (由於只要射出箭矢order分數就會+1的關係,因此第一支的箭矢的order與玩家order不一樣而
  不會傳送)

(有關這個機關的運作方式是怎麼運作的你們可以想一想,如果想不出來再問我)


創作回應

寒淬霜月
趕快GP以免被發現我只看得懂前面一小段
2017-12-10 00:22:26
雪色
沒關係的,老實說我打完之後也不知道為啥會被我自己打的這麼難理解(X
2017-12-10 13:05:03
追蹤 創作集

作者相關創作

更多創作