LV. 28
GP 1k

【製作紀錄】MapleCraft - 在Minecraft裡面還原楓之谷吧!(20240119 更新)

樓主 貓狗喵 kevin22152
GP345 BP-

大家好,我是貓狗喵
有別以往直接的作品發表,這次要帶來的是一系列的製作紀錄這樣隨時棄坑都不用怕心血白費了
主題就如標題所說的是楓之谷,而且是 BB 大改前的楓之谷(一切都是情懷啊)
這個系列之前在動畫名條教學的文章就有提到過了,到了現在也已經有不少的製作進度。本來是想做一點東西就發一下紀錄的,不過發文什麼的很花時間,所以還是決定先把進度趕到一個段落到現在才發文了
如果對這系列有興趣的可以到我的 YT 頻道,會有比較即時的內容更新喔

喔對忘了一件很重要的事,由於我的專長是在指令系統,但是要還原楓之谷還需要很多場景跟怪物(或NPC)模型,因此如果有相關專長而且對這計畫有興趣的人,歡迎附上相關作品連結寄站內信來詢問喔

那麼廢話不多說,開始這次的主題吧!

目錄 (待更新的主題可能異動)

第一步:熟悉的數字最對味 ------------------------------------------(本篇)
第二步:血條會動才生動 ---------------------------------------------(二樓連結)
第三步:做出流暢的攻擊吧 (上) ------------------------------------(三樓連結)
第四步:做出流暢的攻擊吧 (下) ------------------------------------(四樓連結)
第五步:讓子彈飛一會 ------------------------------------------------(五樓連結)
第六步:殭屍菇菇動起來 ---------------------------------------------(六樓連結)
第七步:忽聞技能聲,BUFF有多少? ----------------------------(七樓連結)
天龍八部(?):然後他就死掉了 --------------------------------------(八樓連結)
第九步:在原版麥塊尋求UI是否搞錯了什麼 ---------------------(九樓連結)

番外:今晚,我想來點 ... 版本升級加優化 -----------------------(十樓連結)

第十步:黏在玩家頭上的血條 ---------------------------------------(十一樓連結)
第十一步:戳箱子與楓幣丟丟樂 ------------------------------------(十二樓連結)
第十二步:打開 Shader 的大門 - 怪物淡入 ----------------------(十三樓連結)

番外二:移動速度大解密 ---------------------------------------------(十四樓連結)

第十三步:拒絕霸凌,從怪物 AI 做起 ----------------------------(十五樓連結)
第十四步:進階 shader - 楓谷式的受傷效果 --------------------(十六樓連結)
第十五步:進階 shader - 自訂血條顯示高度 --------------------(十七樓連結)
第十六步:objmc,參戰! -------------------------------------------(十八樓連結)
第十七步:你有多猛?找出打我的怪物等級與命中率 ---------(十九樓連結)
第十八步:玩轉 damage 新世界 -----------------------------------(二十樓連結)
第十九步:來點互動吧 - interaction 的相關應用 ----------------(二十一樓連結)
第二十步:讓藥水雲退休吧!1.19.4 的效能優化 ---------------(二十二樓連結)
第二十一步:做出椅子與特效綁定 - ride 的妙用 ----------------(二十三樓連結)

番外三:BUFF 顯示超進化 -------------------------------------------(二十四樓連結)

第二十二步:為模型道具建立獨立的 ICON ----------------------(二十五樓連結)


第一步:熟悉的數字最對味
每款戰鬥遊戲幾乎都有自己的一套傷害數字顯示系統,而講到楓之谷大家腦中第一個浮現的肯定就是那個橘黃色的傷害數字了,那麼第一步就來還原那個數字顯示系統吧!

在 Minecraft 中顯示傷害數字的方式有幾種,而當前版本最簡單效果又好的就是用實體名條搭配 json 設定記分板數字了,不知道我在說什麼的可以去參考這篇:【密技】告示牌即時解譯JSON的應用
而在 1.16 新增自訂字體以後,要做出花俏的傷害數字更方便了。只要設定各個數字在自訂字體中的圖片,就可以單純的用一個顯示記分板分數的 json 來顯示特殊字型的傷害數字
然而問題出現了,如果單純把楓谷各個數字的圖片直接搬進來用,會發現成品長得像下面這個樣子
(怎麼有種遊X桃小遊戲的粗糙感
這時候搬出 Youtube 時光機來看看正版的數字長怎樣
(圖片擷取自 ayumilove8影片)
從這張圖可以看出兩個很明顯的特徵
  1. 每位數字之間是有重疊的
  2. 數字會有規律一上一下的偏移
當年大部分小遊戲都沒有特別處理這些問題,即使有也只有增加數字重疊效果而已,印象中不曾看過哪款小遊戲有把數字高低差做出來的
好了以上都是題外話,現在讓我們來把數字變得更逼真吧!
首先來讓數字有重疊效果,為了達成這個效果,是時候把超級方便的倒退字符搬出來用了 (一樣有人對這個功能有興趣的話留言跟我說,我再考慮要不要開一篇教學)
不過這時候遇到一個小問題,那就是顯示計分板的 json 是一個不可分割的字串,我們沒辦法在每一位數之間安插倒退字符。這裡我採用的作法是重複把記分板分數除 10 取餘數,藉此取得每一位數的數字,再把數字跟倒退字符照順序排好存在隨便一個虛擬 nbt list 中,之後再利用 nbt 顯示的 json 來顯示這串東西即可。比較可惜的是這個做法每一位數都要自己在最後顯示 nbt 的 json 中指定,也就是說最大能顯示的位數是固定的。不過舊楓谷頂傷也不過 99999,考慮進去光速神弩的頂傷突破也不會超過六位數,因此設定好六位數的顯示就可以包含所有舊楓谷的傷害數字了
(看起來像比較精緻的小遊戲了呢)
下一步就是把數字的高低差做出來了,數字的高低非常規律,最左邊是高的,往右依序就是一低一高一直排下去,在上一步的基礎下要達成這個效果也很簡單,只要特定位數的字體套用另外一套有高低偏移的字體就好了
到這裡看起來已經有模有樣了呢,不過眼尖的人應該已經注意到還是有一點違和感,而違和感的來源就是開頭那個數字,看起來好像有點萎縮?
其實當初收集數字圖片的素材時就有注意到,不知道為什麼同樣的數字都有兩個版本,而且另一個版本除了大了一點看不太出來有什麼差異。做到這邊我才理解那個比較大的字體的用途,果然把第一位數字的字體換成比較大的版本感覺就對了
(MapleCraft 最終版數字與上方影片擷取數字對照圖)
至於實體名條背景那條灰灰的海帶用倒退字符就能輕鬆解決,這裡就不再多做贅述

處理完傷害數字後,回血的藍色數字與玩家受傷的紫色數字也用相同邏輯就能解決
比較特別的爆擊數字也只要在前面加上爆擊符號,並且增加數字重疊的寬度也能完美還原
MISS 則是另外顯示 MISS 圖片的字體就行
最後給數字實體加上 NoGravity 與往上飄的 Motion 就大功告成了,要注意的是最省資源的藥水雲實體是不會被 Motion 影響的,這邊我是用道具實體來取代,還能設定讓他自己過一段時間就消失
文章的結尾就來看看這套數字顯示系統的效果吧
我是貓狗喵,我們下篇主題見

345
-
LV. 28
GP 1k
2 樓 貓狗喵 kevin22152
GP49 BP-
第二步:血條會動才生動
在上一步中我們完成了傷害數字的顯示,但是空有數字卻看不到自己的血量叫人怎麼玩遊戲呢?所以今天就來還原玩家的血條吧!

在大多數遊戲的戰鬥中,HP 跟 MP 是同樣不可或缺的,楓谷也不例外。因此光是顯示 HP 條是不夠的,還要把 MP 條一起做出來。以 Minecraft 原生系統來說最明顯的兩條數值就是血量跟飽食度,大多數的戰鬥地圖也是直接使用原版的血量。不過當血量基準跳脫 Minecraft 預設的 20 點血量(10 顆愛心)時,這個血量條就不是那麼好用了。而飽食度更是不受控制,在許多冒險地圖中常常慘遭直接被隱藏的命運。另外眾所周知 Minecraft 血量太多的時候愛心是非常擋畫面的,而且上限也就 1024 點血量,在楓谷以及許多遊戲的數值架構下完全不夠用,因此使用自定義血量條在 Minecraft 地圖界就有不少案例。

  • (以下段落內容偏向歷史介紹,沒興趣的可以跳過)
早在 1.11 版本的 title 指令出現 actionbar 這個功能時,就有自定義血條的概念出現(或許還有更早的,但視覺效果與實用程度肯定不那麼好),不過礙於當時指令功能的限制,原則上只能透過窮舉的方式去偵測每一種血量做不同顯示(純數字顯示的除外,不過純數字的缺點是玩家很難在短時間內判斷自身的血量狀況)
而在 1.13 更新 bossbar 之後也有人利用 bossbar 來做血量顯示,撇開我個人認為血條在上面不容易看以外,這個做法的效果各方面都挺不錯的。不過這個做法也有一個顯而易見的缺點,那就是每要多支援一個玩家使用,就要多做一套 bossbar 機關,這又是另類的窮舉了
同樣在 1.13 還有一樣更新是圖片字體的設定,在這項更新中拔掉了以往自訂字體圖片大小的限制,讓 actionbar 製作的血條變得更加精緻。同時倒退字符的出現也讓血條跟血量數字可以重疊並存(雖然很多人不是很在意它們有沒有重疊,不過楓谷的血條就是重疊的,所以勢必要用到這個做法)(我之前做的浮光地圖的血條跟數字就是重疊的,不過好像從來沒有人注意到)
到了 1.14 神器 data modify 與 nbt 的 json 元件出現後,actionbar 的顯示法終於可以跟窮舉說再見了,也因此加上魔力條等等其他數值條也變得相對容易(使用窮舉時若要顯示多個數值條,總共需要窮舉的量是各個需要窮舉的量相乘)。不過雖然可以不用窮舉法,在這版本要做出這個效果,複雜程度甚至不比少量的窮舉法低,因此窮舉法還是有它的存在價值。
另外還有一派作法是透過道具的 AttributeModifiers 設定玩家最大血量,來達成自由調整玩家當前血量的效果。不過這個效果在 1.14 前同樣免不了窮舉的命運,而且這個功能還有一個惡名昭彰的 BUG,那就是玩家的假死狀態(當透過 AttributeModifiers 調整最大血量時,若玩家更新血量(補血跟扣血都算)後,原先剩餘血量 - 當前剩餘血量 > 當前最大血量,就有高機率觸發假死狀態。雖然其他玩家看起來是正常的,但是玩家本人第三人稱會看到程度不等的死亡抽動,甚至會跳出死亡復活選單,相當干擾)

到了 1.16 自訂字體大更新之後,自定義血條能玩的花樣更多了,製作起來也相對變得非常簡單。那麼是時候好好的來還原楓谷的血條了!(欸喂前面講這麼多廢話原來還沒開始啊)

註:這邊採用的是 actionbar 的顯示方法
首先翻出楓谷的素材庫會發現,HP/MP/EXP 是連在一起的,不過為了製作方便跟視覺上比較平衡(以 actionbar 的位置來說,照原本的樣子塞進去會不對稱),戰鬥時比較不緊急的 EXP 條就把他拔掉吧
另外楓谷的 HP/MP 條是採底色加上一層遮罩圖片的模式,如下圖
(由上而下依序是底色→遮罩→合併的樣子)
當血條不是滿的時候,有顏色的部分就會用白色來取代,因此我們只要準備好白色版本的底色素材、HP/MP 顏色的長條圖片、最上層的遮罩圖片,透過倒退字符把它們依序疊在一起就完成了
先不要急著動工,把這個東西暫時放在一旁。在正式動工之前要先考慮的是幾乎所有遊戲都有做的,所謂平滑化與使用者刺激的動畫設計。想像一下若你受到傷害的時候,血條無聲無息的變短了一點,你甚至懷疑他剛剛有沒有動過,這樣你根本不太會注意到扣血了(還有扣了多少血)對吧?大部分的遊戲都會讓血條用一段時間慢慢的更新到新的長度,我認為這是因為人的眼睛對移動中的東西會比較敏感,也就比較容易把注意力放在這個變動上,也就比較容易掌握血條變動前後的差異(具體這樣設計的原因就要專長 UX 的人來解惑了,我只是依照我的感覺來描述)。即使是 Minecraft 愛心的設計同樣也有依靠閃爍來達成類似的效果。
(這個問題在浮光的地圖就有遇到,不過在 1.16 前這問題實在不好解決)
有了平滑動畫的概念之後,現在可以把剛剛被晾在一旁的素材拿回來,準備開始動工了
要想做出平滑動畫的效果,首先必須要有精細切分的血條圖片,切得越細動畫就越流暢(當然也要畫面的解析度支援),於是我把目標設定在切割成 100 份。(試想一下窮舉法如果要同時顯示 HP 跟 MP 條,那就得要窮舉 10000 次,即使用 binary search 也是相當麻煩)
那麼具體要怎麼切成 100 份呢?關於這點我想到的解決方法是十進位的表示法。當我們在寫一個三位數以內的數字時,會切分成個位數、十位數跟百位數。同樣的概念,如果我們把每一位數的數字換成對應長度的血條圖片,那不就能把三個位數的數字圖片疊加成我們要的長度了嗎?也就是說我們先用上次傷害數字顯示的方式,依序除 10 取餘數獲得每一位數的數字,再透過不同的字體檔案去顯示對應長度的圖片,三個位數寫在同一個 json 內恰好就是我們要的血條長度
實際上因為原始素材寬度與遊戲內顯示圖片比例的調整,並不會用到完整的 100 pixel 寬,每位數字對應圖片的寬度也不會剛好是 10 的倍數。在 MapleCraft 的案例中,最大我也只用到 85 pixel 寬的圖片而已。其他還有一些細節例如文字圖片之間會有一點空隙,必須要塞倒退字符,還有文字圖片的解析度與螢幕解析度是不同步的,導致有些帶小數點的圖片寬度會在某些解析度下出現裂痕的問題,雖然處理起來也是花了一番功夫,但是太過繁雜這邊就不一一解釋了。

另外這時候眼尖的人應該也注意到了一個問題,那就是這種方式做出的血條只能有一種顏色(否則在三個位數拼接的時候就會看出接痕),然而 MP 條的底色並不是單一的藍色。不過我用分布最廣的顏色當成 MP 條的顏色,看起來其實差異也不明顯(EXP 條就比較明顯了)
(上方為單一顏色版本,下方為原始版本)

有了 HP/MP 條,每次更新顯示時只要取得當前血量的百分比,轉存出各個位數的數字,再用 actionbar 來顯示就大功告成了。至於平滑動畫的設計,我是採用很簡單的方式。每次血量變動的動畫大約耗時 1 秒,也就是 20 tick,我就簡單地用 (目標血量百分比 - 當前顯示百分比) / 動畫剩餘 tick + 當前顯示百分比 來當成每 tick 要顯示的百分比,這樣就可以讓血條以等速滑動到目標百分比了。不過如果你單純這樣設計會發現,血條常常一開始都不會動,直到動畫快結束才一下子跑完。會發生這個問題主要原因是 Minecraft 的除法是整數除法,小數點會直接被捨去,也就是說如果血量的變動不多,上面的數學式子在動畫剩餘 tick 還很多時的變動量幾乎都是 0。為了解決這個問題,我讓動畫前半部的變動量增加成兩倍,減少變動量為 0 發生的可能性。雖然動畫變成不是等速的,但是呈現的效果我覺得流暢許多。(本篇最後的展示影片是沒做過 2 倍速調整的版本)

對了差點忘了提到一個技巧,為了讓 HP/MP 條保持在固定的位置,我們必須要掌握每個顯示的東西的寬度,但是 HP/MP 條本身就是各種不同寬度圖片的組合,要怎麼掌握他們的寬度呢?
答案是做多少就報銷多少。
我們事先就知道每位數字的對應圖片的寬度,這時候我們只要再做一套字體,裡面每個數字對應的圖片是相同寬度的倒退字符,這樣我們顯示數字圖片的時候,只要同樣的數字在後面用倒退版本的字體再刷一次,指標的位置就會回歸到開頭的地方了,就好像什麼都沒打過一樣,這個技巧很重要,後面會考常常用到。
(例如十位數套用的字體的 2 原本是 20 pixel 寬的血條圖片,在十位數的倒退字體中的 2 就設定成倒退 20 pixel 的倒退字符,顯示的時候就是 [{位數字套用位數字體},{位數字套用位數字體},{位數字套用位數字體},{位數字套用位數倒退字體},{位數字套用位數倒退字體},{位數字套用位數倒退字體}])

最後把 HP/MP 的數字補上去就完成一個基礎的 HP/MP 條了,透過 1.16 自訂字體套用楓谷的數字字體簡直不能更簡單,而數字顯示同樣會面臨到上面剛介紹的不同長度的問題(畢竟數字有幾位數不是固定的),不過剛剛的技巧已經介紹得很清楚了(謎:你確定?),完全同樣的招式套用過來就能解決

對,我上面說的是基礎,意思就是還有進階版
什麼你說楓谷的 HP/MP 條不就長這樣,還有什麼好進階版的?
你是忘記了,還是害怕想起來
每當被怪物撞死扣經驗值的那個瞬間,心跳總會跟某個東西一起跳上跳下的
沒錯就是警示閃爍
警示閃爍,又名寵物喝水限制器。作為提醒玩家該喝水的存在,調太高使強迫症崩潰,調太低又常常不小心掉墓碑,彷彿一股意念叫你去買一隻寵物跟寵物喝水技能(等等離題了啦

這東西會在 HP/MP 的消耗事件發生時(注意是數值的降低,不是百分比的降低,所以聖火並不會引起閃爍),如果降低後的 HP/MP 百分比低於設定的警示百分比,就會以「白黑白黑白」每張圖停留 130 毫秒的頻率覆蓋在 HP/MP 條上方。鑒於 Minecraft 最小單位是 1 tick 也就是 50 毫秒,我把閃爍的頻率設定為 2 tick 也就是 100 毫秒(這樣顯示「白黑白黑白」共 5 張圖需要 10 tick,恰好可以用 0~9 的數字完美對應)
了解運作模式之後就是實作了,上面已經說了 0~9 的數字恰好可以各自對應一張閃爍過程中的圖片,也就是說只要透過一個倒數記分板從 9 數到 0 並透過字體顯示對應閃爍圖片就能達到閃爍效果。至於要讓閃爍的圖片對齊的方式,除了同樣可以採用上面那招實報實銷,也可以透過讓閃爍分數不在 0~9 範圍內時(也就是沒在倒數的狀況下),顯示與閃爍圖片相同寬度的空格來解決

最後同樣用成品效果的影片來做結尾吧
一樣對這計畫有興趣的建築與模型製作者歡迎私信詢問
我是貓狗喵,我們下篇主題見
49
-
LV. 28
GP 1k
3 樓 貓狗喵 kevin22152
GP35 BP-
第三步:做出流暢的攻擊吧 (上)
數字有了,血量有了,動畫特效也在先前的名條動畫中介紹過,再來就是上機實戰了。今天的目標是高還原度的楓谷技能攻擊!
由於這個主題的內容非常繁雜,比較缺乏技術應用的內容就會比較簡略的帶過

在開始動工之前,必須要先規劃架構,越龐大的計劃越是如此
比如觸發技能的方式有很多種,但考慮到楓谷技能非常的多,不可能為每個技能特別設置一種操作方式(例如蹲下按 F 之類的)來施放,因此胡蘿蔔釣竿搭配自訂技能 icon 道具是個實用的選擇
而每個技能的 function 雖然可以各自編寫,但如果某天已經做了幾十個技能,卻突然要調整一個所有技能都會用到的功能,那肯定是改得要死要活。所以統一的架構就很重要,重複的功能全部用同一套 function 處理,甚至透過外部編輯器引用一個模板,只修改其中部分的內容,都能大幅降低開發的時間成本。不過這已經是程式編寫邏輯的領域了,到這邊先點到為止

(偷偷廣告時間:MapleCraft 整套資料包都是用我之前編寫的 MCDP 程式來製作,我敢說如果沒用這個工具,MapleCraft 的開發時間至少是現在的 2 倍。如果你有大型企劃強烈建議來學看看怎麼使用這套工具,有興趣的歡迎到我的小屋創作查看相關內容。想參考 MapleCraft 怎麼透過這套工具開發的,也可以到我的 github 頁面查看:連結)

總之為了方便開發,首先我把攻擊的 function 拆成以下的架構

所有技能原則上都套用這套架構的 function 安排,只有細部各 function 的實作不同而已(除了少數技能效果很特別的,只能特例處理)

架構建好之後就是一步一步把每個 function 完成,首先第一步的 check 就是拿來檢查玩家有沒有學這個技能啊、HP/MP 夠不夠啊、武器符不符合啊、要求在地板放的技能有沒有站在地板上啊,總之把該檢查的都照順序檢查一遍(為了確認順序我花了一堆時間在X服測試),該提示的文字丟一丟,都沒問題就可以進到施放的流程了

首先進到目標搜尋的流程,大部分的技能的效果都是向前方一段距離內的範圍,另外有一些則是玩家周圍都有效果。由於 Minecraft 跟楓谷的操作體驗不同,沒有能夠直接把楓谷的範圍換算成 Minecraft 中的範圍的方法,所以我就用大略的比例去找出我覺得最合適的範圍設定了
以玩家周圍為效果範圍的技能我就簡單的用 distance 來解決,不過目前沒有一個好方法可以使用動態的 distance (窮舉不算),所以目前我沒有針對這種搜尋方式作範圍設定(也就是說會隨等級增加範圍的技能目前不會套用範圍增加的效果,雖然目前做到二轉也還沒有這種技能,到時候遇到再考慮要不要窮舉吧)
而以向前攻擊的技能來說,我的作法是根據該技能(近戰技能使用的武器、增加攻擊距離的被動技能等等也有影響)的距離當作搜尋次數,依序向前搜尋半徑 2 格內的目標,接著將位於玩家背後的目標排除,最後根據技能攻擊敵人的數量上限(N),從被搜尋到的目標中標記出距離玩家最近的 N 名目標
這樣只要設定兩個記分板分數就能用一套搜尋 function 達成不同技能的搜尋效果,大幅降低開發維護成本。雖然為了處理一些細節效果(主要是多體技能的命中時間差),後來還衍伸出了紀錄怪物距離的版本,以及被標記的順序的版本,不過基礎的運作邏輯都相同
(模擬距離 4 的敵人搜尋範圍,首先將黑色圈圈內的目標全部標記,排除紅色圈圈外的目標,最後就會留下藍色範圍內的目標,再從中標記出距離玩家最近的 N 名目標)

接著來到技能數值與特效的環節,技能數值設定就是舉凡攻擊力、DEBUFF 數值、MP 消耗、攻擊速度等等一切與技能施放相關的公式計算,有興趣的可以參考這篇詳細的舊楓谷公式表:連結
公式計算的部分基本上就是數字遊戲而已,有確切的公式在都好解決,需要注意的主要是三個問題
  1. Minecraft 只有整數運算:除了 execute store 跟 data get 可以在取得數值(可能為小數)後乘以一個常數(可以是小數)以外,所有的數值運算只能透過記分板進行,而記分板只支援整數,也就是說所有運算都只能用整數進行。要想多精確到一位小數,所有數值就要多乘以 10 才能開始計算
  2. Minecraft 只有整數除法:不要以為所有小數都先乘以 10 就沒事了,要記得 Minecraft 的除法是整數除法,什麼 1 除 2 不會變成 0.5 而是會直接歸零,所以要進行除法之前也要確認你想精確到幾位小數,事先把分子 x10 個幾次
  3. 記分板的數值有效範圍是 -2^31 到 2^31 - 1:也就是說如果你計算的數字會小於 -2,147,483,648 或大於 2,147,483,647 數字就會出錯。當你天真的以為把所有小數都乘好幾倍都不會有精確度問題時,你發現公式裡一下來個平方啊,一下兩個小數相乘啊,分分鐘都在弄爆你的記分板。所以在精確度跟記分板上限之間要做個取捨,思考一下這個公式最極端的狀況會有什麼數字,在不超過上限的情況下去追求小數的精確度才是正確的做法
而技能特效原則上就是音效與視覺特效。音效的部分比較單純,通常單純找到技能的施放音效貼過來就可以(雖然有些是組合音效,各音效之間的時間差也是花了我一堆時間戴耳機去抓出來的。還有一些平常不會注意到的小細節像是開著無形之箭放招不會有箭矢的聲音之類的)。至於視覺特效的部分就有簡單有複雜了,考慮到篇幅安排,這部分就留待下一篇再作介紹。

再來就到了收割的環節,為了節省資源,這邊做了一個小紀錄檢查有沒有標記到任何敵人,沒有的話就不用做後面那堆事情了。這區塊的任務主要分成三項:計算傷害、命中特效與計時器
計算傷害又是數字遊戲,不必多做說明。命中特效如擊退、傷害數字、技能特效等等都是常見的概念(或是之前提過),這邊也不著墨於此。現在的關鍵在於如何實做計時器這件事情上,之所以要做計時器是因為楓之谷並不是放招的瞬間看起來就打到怪物(很少遊戲是沒有延遲的,哪怕只有一點點的前置動作),為了讓戰鬥看起來更流暢且真實,增加命中延遲是必須的
說到計時器,很多人最直接想到的就是一個記分板每 tick 減 1 然後歸零就是時間到。這個方法確實很實用,很多情況下我也會用到這個做法。然而當情況來到有 100 多個技能的楓之谷,而且一般情況下同時只會有少數技能需要計時,其他都是待命狀態時,這顯然是個過度連閃消耗資源的方法。

還記得 1.14 出現了新的省資源神器 schedule 嗎?沒錯是時候該把它拿出來了。不過要記得預設的 schedule 指令如果設定相同的 function,新的 schedule 會取代掉舊的,所以記得使用 1.15 新增的 append 選項
這邊要利用的是 queue 的概念去做變形。schedule 指令會回傳目標執行 tick 的 gametime,可以用 execute store 把這個數值記錄起來。以魔力爪為例,魔力爪有兩次不同延遲的命中特效,用上述的方法就可以把兩次命中特效執行的時間記錄在虛擬 nbt list 中。接下來我們要做的是,把這個執行時間烙印在我們的目標身上
(圖片來源:老爹講楓之谷)
然後當 schedule 的 function 執行時,先用 execute store 搭配 time query gametime 取得當前的遊戲刻,再讓所有有被烙印的目標去檢查自己下一次的命中時間是不是這個遊戲刻,如果是的話就執行命中特效,並把一個時間烙印消除
至於要怎麼找到下一次的命中時間就要自己設計烙印的方式了,我這邊採用的方法是每次有新的 schedule 時間 list 要加進來時,都用 merge 的方式讓他們照時間順序排好,這樣我每次只要抓最開頭那個的時間肯定就是下次命中的時間了。雖然這個做法會在遊戲刻跨越 2^31 時出一點問題,不過即使伺服器 24 小時開機也要快 7 年才會遇到一次問題,這邊我就當作這問題不存在吧
要記得對於目標怪物,每個技能的命中延遲都必須個別用一個 list 來記錄時間,畢竟每個技能schedule 安排的 function 都不相同,時間互相是不影響的(我也不知道我在說什麼反正你們理解就好)

使用這套計時器在正常情況下大幅減少了連閃的資源消耗,在 MapleCraft 這種大計劃中可說是不可或缺的存在。不過這套方法也不是萬能的,某些極端狀況下他可能比一般連閃還要吃資源。最簡單的例子就是,假設今天總共有 100 個技能,而「平均」每「tick」都有超過 100 個人在放技能,那就會發生每 tick 都被 schedule 超過 100 個 function 的狀況,這種狀況就會導致 schedule 法比起連閃倒數法還要吃資源的情形發生。不過這個狀況實在太極端了,所以就 MapleCraft 而言,使用 schedule 法肯定是比較好的選擇

最後稍微提一下怪物受傷音效與死亡音效的部分,這部分因為沒有一個好方法可以讓不同怪物發出不同的聲音,所以不得不採用窮舉的方式。不過楓谷的怪物種類超級多,音效也超級多,因此我是把每種受傷跟死亡音效個別設定一個編號,紀錄在怪物的分數上,每當需要發出對應的聲音時就透過 binary search 去播放對應的音效。雖然還是窮舉,但相對於寫滿整排 if 在一個 function 裡面,這已經是相當雅致的做法了



之前的魔力爪影片就已經很完整的展示了這次的主題,所以就不用再放影片做結尾了
然後因為連續打了三篇文章覺得有點懶了,所以這篇下半部的主題介紹完之後我可能就不會穩定更新這邊的文章,如果你對目錄中哪個內容特別有興趣再用留言跟我說,越多人想看我會越快更新(如果都沒人想看就...隨緣)

我是貓狗喵,我們下篇主題見
35
-
LV. 28
GP 1k
4 樓 貓狗喵 kevin22152
GP47 BP-
第四步:做出流暢的攻擊吧 (下)
在上一篇中我們完成了基本的攻擊流程架構,這篇就把缺漏的細節補齊吧

首先回到技能施放特效的部分,在上一篇中被略過的,關於視覺特效的內容
就如字面上所說,視覺特效就是所有放招以後會看到的畫面效果。例如魔力爪施放時玩家身邊的藍色圈圈、雙飛斬玩家前方的紅色揮動特效,以及這篇的一個重點:武器揮動特效
這三個例子恰好分別代表三種簡單到複雜的技能特效:
  • 魔力爪這種沒有方向性(通常是球狀概念)的特效,使用之前介紹過的名條動畫再適合不過了,因此製作上相當簡單快速(其實魔力爪的圈圈還是有方向性,但是因為很明顯是球狀特效,所以還是歸類在第一種)
  • 雙飛斬這種有方向性的動畫特效不能像名條一樣永遠面向玩家,因此必須透過模型製作。為了讓模型照著我們的預期播放動畫,就必須不斷更換模型道具。這部分的邏輯還是與名條動畫滿類似的,只不過從字體對應數字變成了模型對應編號(原則上就是 CustomModelData)

CustomModelData 有個特性是,假設今天設定了編號 1 跟編號 10 的模型,中間沒有指定的編號 2 到編號 9 的模型都會自動套用編號 1 所使用的模型。也就是說我們只要照順序把每個關鍵畫格的時間對應模型寫進 CustomModelData 的設定就可以了
{"predicate": {"custom_model_data": 400000},"model": "item/empty"},
{"predicate": {"custom_model_data": 400009},"model": "skill/400/1344/effect/1"},
{"predicate": {"custom_model_data": 400012},"model": "skill/400/1344/effect/2"},
{"predicate": {"custom_model_data": 400014},"model": "skill/400/1344/effect/3"},
{"predicate": {"custom_model_data": 400017},"model": "skill/400/1344/effect/4"},
{"predicate": {"custom_model_data": 400019},"model": "skill/400/1344/effect/5"}
上面的是雙飛斬的範例,動畫分別會在第 9/12/14/17/19 tick 切換畫格,我們只要讓顯示模型的實體有個分數從 400000 持續增加到實體消失為止,過程中每 tick 都把分數寫進頭上的模型道具 CustomModelData 就完成了,不必讓實體慢慢判斷每個 tick 分別要套用哪個模型
  • 最後也是最複雜的武器揮動特效,稍有研究的楓學家應該都知道,武器揮動的特效是會隨著武器的熟練度改變的。或許很多人都知道熟練度滿了就會變成潮到出水的橘色煞氣特效,但有不少人都沒注意到,熟練度點滿之前也是會有特效的差異的
◀ 不同熟練度的揮動特效範例
所有武器就算沒有學習精準技能也有 10% 的熟練度,而精準技能會以 5% 為最小單位增加熟練度,因此從 10% 到 60% 會有 11 種熟練度特效(透過四轉技能超過 60% 特效也不會改變)。其中已經知道 60% 的熟練度是橘色煞氣特效,剩下的 10 種就是各種色違版本了
但是要知道楓之谷武器這麼多種,每種武器又有許多不同的揮動特效,如果全部都把 11 種顏色通通貼上去,光是圖片就不知道幾百張了,判斷要套用哪個特效時也很冗雜,所以剛開始的時候我也只做了煞氣橘色版本的特效
不過中二如我日有所思夜有所夢,就是想要還原這個效果(法杖敲出來也是煞氣橘總有種廉價感)
這時候就要搬出我們的科學精神 SOP,來解決眼前的問題吧!
  1. 盤點庫存:想想看有什麼工具是跟我們要的效果相關的。跟顏色修改相關的東西?沒錯就是藥水模型與 CustomPotionColor!
  2. 整裝待發:用圖片編輯工具稍微分析一下(其實就是用滴管看啦)可以發現,這些特效都是用白色的底色跟半透明的顏色邊框疊上去的,透過滴管也可以知道他們各自的 RGB 值是多少(原則上就是藍色到紅色的數值增減),利用這個網站把 RGB 轉換成對應的 CustomPotionColor 的值(當然你想自己算也可以,WIKI 都有公式),這樣我們就準備好各個熟練度的顏色了
  3. 我們必須更深入一點:CustomPotionColor 會對整張圖片進行染色,所以直接把原本的圖片漂白丟進藥水模型是行不通的。要想用這樣的染色方式必須把底色跟邊框分隔開來才行,可是要怎麼把一張圖拆成兩個部分呢?聽說 PhotoShop 有能把白色以外的顏色擦掉變成半透明白色的功能,不過我沒有 PhotoShop,手邊的圖片編輯工具也沒有這種能耐。看來是時候從零開始,找出分割圖片顏色的方法了!

要快速寫出一個程式來修改圖片,我選擇用 python 的 PIL 庫來製作。程式碼的部分有點超出主題我就不介紹了,總之我們需要利用半透明顏色疊加的公式反推,來把邊框跟底色分割出來
剛開始的時候我以為兩個半透明顏色的疊加,是單純的兩個 RGB 各自乘以自己的透明度然後相加的結果,透過這套公式反推生成一組邊框與底色的組合。結果實際把成品塗顏色疊上去之後突然發現跟原圖有點不太一樣
沒辦法只好請示 GOOGLE 到底半透明顏色是怎麼混合的了

經過一番搜尋以後找到了這篇專門介紹半透明混色的 WIKI:連結
不過這個公式是用來混合顏色的,不是用來分割顏色的,所以我們要利用這套公式反推回去
也就是說,算數學的時間到了。(懷疑啊!)
以最基本的揮動特效而言,他是用 (0, 0, 255) 的藍色當作邊框的。對每個 pixel 而言,混合的顏色跟透明度是已知的,兩個混合的顏色則分別是 (255, 255, 255)
(0, 0, 255),給定這三個常數的情況下,我們要求出底色的白色跟邊框的藍色各別的透明度是多少。總之經過一連串方程式計算就可以把兩者的透明度求出來了(要注意白混藍跟藍混白是不一樣的結果喔),有了透明度就可以去還原兩張圖片原本的樣貌,由於邊框的部分是要拿來上色的,所以我們一樣存成白色讓 CustomPotionColor 的顏色不會受到干擾
(上面的範例特效分割成邊框與底色的結果)
最後終於來到實裝的部分,有了分割圖片我們就可以開心地在模型中加上「套用 tintindex 材質的邊框 Cube」與「不套用 tintindex 材質的底色 Cube」,然後每次要使用時只要把熟練度對應的 CustomPotionColor 數值寫進模型道具就完成了
關於 tintindex 與 CustomPotionColor 關係的說明可以看這篇:WIKI
關於揮動特效我還有太多東西可以講,也有一些是我沒辦法(應該說不想硬做)完美還原的部分
不過大部分都是非常細節的東西,要不是我有深入研究相關運作模式,一般玩家根本不會注意到吧。總之最有分享價值的東西都在這了,這部分的介紹也就到此為止吧

結果光是揮動特效就寫了這麼長一段,最後就簡略一點帶過吧

首先來補充一下上一篇提到的延遲命中的效果,雖然在上一篇沒有特別提到,不過要執行命中特效也是需要很多傷害計算時得到的數值(最簡單的想想傷害數字怎麼來、仇恨要給誰)
不過因為誰也不能保證那些數值在特效出現之前不會被其他攻擊覆蓋掉,所以勢必得要把它儲存起來,那儲存在哪裏比較適合其實也顯而易見,就是儲存技能命中時間的那個 list,所有需要的數值都跟著時間一起存在同個物件內,這樣取出時間時就可以同時取出需要的數值了
傷害數字是這個技能第幾次攻擊造成的也要記錄起來,才能知道數字要在多高的地方生成(楓谷常識:同一次攻擊越後面的傷害產生的數字會在越高的地方,除非你是新楓谷的火山特效)

接著是改良的命中延遲的部分,主要是楓之谷在進行多體攻擊時,每個目標的命中特效都會有一點時間差,一開始我也懶得去處理,不過所有怪物同時被打體驗上真的有點乾,如以下的電閃雷鳴
為了加上這個時間差,我們就必須 schedule 更多的 function 在不同時間。當然我們可以把所有可能的延遲都 schedule 過一遍,不過當遇到像穿透之箭這種遠距離攻擊時,最大的延遲相當高,太多的 schedule 就違背我們當初用這套做法而不用連閃的用意了。所以我選擇依序判定,有使用到的延遲才去安排 schedule,細部實作的方式很多種,這邊就不特別介紹了
而對應多了各式各樣的延遲,目標在烙印時間的機制上也要有所修改,簡單來說目標要知道哪些遊戲刻觸發的 schedule 是屬於自己的。那要做到這件事很直覺的做法就是在記錄觸發的遊戲刻之餘,額外紀錄延遲的 tick 數,只要目標發現延遲的 tick 數與自己相同,那就是屬於自己的 schedule 觸發時間

最後就附上包含各種揮動特效與多體傷害延遲的效果影片吧

那麼就如上一篇最後提到的,這篇是最後一次穩定更新接下來就要開始富堅了
如果你有想看的主題不要忘了留言告訴我,要不然預計會等到這系列文章總 GP 數超過 150,或是哪天心血來潮才會繼續更新這邊的文章(地圖製作計劃還是會繼續,只是文章不會更新)

我是貓狗喵,我們下次見
47
-
LV. 28
GP 1k
5 樓 貓狗喵 kevin22152
GP45 BP-
(我本來以為至少可以撐個三天的

第五步:讓子彈飛一會
前面兩篇介紹了攻擊的基本運作架構,但是舉凡弓箭、飛鏢、魔法飛彈等等有投射物的攻擊似乎又沒辦法用同一套邏輯去運作。為了流暢的投射物攻擊,這篇就來規劃另一套架構吧
(穿透箭這種不會追蹤的投射物還是套用原本那套攻擊架構喔,只是多了一個遊蕩的箭矢特效而已)

原本的攻擊架構還是算挺完整的,為了方便維護,盡量不要改太多東西,從原本的架構稍微做一點調整是比較好的做法。至於具體要增加哪些東西,大略可以整理成:召喚子彈、幫目標紀錄會打在自己身上的子彈、子彈的移動以及子彈命中的效果觸發

首先是召喚子彈的部分,子彈的樣式大略可以分成兩種:適合用名條圖片顯示的球狀子彈(如魔靈彈)與必須用模型顯示的有方向性的子彈(如箭矢。不過飛鏢與寶殼投擲我也歸類在模型,因為他們應該像飛盤一樣保持一個角度去飛)
為了增加泛用性,我希望一套指令就可以處理兩種子彈,所以所有子彈統一用盔甲架來顯示,既能顯示名條又能顯示模型。不要顯示名條的子彈就用倒退字符當名字,把背景海苔條去掉,不要模型的子彈就套用透明模型,兩者可以任選也能並存
(用模型顯示無形之箭,名條顯示魔靈彈)
另外要記得子彈不是放招的瞬間出現的,它同樣也會等待前置動作結束才出現,所以預設的子彈必須關掉名條顯示,並且套用透明模型,直到設定的延遲時間結束才把指定的模型套用上去並顯示名稱。這個動作恰好迴避了一個 tp 的顯示 BUG,那就是 tp 會用 6 tick 左右的動畫去平滑的移動目標,而非瞬間改變位置與轉向。其中轉向動畫如果在執行的過程中又受到其他 tp 影響(無論有無控制面向),會直接中斷不再執行,也就是會永遠保持錯誤的面向
(想當初這把小刀困惑了我超久)
以楓谷來說,大部分的子彈都會延遲 7 tick 左右才出現,也就是說我們在子彈尚未現形前就讓子彈轉向目標,當子彈現形開始移動後通常轉向動畫都已經結束了,只有偶爾才會發生轉向不完全的情況

另外雖然我覺得很容易聯想不過還是補充一下,讓模型套用使用中投射物模型的方法。只要讓投射物模型跟透明模型套用相同的道具,不同的 CustomModelData 即可,這樣只要在子彈現形時修改 CustomModelData 數值就能輕鬆顯示對應模型。名條的話以盔甲架而言,只有 CustomNameVisible 開啟的狀況下才會看到,所以一開始就可以把要顯示的圖片文字寫進名字,這樣現形時只要打開顯示開關就好(不過這個做法不支援動畫名條,但我覺得子彈在飛的時候動畫也不明顯所以就不特別處理)

召喚出子彈後,接著就要認定目標與子彈的主從關係
(拜託 FGO 粉不要打我)
對子彈而言,要知道自己的目標是誰。而對目標而言,要知道哪些子彈會命中自己,還有他們對應到哪個傷害事件。以往傷害事件的相關數據是直接紀錄在 schedule 時間的 list 當中,不過現在還不確定子彈何時會命中(距離不同命中時間也不同),所以必須暫存在子彈清單中,與對應的子彈綁在一起。老樣子要做對應判定的時候就給他們上編號吧,每隻怪物都有自己的編號,同一次攻擊的所有子彈也有相同的編號

於是我們就有了這樣的邏輯順序:攻擊技能施放 → 目標鎖定 → 子彈召喚 → 子彈記錄目標編號 → 目標計算傷害 → 目標綁定子彈編號與傷害事件儲存在 list 中

放招時的設定都準備完成,再來就是等著子彈命中了
雖然不是很喜歡這個做法,不過要想讓子彈移動免不了就是要用連閃 tp
儘管同樣是連閃 tp,還是可以盡可能減少連閃的負擔。以這邊的例子來說,要讓子彈往目標 tp 最直覺的做法是讓每個子彈透過編號搜尋目標,再給目標暫時的標記,指定面向標記的實體往前 tp
直接用指令來看就會變成這樣:
# (execute as @e[子彈] run ...)
scoreboard players operation 世界共用分數 = @s 目標編號
execute as @e[怪物] if score @s 編號 = 世界共用分數 run tag @s add 暫時標記
execute facing entity @e[tag=暫時標記] eyes run tp @s ^ ^ ^1
tag @e[tag=暫時標記] remove 暫時標記
可以看出實體搜尋的次數高達 子彈數 * 怪物數 * 3 次(包含開頭的 execute as 子彈),在實體數量大時可說是不少的消耗
那麼有什麼方法可以縮減實體搜尋的次數呢?這邊採用的方法是換位思考
不要從子彈的角度去思考,改從怪物的角度去判斷,稍微玩一下 as 跟 at 的小把戲。讓每隻怪物自己的位置,透過自己的編號搜尋目標是自己的子彈,讓它們面向自己往後移動
用指令來看差不多就長這樣:
# (execute as @e[怪物] at @s run ...)
scoreboard players operation 世界共用分數 = @s 目標編號
execute as @e[子彈] if score @s 目標編號 = 世界共用分數 facing entity @s run tp @s ^ ^ ^-1
實體搜尋次數一口氣縮減成原本的 1/3,大幅提升資源使用效率
而且使用這個做法時,要判斷是否命中目標只需判斷「自己」有沒有在指定的距離內即可,而不用判斷目標是否與自己在指定距離內(因為已經是以目標的座標為基準執行指令),再省一波實體搜尋次數

至於沒有找到目標時召喚的子彈,則是另外用一個連閃去默默往前 tp 直到飛行時間結束即可
即使怪物被瞄準後發生大量位移(例如綠水靈跳樓),子彈也會氣死牛頓直接蛇行追到天涯海角

最後當子彈命中目標,就是要執行命中特效的時候了
很可惜的,我們沒有任何辦法在子彈命中的時候直接執行對應技能的命中 function,所以免不了一一判定這個子彈是哪個技能放出來的來決定執行哪個 function
由於子彈類技能也是有二連箭或雙飛斬這種有多次不同延遲命中特效的存在,所以我們在子彈命中後同樣套用原先那套 schedule 的系統去執行命中特效,只是把原本的動作時間延遲移除(要注意 schedule 不能把 function 安排在當前 tick 執行,至少要延遲 1 tick,所以這邊子彈命中後第一個傷害特效固定設定成 1 tick 後觸發)
而為了避免重複觸發命中特效,同一次攻擊即使有多個子彈,也只能有一個子彈觸發命中判定。這個單純的給其中一個子彈指定的 tag 就行,我通常會給最前面的子彈這個 tag (二連箭就沒有前後之分,隨便給哪個都可以),這樣正常狀況下都會在第一發子彈命中敵人時就觸發命中特效(除非目標突然瞬間移動到子彈背後)

其他還有一些細節的設定就簡單帶過不花太多篇幅介紹,例如弓跟拳套要先判斷目標距離,再決定要用近距離普通攻擊還是原本的技能(舊楓谷常識:弓、弩、拳套在近距離狀態下不能射箭 or 飛鏢)。還有被子彈瞄準但尚未命中的目標也要記錄尚未執行特效的分數(第三篇架構圖的倒數第二個步驟),避免子彈命中目標之前,目標被其他攻擊打死,以為沒有還沒命中的特效就直接歸西了

最後就用展示了最多子彈特性的一部影片來做結尾吧

沒想到上次 150 這麼快就破了,這次就稍微把標準拉高一點好了
下一篇就等總 GP 數達到 200 再開吧,想快點看到更多內容可以每篇覺得有趣或實用的主題都幫我點一次 GP (每篇都是花完整打一篇文章的心力去寫的,只是為了避免洗板才統一回覆在一篇裡面)
我是貓狗喵,我們下次見
45
-
LV. 28
GP 1k
6 樓 貓狗喵 kevin22152
GP27 BP-
第六步:殭屍菇菇動起來
經過前面一番努力,攻擊技能的要素基本都已經完成了。然而還有一個在打怪體驗上至關重要的要素,那就是被打的怪物本身。只有 Minecraft 原版怪物可以打是不足以稱為還原楓之谷的,我要看到血流成河怪物在跳!(嫩寶:Am I a joke to you?)

開始動工前要先對楓谷怪物的行動模式有所了解,基本上來說怪物的行動分成五種型態:靜止移動受傷死亡,而會跳的怪物還多一個跳躍的行動。除了跳躍與受傷以外,其他行動大部分都是循環動畫,例如靜止時原地深蹲。
為了還原這種動畫效果,就必須使用動畫模型的技術。這技術其實滿多國外地圖都有大量使用,不過台灣地圖使用的頻率相當的低,或許還有許多人不知道原理(甚至是這東西的存在),所以這邊就介紹一下動畫模型的邏輯概念吧。
(近期版上應用到動畫模型的應該只有殺戮荒漠的敵人與島國勇者的NPC了吧)

要理解動畫模型之前要先理解動態材質,詳細結構可以先看 WIKI 介紹:連結
簡單來說就像 Minecraft 中的火一樣,我們可以設定一連串的圖片讓他每隔幾 tick 自動切換成下一張圖,看起來就像動畫一樣(動畫的原理本來就是這樣)
(營火的火焰材質與照順序由上往下播形成的 GIF)
那麼動畫模型跟動態材質有什麼關聯呢?如果做過模型的人應該都知道,模型檔案本身就只是單純的骨架,跟動畫是八竿子打不著關係的。這時候動態材質就成了模型跟動畫的橋接器,模型可以指定骨架的每個面要顯示什麼材質,而這個材質也可以是動態材質。
先從 2D 的角度來理解,假設在 2D 圖上有一個黑點每幀向右移動一格,他的初始座標是 (0, 0),那麼對於 (0, 0) 這個點而言,他在第一幀的顏色是黑色,之後都是透明的。然而對於 (1, 0) 這個點而言,卻是第二幀是黑色,其他是透明的。再假設這個點在第三幀結束後會回到最左邊,變成一個循環動畫,此時每個座標點在每幀的顏色是如何呢?
對於 (0, 0) 而言是 黑 → 透明 → 透明 → (循環)
對於 (1, 0) 而言是 透明 → 黑 → 透明 → (循環)
對於 (2, 0) 而言是 透明 → 透明 → 黑 → (循環)

有沒有覺得很眼熟呢?沒錯,每一個座標點都可以獨立看成一張動態材質
現在把視角拉回到 3D 的世界,把上面那張 GIF 中 2D 的黑點換成 3D 的黑色方塊,應該就可以理解動畫模型的原理了吧
簡單來說,動畫模型就是將動畫拆成多個畫格,各個畫格都有自己的一套 3D 骨架,但是各自套用不同的動態材質並且塞在同一個模型檔案裡面
(3D 化的深蹲殭屍菇菇)

講解完技術的邏輯之後,接下來就要提一些使用上的注意事項了
首先從上面的範例應該可以觀察出來,對動畫模型中每個動態材質而言,各自都只有 1 幀是有材質圖片的,其餘都是透明的狀態,因次當畫格數量多的時候,材質圖片的量會呈現平方的比例成長(如果有 N 個畫格,就必須有 N 個動態材質 * 每張動態材質切分成 N 份),雖然透明的格子對圖片檔案容量影響沒那麼大,但還是建議用動態材質的 mcmeta 檔直接設定材質在透明畫格與有顏色的畫格個別的停留時間(詳情請仔細閱讀上面的動態材質 WIKI),而圖片本身就單純地用「有材質的畫格」與「透明的畫格」兩格儲存就好。如下方例子中 index 0 的位置是有顏色的材質,1 是透明材質,透過 mcmeta 讓材質在第 5~8 tick 時顯示有顏色的材質,其餘保持透明
{
  "animation": {
    "frames": [
      { "index": 1, "time": 4 },
      { "index": 0, "time": 4 },
      { "index": 1, "time": 8 }
    ]
  }
}
再來是關於模型效能節省的部分。當模型中的方塊數量太多的時候,會對玩家的 FPS 造成直接的影響,這也是我不喜歡鋸齒法精密模型的原因。(雖然現在好像大家電腦都很好根本沒差)
▼ Cubik Studio 中常用的模型匯入功能就是用鋸齒法疊出精密模型,看看 64x 那精美的方塊數量
然而使用動畫模型意味著要把每個畫格的骨架都塞進同一個模型中,也就是說方塊數量會根據動畫畫格數量翻倍增長。為了減少這個問題造成的影響,首先最直覺的就是不要使用太精密的鋸齒模型。神韻點到為止,方塊不必太多,細節透過圖片去修飾,2D 高清肯定比 3D 高清要省的多(畢竟一個是平方一個是三次方)
另外一個減少方塊數量的方法則是使用者付費的概念。動畫模型很多情況下只有一部分的元件會有動畫效果,其他則是固定不變的元件,例如營火的火是動態的,但木頭基座是固定的。
在這種情況下就不用每個畫格的骨架都將固定的元件包含進去,而是將固定的元件獨立出來套用非動態材質,其他再比照動畫模型的格式辦理
(雖然殭屍菇菇整隻都很軟Q,所以完全沒用到這個方法就是了)

講解完動畫模型,終於可以回來讓怪物動起來了
在怪物的五種行動模式中,靜止移動是優先度最低的,也就是說除非沒有其他行動,怪物才會採取這兩種行動模式。優先度比靜止與移動略高的是跳躍,只要怪物在空中(飛行怪物除外)就會套用跳躍的行動模式。而最高的則是受傷死亡,不管怪物原本是什麼行動模式,只要發生這兩種事件就會強制切換成這兩種行動模式。
釐清優先順序後就可以來設計指令架構了,每個低位階的行動在判定前都要檢查是否有高位階的行動正在進行。另外每種行動模式個別使用一套動畫模型(或一般的模型)包在同一個道具裡面,就能透過 CustomModelData 做到方便的行動模式切換
{"predicate": {"custom_model_data": 10000},"model": "mob/zombie_mushroom/move"},
{"predicate": {"custom_model_data": 10001},"model": "mob/zombie_mushroom/stand"},
{"predicate": {"custom_model_data": 10002},"model": "mob/zombie_mushroom/hit"},
{"predicate": {"custom_model_data": 10003},"model": "mob/zombie_mushroom/jump"},
▲ 將殭屍菇菇基本模型編號(10000)寫進記分板,要切換模式只要把分數拿出來加上特定值再寫進模型道具的 CustomModelData 即可

首先從最低位階的靜止移動開始看起,在確認完沒有高位階的行動後,要判斷怪物是在移動還是靜止狀態最簡單的就是偵測 Motion,只要 X 跟 Z 的數值大約等於 0 (浮點數有誤差而且沒辦法直接在 Minecraft 比較大小,所以取個 100 倍的整數判斷是否為 0 就差不多了),就代表怪物正在靜止狀態,反之則是移動狀態。
跳躍就簡單得多,只要 nbt 中 OnGround 的值是 0b 就代表在空中,直接套用跳躍模式
受傷死亡只有在受到攻擊事件時才有可能發生,不用隨時檢測,不過持續時間還是要透過計時器去控制。其中死亡的動畫因為我實在有點懶得做,所以直接讓他保持受傷的模型 kill 掉,結果看起來效果還不錯(畢竟在 Minecraft 看到這種死亡特效完全沒有違和感)就保留這個設定了,或許哪天有人幫忙做模型我再考慮把死亡動畫加回去
就用這個影片來看看這幾種行動模式組合起來的效果吧

模型有了,套用模型的機制有了,但還有一個功能是上面沒有提到的
沒錯就是變色系統

從大家最熟悉的冰凍、中毒,到比較少見的麻痺、火/冰屬性化都有變色的效果
那麼要怎麼讓怪物模型套用變色效果呢?總不可能每種顏色都特別做一套模型去套用吧
這時候相信許多人跟我腦中都閃過一樣的畫面
沒錯,辣個功能回來啦!
(謎:誰跟你閃過這個畫面!)
只要把怪物模型全部用藥水模型來做,就可以套用 CustomPotionColor 的染色效果,不管你是什麼狀態什麼行動模式,顏色數值套下去就能搞定
在指令實作上也沒有太過複雜的內容,只要各種顏色個別套用計時器,時間到了看要套用其他還沒結束的顏色還是變回正常顏色就可以
不過關於這些顏色具體是什麼顏色,我還查不出個所以然,甚至他們的染色方式跟 CustomPotionColor 是不是一樣的方式都不確定,所以就只能自己抓一個感覺差不多的顏色套上去了,反正最後呈現出來的效果看起來是挺不錯的

想看最後呈現的效果可以去四樓的電閃雷鳴影片,這邊就不再貼一次了



文章的進度終於快要趕上製作的進度了,感覺 GP 數量也有逐漸飽和的趨勢
下一篇的目標就訂在 260 GP 吧!究竟是文章會先沉下去還是下一篇會先把文章推上來呢?
我是貓狗喵,我們下次見
27
-
LV. 28
GP 1k
7 樓 貓狗喵 kevin22152
GP35 BP-
第七步:忽聞技能聲,BUFF有多少
就如同 Minecraft 的藥水效果,楓谷的 BUFF 也會以 icon 的形式顯示在右上角。然而 Minecraft 中的藥水效果比起楓谷的 BUFF 數量實在少太多了,而且大多會直接影響玩家的操作體驗(中毒凋零暈眩等等根本不可能拿來顯示),所以用 Minecraft 藥水效果來顯示楓谷的 BUFF 是不切實際的。那麼是時候自己弄一套 BUFF 顯示系統出來了!

如果有看過我浮光系列地圖的人應該有印象,當你選擇近戰職業時,在道具欄左邊有一排用來顯示技能 CD 是否結束的圖示提示。不過這套系統並不能直接搬到這個楓谷計畫來用,主要原因是當初的做法是事先指定好六個 icon 個別要顯示什麼東西,當沒有要顯示的東西時就顯示透明圖案。因為浮光的近戰技能最多就只有 6 個,所以可以用這種預先設定 6 個顯示欄位的作法。
然而到了楓谷事情就不一樣了,天知道楓谷同時能有多少 BUFF 存在?而且哪個欄位要顯示哪個 BUFF?要清掉某個 BUFF 時要刪掉哪個欄位的顯示?輕鬆修改玩家道具資料的神器在 1.15 殞落之後,要顯示的 BUFF 又要存在哪?
這時候就再度請出我們科學精神的 SOP 來一步一步解決遇到的問題吧!

首先要解決 BUFF 數量未知的問題,就要有一個基本上長度不限的儲存結構,這個東西也非常容易聯想,那就是 nbt 中的 list 型態物件。然而緊接著就會遇到下一個問題,那就是怎麼把顯示跟儲存的資料連結起來。以浮光的邏輯來說,我們就是固定顯示前 6 個技能,但楓谷的 BUFF 上限非常多(你可能會說這麼多畫面也顯示不完,但是如果有人螢幕很大是可以顯示非常多圖示的,所以還是盡量把這個彈性保留起來),所以比較理想的做法是找出一套不管 list 多長都能全部顯示的作法。
其實這套作法早就已經在前幾篇的文章跟留言中劇透光了,要顯示整個 list 資料的方式主要有三種:
  1. 第一種也是最簡單的一種,那就是當整個 list 都是純字串(非 json 格式字串)所組成的時候,只要用 json 的 nbt 物件把整個 list 當 json 字串顯示即可
    之所以能這麼做的原因,是因為純字串格式的 list 跟 json 格式字串一模一樣,所以用 json 的 nbt 物件把 list 抓出來後,有辦法透過 interpret 進行 json 解析,變成解析後的字串。這套做法雖然簡單,缺點也十分明顯,那就是內容物只能是純字串,連 json 格式字串都不行(意味著不能個別物件進行顏色字體等等的設定),而且正常顯示的字串每個文字之間會有 1 pixel 的間隔,在這套做法中這個間隔也沒辦法消除(除非你在字體檔設定某個字是倒退字符,然後 list 的每個物件之間都塞一個倒退字符字串)
  2. 第二種同樣是用 json 的 nbt 物件來顯示 list,不過比起第一種做法要強大的多。list 的內容不再限定是純字串,可以是 json 字串,甚至是任何型態的物件
    這套做法的原理是 json 的 nbt 物件如果使用 a_list[] 這種格式時,會將該 list 當中每個物件拆開來當成單一的 nbt 物件去做顯示,然後再把結果用逗號與空格串接起來。這套做法只要設計一套字體,把逗號設定成倒退字符(已經幫大家測試過了,倒退 5 pixel 剛剛好),用來把逗號跟空格移除就可以。本篇中使用的也是這個作法
  3. 第三種是由 在留言中提到的做法,這種作法的效果有點像前兩種做法的組合。首先同樣有一個 list 包含要顯示的內容,利用 function 遞迴效果依序將 list 中要顯示的內容寫進告示牌文字的 extra 中
    # main:loop
    data modify block <告示牌座標> Text1 set value '{"nbt":"Text1","block":"<告示牌座標>","interpret":true,"extra":[{"nbt":"test[0].show","storage":"foo:test","interpret":true}]}'
    data remove storage foo:test test[0]
    execute if data storage foo:test test[] run function main:loop
    這種作法可以在保留第二種作法對 list 格式的彈性之下,達成第一種作法的效果,不過在指令上相對繁瑣。雖然沒有經過大量測試,但我認為這樣是比第二種做法消耗系統資源的。儘管如此,這套系統還是有一個獨有的優勢,那就是不需要資源包就能達到串接顯示 list 資料的效果

有了顯示的工具之後,接下來要處理如何清掉消失的 BUFF 的問題。要從 list 中清掉某些物件,只要把物件設成像是 {shark:"A"} 這種格式,然後 data remove the_list[{shark:"A"}] 就可以。然而碰到楓谷的 BUFF 覆蓋機制時,這個做法也變得不太實際,原因是每當你使用一個 BUFF 時,你就要列出所有會被覆蓋的 BUFF 去清除,考慮到楓谷的 BUFF 這麼多種,聽起來肯定不是一個好做法
要解決這個問題要先理解楓谷的 BUFF 覆蓋機制,首先每個 BUFF 可能會有一種或是多種效果(龍之魂的持續扣血效果也算一種),當新的 BUFF 與現有的 BUFF 有重複的效果時,新的 BUFF 會取代舊的 BUFF 的該效果(包含數值與持續時間),而當某個 BUFF 不存在任何生效的效果時,他就會從 BUFF 列表中消失。舉例來說:疾風之步(移動速度)在放了速度激發(移動速度&跳躍力)後會消失,而激勵(攻擊力&防禦力)在喝了攻擊藥水(攻擊力)並開自身強化(防禦力)後也會消失
透過這個特性我們就可以列出 BUFF 的關係表,若兩個 BUFF 控制的效果完全相同(例如攻擊藥水加量章魚燒),就給他們相同的編號,這樣在 data remove 時就可以不用每種 BUFF 都搜索一遍,而是只要把同一類的 BUFF 清除就可以,大幅降低開發成本與資源消耗
物攻 魔攻 防禦 魔防 命中 迴避 移動速度 跳躍
自身強化
魔力之盾
疾風之步
精神強化
鬥氣爆發
命中藥水類
迴避藥水類
激勵 激勵
禦魔陣 禦魔陣
集中術 集中術
天使祝福 天使祝福 天使祝福 天使祝福
速度激發 速度激發
黑暗守護 黑暗守護 黑暗守護 黑暗守護 黑暗守護
龍之魂
念力集中

聽到這邊你以為我就要用 data remove 那套方式去做 BUFF 的顯示嗎?
別忘了上面還有提到最後一個問題,那就是 1.15 之後玩家道具修改神器殞落。上面清除 BUFF 顯示的做法都是建立在有一個 list 可以用來儲存要顯示的 BUFF 的基礎上,然而事實上要儲存這麼一個 list 的方式,除了直接存在玩家身上的道具資料外,幾乎沒有什麼更好的方法了。但在 1.15 之後,要達成這個效果必須要繞好大的彎,所以非必要情況下我是不想使用這個方法的 (1.17 之後這個方法稍有優化,但是還是不比 1.14 的強大)
說了那麼多,要怎麼找到替代的方案呢?這就是考驗對各種工具活用程度的時候了
要是 list 型態的資料,可以在裡面自由儲存要顯示的字串,還能輕鬆清除其中的內容,綁在玩家身上又不用動到玩家身上的道具,有這麼方便的東西嗎?
......
......
......
還真的有!就是 1.16 新增的 attribute 指令

說起這個指令,可能很多人跟我一樣覺得是很雞肋的存在,沒特別有用,但偶爾還是有方便的地方。這麼一個指令,要怎麼跟上面的功能掛勾呢?秘密就藏在 Modifiers 裡。每個 Attribute 中都可以存在一個 Modifiers 的資料,在過去版本中只有藥水效果能夠增加這個項目(例如生命值提升、加速、幸運等等)。他的結構跟功能就跟武器裝備的 AttributeModifiers 很像,差別是這個是直接存在玩家身上的 Attributes 裡面的,而且透過這個修改最大血量也不會造成假死狀態(詳見二樓介紹)
而透過 attribute 這個指令也可以對 Modifiers 進行增減,雖然效果的值只能是指定的常數而不能是記分板或其他 nbt 資料這點令人詬病(也是造成這個指令雞肋的主因),但對這邊的需求來說堪用了

attribute 指令的格式如下(來源自 WIKI):
attribute <target> <attribute> modifier add <uuid> <name> <value> (add|multiply|multiply_base)

我們要利用的是 <name> 來做文字圖示顯示,並且利用 <uuid> 來做效果分類,<value> 對我們來說不重要,只要設成 0 就好。所有 modifier 會照順序排好,新的排在最後面,也就是說我們只要利用上面第二種顯示 list 內容的方式,抓 Attributes[{Name:"我們拿來儲存的屬性"}].Modifiers[].Name 這個資料就可以由左至右列出所有 BUFF,搭配之前提到的實報實銷法(詳見二樓介紹)就可以跟血量一起顯示在 actionbar 且不影響排版。雖然沒辦法像楓谷由右至左顯示在畫面右上角,不過顯示在道具欄右邊效果已經很不錯了
另外使用這個做法要注意不能給用來儲存 Modifiers 的屬性相關的藥水效果,要不然抓出來的 Name 會直接大爆炸,像這邊我用的是 generic.luck 這個屬性,所以就要注意不能給玩家幸運或霉運的狀態

現階段有一個很可惜的點是沒有一個好的方法(暴力法不算)可以顯示 BUFF 剩餘時間或剩餘時間的比例
要是之後 attribute 指令新增了可以對 modifier 的值進行修改的功能,這個效果就可以用同樣的方法,除了 Name 以外另外抓 Amount 這個資料來達成,不過目前就只能先維持這個版本的顯示效果了
(其實一開始的楓谷 BUFF 顯示也是沒有剩餘時間相關的顯示,也是後來版本才加上去的,所以某方面來說這樣也是好好地還原了楓谷的效果 #???)

雖然前面也有很多影片有顯示 BUFF 的效果,不過還是用這個專門展示 BUFF 效果的影片做結尾吧

這次的目標花了比以前多好久的時間才達成(我還在猜會不會直接沉了),看來 GP 數確實達到飽和了
下一篇的目標就先訂在 300 GP 吧(玩麥塊這麼久還沒有任何一篇文是 300 以上的)
我是貓狗喵,我們下次見
35
-
LV. 28
GP 1k
8 樓 貓狗喵 kevin22152
GP30 BP-
第八步:然後他就死掉了
稍微把時間回溯到第二步的時候,那時我們做出了血量的顯示,但是血量條可不是裝飾,大家都知道當 HP 歸零的時候會......
(圖片來源:楓葉物語)
(順便推一下大家都知道官網斷更很久了,不過現在有一位氫氧化氫大大接著在做後續內容翻譯,想填完這個有生之年系列的可以去這邊,底下留言有舊版官網的時光機連結)

回到正題,可能有些人會覺得死掉就把玩家 kill 掉什麼的就好,但是 kill 掉意味著玩家只能盯著傷眼的紅畫面(還常常超自然震動)或是復活回重生點,這樣楓谷招牌的墓碑跟幽靈就派不上用場了。那麼有什麼方式可以讓玩家無法跟世界互動卻又可以看到自己的墓碑呢?其實這東西也滿常見的,那就是觀察者模式。早在 1.9 就有人用觀察者來做假的死亡狀態了(例如 The Unseen Forces),只要有明顯的提示讓玩家知道自己死了,這東西基本上沒什麼缺點(除了 PVP 地圖不適合讓玩家到處飛觀察敵情以外),搭配假血量使用更是輕鬆方便。

為了達成這麼一個死亡畫面的效果,我們需要準備幾樣東西
  1. 墓碑
  2. 有玩家頭顱的幽靈
  3. 復活選項的視窗
  4. 在玩家復活時清除幽靈的機制
首先墓碑基本上單純用模型就能搞定。要做出一個 3D 的墓碑,當然可以自己慢慢刻出一個模型出來,但這邊我要用的是比較簡單的作法。大家都知道 Minecraft 原版的道具都是 2D 的圖片,但是拿在手上的時候卻是有一點厚度而不是跟紙一樣薄。有研究過模型的人應該都知道那個功能可以繼承 item/generated 這個內建模型來達成。
所以我們要做的就是找到墓碑的正面圖
接著把邊框的黑線去掉(要不然墓碑的側面就會是整片黑的)
接著新增一個模型繼承 item/generated 並套用這張圖片,再調整一下模型的厚度就完成了
如何,比起自己刻模型快多了吧?

另外楓谷的墓碑是邊旋轉邊掉落的,但是我試了幾種旋轉的方式效果都不太好,畢竟墓碑本身掉落的過程就不是很顯眼,所以就放棄了墓碑旋轉的功能,單純讓他掉落而已。而掉落這件事看似單純,其實也藏了不少學問。首先為了避免墓碑卡在頭上的方塊下不來,墓碑生成的高度不能太高。玩家的高度是 1.8 格,所以墓碑生的的高度必須在玩家頭頂 1.8 格以內。而為了配合墓碑音效,使用原版的實體掉落速度很明顯掉得太快,使用緩降的話又太慢,最後幾經測試我採用了緩降搭配預設向下的 Motion 的作法,利用慣性提升緩降狀態的掉落速度,讓墓碑落地的時機大略與音效同步

接著是幽靈的部分,為了讓幽靈顯示玩家的頭,頭盔的部分必須留給玩家頭顱,這也意味著幽靈不能在頭上套自訂模型,所以要找一個可以帶玩家頭顱又符合幽靈形象的生物。看過我浮光地圖的人應該也知道我會用什麼了,那就是惱鬼。只要把惱鬼全身塗白翅膀擦掉,就是個完美的幽靈形象,最大的缺點就是小了點。而為了讓惱鬼更貼近楓谷幽靈的形象,我還把他的兩隻手拔了裝自訂模型的義肢上去(怎麼聽起來有點獵奇)

第三個是復活選項的視窗,畢竟玩家被切成觀察者模式後什麼都不能做,總要給玩家一個復活的方式吧。通常這種把觀察者模式當死亡狀態的地圖都是採用倒數復活的方式,不過楓谷死亡時都有那麼大一個復活視窗擺在那,不用也說不過去
那麼要把復活視窗搬進來,還能讓玩家點選復活按鈕的方法,原則上也只有 tellraw 了,也就是說我們要再次召喚 1.16 最強外掛 - 自訂字體
不過這邊暫且打住,關於怎麼在 tellraw 做出漂亮的 UI 視窗的部分我會在下一篇詳細解釋,這邊只要先知道我是用 tellraw 來顯示復活視窗的就好

最後一個是玩家復活時清除幽靈的機制,你可能會想說啊不就復活時刪除自己的幽靈有什麼好說的,但實際上還要考慮到區塊讀取以及重登的問題
首先來說說重登的問題,在楓谷裡面如果在死亡狀態下重登會復活到最近的村莊,也就是說玩家在重登的時候要檢查是否為死亡狀態,如果是則執行復活與清除幽靈的效果。要抓到重登的玩家可以用 minecraft.custom:minecraft.leave_game 這個記分板判據簡單的達成,只要玩家重登分數就會+1
不過這種作法只有在玩家重登之後才會清除幽靈,也就是說如果玩家在死亡狀態斷線,其他玩家依然會看到幽靈卡在那邊,直到那個玩家重登為止。所以必須額外反向讓幽靈定時偵測自己的主人是否還在遊戲中,來判斷是否要清除自己。從屬關係判定的方式在前幾篇文章就有很多說明,這邊就不多做贅述

接著也就是本篇的重點,也就是區塊讀取機制。在 Minecraft 中,沒有被讀取的區塊內的實體是抓不到的,理所當然也沒辦法被 kill 掉。因此要確保幽靈所在的區塊是保持讀取的,才能避免玩家離開幽靈太遠復活導致幽靈沒被清掉的問題
要保持幽靈所在的區塊被讀取,最簡單的方法就是用 forceload 這個指令強制保持讀取幽靈所在的區塊。不過如果不清掉強制讀取區塊的話,每次玩家死掉要讀取的區塊就會變多,就跟玩家視野距離開太大很容易 lag 的原理一樣,這樣伺服器的負擔就會越來越重(伺服器設定之所以有限制視野距離的選項,八成也是因為這個原因)
至於要清除強制讀取的區塊好像也很容易,只要同樣的指令把 forceload add 換成 forceload remove 就搞定了。然而如果有兩個玩家在同一個區塊死掉的時候會發生什麼事呢?沒錯,只要有一個玩家復活了,那個區塊就會被取消強制讀取,儘管另一個玩家的幽靈還在那邊。所以我們要做的就是:當所在區塊內不存在其他幽靈時,才執行取消強制讀取區塊的動作
為了達成這樣的效果我想了好幾種方案:
  1. 把幽靈所在的 xz 座標換算成 chunk 編號,例如 x 軸在 0 ~ 15.999... 就是 x 軸編號為 1 的區塊, z 軸在 32 ~ 47.999... 就是 z 軸編號為 3 的區塊之類的。若有存在一個幽靈兩個軸的區塊編號都跟自己相同,就不執行區塊讀取清除的功能。不過這個做法一來計算編號比較麻煩,二來在區塊邊界有可能導致算出來的區塊跟實際讀取的區塊不同的問題(浮點數誤差)
  2. 計算出所在區塊的起始點,位移到該點後透過 dx, dy, dz 抓一個區塊的範圍,看有沒有幽靈在裡面。這個做法跟上個做法的問題基本差不多,而且 dx, dy, dz 只要碰撞箱有重疊就會抓到該實體,比起上個方法更容易誤判
  3. 就跟課本一樣,最好的方法都是留到最後才介紹。要介紹這個方法之前,首先要對 forceload 這個指令的運作模式有更深入的了解。在使用 forceload add 的時候,指令會把整個遊戲卡住,直到讀取完指定的區塊才繼續進行。因此使用完 forceload add 可以確保接下來的指令肯定能讀到區塊內的實體。若嘗試讀取一個已經被 forceload 的區塊,則指令會執行失敗,不會有任何動作
然而 forceload remove 就不是這麼一回事了,他並不會在指令執行後把遊戲卡住,而是過一段時間才把區塊卸載掉(但是強制加載區塊的清單在指令執行的瞬間就會清掉該區塊)。具體這樣設計的原因我也不清楚,不過我測試的結果會有相當穩定的 2 tick 延遲。也就是說,我們可以透過這個延遲,在同個 tick 內卸載又加載區塊。先讓要清除的幽靈卸載所在的區塊,同 tick 馬上又讓所有幽靈加載自己所在的區塊,就能輕鬆達成我們想要的效果

最後就用死亡效果的影片來做結尾吧


隔了快一個月終於達成 300 GP 了,算是人生的一個小成就(?
這次的文章內容相比之前幾篇單薄許多,本來在想要不要合併到 UI 設計一起講,不過這樣好像又會太多,所以還是獨立一篇出來了。接下來雜項的部分我可能會先延後甚至刪除,畢竟內容很雜也不太好寫。而等 UI 的部分講完大致上就追上目前的製作進度了,所以接下來也不會有 GP 數的門檻來解鎖新的內容(至少要等我做完吧)
目前在努力超綠的內容製作,不過也要等 Terry 1/16 期末考完才能開始建築的部分,希望寒假有辦法推出一個可以玩的版本。下一篇 UI 的部分等我把我的進度弄得差不多之後就會補上,有興趣的人就再多多關注啦!

我是貓狗喵,我們下次見
30
-
LV. 28
GP 1k
9 樓 貓狗喵 kevin22152
GP36 BP-
第九步:在原版麥塊尋求 UI 是否搞錯了什麼
作為玩家與遊戲內容互動的橋樑,舉凡所有能讓玩家與遊戲互動的存在,從道具欄,對話框再到血條都可以算 UI 的一部分。沒有 UI 的話,既使有龐大的遊戲內容也無法構成可以玩的遊戲。前面幾篇對於舊楓之谷的戰鬥我們已經做了一套相當完整的系統,那麼是時候讓人可以「玩」了

撇除社交相關功能,在楓谷中有四大 UI 視窗:能力值欄技能欄道具欄裝備欄

其中在 Minecraft 中有與道具欄功能非常相似的包包功能,對玩 Minecraft 的玩家來說,使用包包看道具應該也比另外弄出一個視窗來的直覺,所以道具欄就用 Minecraft 原有的包包來代替。因此本篇的主題就是剩下的三大 UI 視窗了!

就像上一篇死亡復活視窗當中提到的,能讓玩家細部互動的視窗功能基本上只有 tellraw 能做到了。有些人可能會說為什麼不用書本,麻煩你用書本做能力值視窗然後點個 100 點再跟我討論主要是因為書本的 run_command 功能每點一次就會自動關閉書本,對於需要頻繁點擊的楓谷視窗來說操作體驗會非常糟糕

決定好工具之後,在正式動工之前我們要先知道 tellraw 與自訂字體的幾個特性
(不然就會跟我一開始做的時候一樣踩一堆雷)
  1. 自訂字體的圖片長寬都不能超過 256 (之前動畫名條有講過但還是再講一次)
  2. 每個字元的後面會有 1px 的留空,不過倒退字符中的指定寬度空格已經把那 1px 包含進去了
  3. 當文字圖片的高度超過一行的時候,上面的內容會蓋過下面的內容(包含背景黑框)
  4. 無論字體的圖片設定多高、看起來是 0.5 行還是 5 行,他的點擊事件的可觸發範圍就是自己所在的那行,完完整整的一行,不會多也不會少
  5. 對話框每行的行高是 9px,設定 height9ascent8 剛好會上下貼齊。也就是說在設計視窗的時候必須把握 9 的倍數原則,例如楓谷的視窗大約縮小一半會是 Minecraft 中比較適當的大小,我就把視窗每 18px 切成 tellraw 的一行來做顯示,同樣 height 設為 9 然後 ascent 設為 8,這樣就能把好幾行串接起來變成一個大視窗了。不過視窗上的按鈕並不會每個都剛好保持在每一行的正中間,因此適時的裁切或拉伸原始視窗來配合 18px 的倍數就成了必要的流程
  6. 使用倒退字元後有重疊的文字時,後面的字會顯示在前面的字上面。然而 clickEvent 與 hoverEvent 正好相反,前面的字的事件會蓋過後面的字的事件,即使前面的字沒有事件,也無法觸發後面的字的事件。也就是說,要想讓按鈕顯示在背景視窗上面,必須先在「按鈕要顯示的位置」設定帶有 clickEvent 的文字,然後倒退回起始點顯示背景視窗,再到「按鈕要顯示的位置」顯示按鈕的圖片。這到底什麼設計喔齁齁齁齁齁
  7. 使用 json 格式中的 list [...] 或是 extra 時,後面的內容會繼承前面內容的格式,包含 clickEvent 與 hoverEvent。這個性質運用的好可以很方便,運用不好則會瘋狂踩雷

掌握這些技巧以後,剩下的就是需要大量的工人智慧了。基本上要做的事就是不斷的計算每個部件的顯示位置、倒退多少再顯示什麼東西、然後再倒退再顯示直到所有部件顯示出來為止

不過其實也不是毫無技術性可言,顯示的東西是死的,可是內容是活的,要如何在對應的位置顯示我們要的內容,而且還要有指定的按鈕功能又是一門學問
基本上面對這種充滿大量排列組合的東西時,有一個處理原則就是「加法取代乘法」
舉個例子來說,狂戰士二轉有 8 個技能
其中 5 個技能是有前置技能的,平常會是正常的藍色,但前置技能等級不夠的時候會顯示成灰色
如果我們用排列組合去窮舉所有的可能,我們會需要做 2*2*2*2*2 = 32 種不同的顯示。但是如果我們把每個技能拆開來判斷該顯示什麼,最後再合併起來,就只需要做 2+2+2+2+2 = 10 種不同的顯示。個別判斷的方式也非常簡單,只要把個別要顯示的內容存成 nbt 字串,透過 json 的 nbt 功能去讀就好

嘛原則擺在那邊,實作起來每個人的思路還是不太一樣,基本上就看邏輯架構可以越清楚,寫出來的東西就越精簡,尤其是 json 格式繼承的部分。所以這邊我就不一一敘述我怎麼把東西拼起來的了,細講下去內容也挺枯燥的,反正已經具備所有看懂這套系統組成的知識了,真的看不出來又想知道的再來問我吧

慣例附上成品效果的影片
(話說感覺最近文章越來越沒梗了,大家會不會越看越無聊啊)



同場加映:打造最強裝備!

說到楓谷的裝備,大家一定都不會對衝捲系統感到陌生,玩全幸賊的身上沒有+敏浴巾遇到青蛇只能叫爸爸,傳說中的 9 捲褐手憑著多出 2 捲價格就翻了好幾倍。要想知道一件裝備到底衝成怎樣也是要靠 UI 來做顯示,所以就放在這篇一起講吧

裝備數值的部分直接存在道具的虛擬 nbt 即可,非常簡單不再贅述
而關於怎麼顯示數值,從上面的影片可以看到我怎麼顯示道具的詳細資料,基本上是照著楓谷的資料顯示順序一行一行顯示下來,扣除掉道具名稱是 Name 以外每行就是 Lore 的一項內容。而在楓谷中裝備每項能力的顯示是有固定順序的,能力種類非常多的混炎盔就可以拿來當作參考

順帶一提中文版的排版雖然稍有不同,但是順序是相同的,為了方便製作就統一套用英文的排版方式

因為前面裝備條件的部分行數是固定的,所以只要有了這個順序,我們在衝捲時只要先把條件顯示之後的 Lore 清除,再依序檢查每種屬性是否存在,如果存在就新增對應數值的顯示就行。而衝捲失敗的時候更是簡單清掉 Lore 的最後一項內容(必定是剩餘捲數),補上新的剩餘捲數顯示即可

另外大家應該有印象,從滿早的一次改版之後,衝過捲的裝備不再是單純的橘色名字了,而是有了灰、橘、藍、紫、金這幾種顏色,而且衝過捲還有一個特色就是名稱後面會有成功幾次的數字顯示
(傳說中的+9荷葉應該就是起源於此)
首先來解決名稱顏色的問題,經過一番考古得知裝備名稱的顏色是依照下列的公式運作的
~0:灰色
0~4:白色  (只要衝卷成功過就是橘色)
5~21:藍色
22~39:紫色
40~:金色
而上面的數字就是所有屬性,相對裝備標準屬性總共增減了多少,其中 HP/MP 是以 10 點為一個單位
例如楓葉法杖標準屬性是 HP+100,MP+50,物攻+39,魔攻+58
假如衝捲之後變成 HP+120,MP+50,物攻+39,魔攻+61
HP 相對標準增加了 20,換算整體素質+2
魔攻相對標準增加了 3,整體素質+3
整件裝備的素質就是 5,也就是藍色的名稱

有了顏色公式以後就是把東西串起來了,不過我們不能直接拿道具的名稱來作修改,因為只要衝過卷軸裝備的名稱就會改變,我們沒辦法把它變回乾淨的樣子。而這個問題的解決方法也很簡單,就是另外存一份裝備的原始名稱在虛擬 nbt 中就好,每次更改道具名稱都抓這個原始名稱來改,改完存進顯示名稱中就沒有問題了

至於成功機率與爆裝機率的部分用隨機 0~99 的數字生成,判斷是否小於指定機率百分比就能簡單完成,隨機數字的製作方式網路上也有百百種,這邊就不多作說明了

最後來看一下衝捲的效果影片吧

轉眼間馬上就要過年了,過完年就要準備當社畜了,不知道之後還有沒有機會跟心力做這些東西
Terry 目前正式在建築部分上工,希望過年前能順利推出超綠的地圖,畢竟也已經花了半年的心力在這個作品上,至少出充滿未知的社會前有一個作品出來就算之後棄坑也比較沒有遺憾
等推出之後再麻煩各位多多支持了!

我是貓狗喵,我們超綠體驗版見
36
-
LV. 41
GP 2k
10 樓 貓狗喵 kevin22152
GP16 BP-
時隔一年半,富堅都回來畫獵人了,是時候回來填坑了
順帶一提,四樓留言朝聖 Terry 預言家

番外:今晚,我想來點 ... 版本升級加優化
時隔這麼久,版本都從 1.16 來到 1.19 了
雖然最近幾個版本沒有大規模的指令格式調整,不過還是有不少細節需要修改
在做新東西之前,還是先來把一些陳舊的內容更新一下吧!


會導致 BUG 的東西

1. replaceitem
這次的版本更新最直接有影響的就是 replaceitem 指令了,直接被合併到 item 指令裡面
好在換成新格式後不用調整太多,把 replaceitem 改成 item replace 然後補個 with 就好了

2. 自訂字體的空格現在是一個字元了
自訂字體可說是 MapleCraft 使用最廣的東西了
在 1.16 的時代,空格就是空格,無論什麼字體用起來都一樣
然而到了 1.19 後,空格也是一個字元了,自訂字體中沒有指定的字元會變成方格符號,可想而知我用 1.19 打開馬上就看到血條介面到處都是方格......
不過解決方法也很簡單,在用到空格的自訂字體中補上空格的定義 (5 px 的空白) 即可

3. 界伏盒大法修改玩家身上道具
不確定哪個版本,界伏盒大法需要的 loot table 有了小小的格式修改,導致舊版的 json 檔直接失效
修改的方式很簡單,只要在 set_contents 那層加上 "type": "minecraft:shulker_box" 即可
可能會有人問不是有 item replace from 可以用了嗎,還要界伏盒大法做什麼?
答案是 item replace 沒辦法用 give 的方式,只能指定一個欄位塞東西
所以要做到不特定欄位給東西,或是從某格往後塞不定數量東西,還是得用界伏盒大法


系統優化的東西

1. 用 Marker 取代藥水雲
這應該是有考慮過效能的人都知道的事情了,顯示模型要用盔甲架或人形生物,只顯示名條可以用藥水雲,如果連名條都不用只是用來定位,那用 Marker 就是最輕量化的作法

2. 隨機數產生器
原先版本中,我是用經典的 LCG 公式來生成隨機數,計算上相當輕量
不過這種做法最大的缺點是,容易遇到取某些範圍的隨機數時,會有規律循環的問題
最簡單的例子就是當取 0 ~ 1 的隨機數時,上述公式無論怎麼設定參數都會遇到規律循環

稍微列個式子就會知道:
A奇數,C奇數:必定規律 0 1 0 1,因為 奇 * A(奇) + C(奇) = 偶偶 * A(奇) + C(奇) = 奇
A奇數,C偶數:必定固定為 1 或固定為 0,由初始值決定
A偶數,C奇數:必定為 1
A偶數,C偶數:必定為 0

這個現象不會只出現在 0 ~ 1 這個範圍,以我原先使用的公式來說,取 0 ~ 2 同樣會遇到規律循環的問題,因此要用到這些範圍時,我都要取個比較大的範圍(例如兩倍),然後再平均分配區間做判斷
一不小心忘記就會發現某些事情出現的特別規律,相當麻煩

如今各類程式語言基本上都有內建的 random 函式庫,公式會稍微複雜一點,基本不太會遇到上述的問題,甚至是經過統計與密碼學驗證的,所以能呼叫內建的隨機數功能是最好的
Minecraft 內建也有許多取得隨機數的方式,如 @r 的實體分數組合、生成實體取 UUID 等等,不過考量使用的便利性以及效能消耗,這些做法我都不是很喜歡

剛好之前有看到一個方便的隨機數產生器,用的是 loot table 功能中 設定可疑的燉湯效果時間 的功能
搭配 1.17 後 loot table 數值可以傳入計分板分數的功能,可以很方便的指定一個範圍的隨機值。效能上只需要生成物品到箱子內再讀進記分板,也算是相當輕量的
相關原理可以參考璇的這篇文章:【閒聊】參數化的隨機數產生器


體驗優化的東西

1. 等級與經驗條顯示
當初製作 HP/MP 條時為了視覺平衡把 EXP 條拔掉了,不過這也代表這些資訊只能打開能力值 UI 來看,使用久了其實滿不方便的,打怪也缺少回饋感 (尤其把經驗倍率調高之後)
趁這個機會就把這兩項資訊塞進去吧!

相關技術基本上已經在第二步解釋過,剩下的就是慢慢喬的苦力功了
不過這邊有一個小東西可以提一下,就是左邊的等級顯示,這東西最多三個位數,並且置中顯示
沒錯,這玩意不靠左也不靠右,而是置中
在不額外用指令計算的情況下,我怎麼知道現在有幾個字,應該要退到哪邊開始顯示才會是置中?

答案是:把倒退文字的寬度切成一半

原本我們是定義另外一套,根據每個字的寬度倒退同等寬度的字體
現在把這套字體的寬度砍半,把要顯示的東西夾在中間就達成了
(依序找到中線 → 用倒退字體顯示內容 → 顯示正常內容 → 用倒退字體顯示內容)

這樣無論有幾個字,都能用同一條指令輕鬆做出置中效果了。其實只要做一次倒退就能讓文字有置中的效果,之所以做第二次倒退,是為了不要影響後面內容的顯示位置,否則顯示的文字越多,後面內容就會被推得越右邊

但是當遇到有文字的寬度是奇數 px 時怎麼辦呢?又不能倒退 0.5 px 這種數字
其實就把倒退字體拆成兩份,個別倒退 (X / 2)(X / 2 + 1) 的寬度,一份放前面一份放後面就好
雖然多少會稍微偏移中線,但是其實不太明顯,只要不影響後面排版即可

2. 繩子好難爬!
想當初超綠版本被反應最多次的問題就是,那個繩子有夠難爬!
看大家整天繩子爬一爬掉下去也不是辦法,只好做點什麼來讓爬繩子這件事變自然了
就我的觀察,玩家常常爬到一半掉下來的原因就是:因為慣性滑出去
因為抓不清楚繩子的大小,玩家會跳來跳去,感覺抓到繩子後按住空白鍵往上爬,此時方向鍵通常還沒放開,配上跳躍時的慣性一下就滑出去了

因此我們要做的就是兩件事:消除慣性 以及 置中

消除慣性的作法相當簡單,只要讓玩家 tp 到自己身上即可。當傳送到實體目標 (傳送到座標沒有用) 時,除了位置之外 Motion 也會重置,所以簡單的 tp @s @s 就能達到消除慣性的效果
然而僅僅消除慣性是不夠的,因為只要玩家方向鍵還按著,動量就會不停地產生。如果每 tick 強制消除慣性,那玩家會變成完全無法移動。此時就需要搭配置中的效果

置中的做法同樣簡單,只要 execute align xz 後再做 tp 即可,問題在於何時要做置中
當玩家在鷹架內時連閃置中顯然是個直覺的做法,不過我不是很喜歡連閃 tp 時移動那種畫面快速抖動的樣子,而且 tp 也是相對消耗效能的功能,能少用就少用

綜上所述,我選擇只有當玩家在鷹架內,且水平距離中心 0.3 格以上時才做 置中 + 消除慣性
此時會冒出另外一個問題:玩家該怎麼從繩子上下來?
同樣是在移動,要能區分出一種狀態是不會被置中的,我想答案已經很清楚了,就是用跑的
奔跑狀態用 predicate 就能偵測,只要做置中時排除奔跑中的玩家就大功告成了

說實話本來我是想做離開繩子時有個動量跳出去的,而目前要讓玩家移動不外乎四種做法:
  1. 生物推擠
  2. 擊退效果
  3. 爆炸效果
  4. 連閃 tp 模擬
其中 23 可控性太低,而 4 沒辦法取得滑順的移動路徑,畫面也會一卡一卡的
然而即使是 1 在這邊也會被鷹架「黏」的特性給限制住,導致效果大打折扣 (打到骨折那種)
且玩家的碰撞箱是 0.6 格寬,也就是說即使玩家座標 (碰撞箱中心) 離開鷹架 0.3 格,都還在鷹架的影響範圍內,這讓玩家從鷹架影響範圍離開這件事變得很難判斷,最後也只好作罷



這次的內容大概就是這些,其實還有一些優化內容沒提,不過因為篇幅關係就把它們獨立出來了
本篇的內容比較雜,技術成分也比較少,就當作大掃除吧
那麼照慣例每篇文章更新都要來點 GP 門檻,這次就先訂在 510 GP 吧

然後!
先不要急著關掉
首先預告一下,下個階段性目標是 101 組隊任務
但是那個人力是非常之吃緊,尤其 Terry 現在很忙,我們建築人力基本是 0
所以你各位想在麥塊看到 101 的,趕緊通知你的左鄰右舍,把有興趣幫忙的人拉過來吧
不然就要變成另外一個有生之年系列了

目前徵求職務:
  • 場景建築 - 超缺 (給我再多時間也沒辦法自己生出來)
  • 怪物模型 - 也很缺 (雖然可以自己慢慢捏但是超慢)
  • 資料建表 (無特殊能力需求,翻資料庫整理數值、圖片素材即可)
  • 自動化工具設計 (主要將填表資料轉成指令或 loot table 內容)

有興趣的歡迎站內信或 DC 私訊 MaugouMio #2054 (請註明來意,不然我不會隨便加好友)
也可以到我們的 DC 群組:MapleCraft 佛系製作群 追蹤第一手的製作進度
16
-
LV. 41
GP 2k
11 樓 貓狗喵 kevin22152
GP16 BP-
第十步:黏在玩家頭上的血條
可能有人覺得很困惑,之前超級綠水靈版本不是就有玩家血條了嗎?怎麼現在才突然抓出來講
原因是舊的做法跟未來的功能會有衝突,而且某些部分不太美觀,所以這邊找了另外一種方法來替代
不過既然要講新作法,就先簡單介紹一下舊的做法是什麼,以及為什麼要換掉吧

舊版作法
稍微觀察過的人應該會發現,舊版血條其實就是隊伍名稱前綴
簡單來說,玩家會根據當前血量比例,被分配到不同隊伍中,而該隊伍的名稱前綴就是對應比例的血量圖片 (當然是用自訂字體去做)
然而這個做法有幾個問題:
1. 血條顯示無法置中
因為玩家名字長度是不固定的,沒辦法透過位移調整到正中間的位置
仔細觀察就能發現,舊版的血量是向左對齊的,當玩家名字很長或很短的時候特別明顯
2. 玩家打字時,聊天室的名字上方會有血條
隊伍名稱前綴就是名字的一部分,像聊天室這種會顯示名字的地方,理所當然就會把血條一起顯示出來了,當很多人打字互動時,那畫面真D混亂
3. 沒辦法做一些隊友隱形顯示、隊伍推擠等等設定
這也是我決定換掉這個做法的主要原因
當初隱身術就因為這個,沒辦法用預設的隱形,否則會看不到隊友
後來是用 shader 硬刻了一個模擬隱身的半透明效果
但推擠就沒辦法靠這樣蒙混過去了
如果有看上一篇的應該還有印象,我原本想做離開繩子時跳出去的效果
其中提到要做玩家移動最適合的做法,就是利用生物推擠
雖然後來放棄做跳出繩子的效果,但考量到之後三轉技能有二段跳,還是得讓這個功能正常運作

總之因為上述幾個問題,最後就決定找個方法替換掉了

新版做法
不對玩家名條出手,剩下的就只有 tp 實體到玩家身上一條路了 (名字下方的分數顯示沒辦法應用)
但是 tp 這件事有很經典的顯示延遲問題,以單純顯示名條來說,我們做 tp 有三種方式
  1. 單純 tp 盔甲架等普通實體 (下圖 AAAAA)
  2. 單純藥水雲 (下圖 BBBBB,後面會解釋差別在哪)
  3. 任何實體騎乘藥水雲,再 tp 藥水雲 (下圖玩家血條)

首先看到 AAAAA 的部分
可以發現在其他玩家頭上時,這個是最同步的,但是在自己頭上看起來有很大的顯示延遲
造成這樣的原因是,麥塊為了避免網路延遲,導致看其他人位置出現跳動,會把顯示位置做平滑位移,而不是直接更新成最新的位置,這是線上遊戲常用的手法
而玩家自身為了有即時的操作反饋,會直接根據玩家的輸入做運算直接顯示,再把結果更新給伺服器做驗證、修正等等 (很 lag 的時候常發生的回溯就是被伺服器修正的現象)
有興趣的可以參考這篇 文章,在 Improved latency handling 的部分有做相關解釋

玩家自身跟其他物件的不同顯示延遲,就導致了上述的現象
這個做法的缺點很明顯,就是自己的血條延遲實在有點高,畫面會很混亂

再來看 BBBBB 的部分,你可能想說奇怪了同樣是實體,藥水雲是有什麼特別的
還真的很特別!
如果你把藥水雲拿去 tp 你會發現,這玩意壓根一動也不動
要想讓它移動,你必須更動 Air 這個 nbt 才會刷新顯示 (不要問我為什麼,我也不知道)
要注意有更動才有效,所以如果是相同的值,寫入 nbt 這個動作會執行失敗,也就等於沒有更動
最簡單的做法是直接 time query gametime 或每次 add 某個分數並取值來確保每次的值都不相同

這個做法相當暴力,每次更新顯示就是強制顯示到最新位置,自然也就沒有平滑移動的處理
而這個問題也很顯而易見,那就是沒有平滑移動的壓制,顯示跳動整個重返榮耀
(你看上面 GIF 沒有跳動感,是因為我為了壓縮圖片大小把 FPS 縮成 20 了,跟 tick 剛好同步)

最後是你看已經被我拿來做血條,肯定就是我覺得比較好的 實體騎乘藥水雲 的做法
這個做法可以說是上面兩種方法的折衷方案,保留了平滑移動的效果,但遠比單純 tp 要快得多
跟第二種做法一樣,這個做法必須對藥水雲做 tp 並刷新 Air 來進行移動
上面騎乘的實體可以是任何實體,包含藥水雲
也就是說單純要顯示名條的情況下,用 藥水雲騎乘藥水雲 是最省效能的組合方式

另外順便提一下這個做法的另一個優點,那就是可以保證面向更新
第五步的時候有提到,做 tp 有時候會遇到轉向不完全的情況
然而如果是使用這種作法,就可以保證實體一定會轉到指定的面向
要注意 轉向 騎乘者 控制,但 座標 藥水雲 控制,兩者必須分開處理
這個特性也是 Animated Java 得以實現的技術基礎
(這是一個方便做模型動畫的工具,詳細介紹可以參考【攻略】免模組製作實體動畫 Animated Java)

什麼?你說為什麼會有這個特性?還有怎麼被發現的?

不過新版做法也有一個小缺點,那就是玩家一定會看到自己的血條
說實話有時候真的滿礙眼的,而且原本楓谷也不會看到自己頭上的血條
但是看到隊友頭上血量資訊還是很重要,所以就將就一下吧

效能優化
工具齊全了,剩下的就是把藥水雲 tp 到玩家身上,以及更新顯示的血條
讓藥水雲 tp 到玩家身上很容易,把玩家跟藥水雲配成一組,用編號對應就好
而這個做法必須讓 所有玩家 搜尋 所有藥水雲 才能做到
也就是 玩家人數 x 藥水雲數量 的複雜度

但是你有沒有覺得哪邊很多餘?

我們要的效果是:每個玩家都有一個藥水雲跟著自己跑,並更新血條顯示
理論上每個玩家只要依序認領一組藥水雲就好了對吧
預期應該只要 玩家人數 (= 藥水雲數量) 的複雜度就能做到
那具體要怎麼做呢?

我的作法是:紀錄每個玩家的座標跟血量資訊,再由藥水雲依序認領
只要讓所有玩家依序把 座標血量 紀錄在 storage 的 list 中,接著就只需要讓藥水雲依序取 list 中的第一筆(或最後一筆)資料並移除,就能有一一對應的效果了
而當藥水雲數量跟玩家數量對不上時,再看要來多生幾組或刪掉幾組藥水雲即可
另外要記得顯示血條的藥水雲,跟控制座標的藥水雲是兩個不同實體,讀資料時也要分開來運作

由於預設 @a@e 的執行順序是實體出現的順序,在玩家數量不變的情況下,每次藥水雲都會認領到同一個玩家記錄的資料。只有在有玩家登出時,會發生藥水雲更改對應玩家的情形,也只有這時候可能會看到短暫的血條閃現,但相信這個看起來影響不大

因為上面的 GIF 已經展示過新版血條的效果,最後就不放展示影片作結尾了



現在時間變破碎,常常想找時間寫文章,看看只有幾十分鐘又鴿了跑去耍廢
好懷念以前整天幾乎都是自己時間的時光啊
不過既然 GP 達標了就會更新,應該不會拖到一個禮拜以上   
不過看現在 GP 增加速度,搞不好哪天真的就永遠不會達標了也說不定 (?

下次目標先訂在 540 GP,如果真的無法達標就看心情更新吧
16
-
LV. 41
GP 2k
12 樓 貓狗喵 kevin22152
GP27 BP-
第十一步:戳箱子與楓幣丟丟樂
之前提到下個目標是 101 組隊任務,其中戳箱子的功能就是一大特色
而丟楓幣則是玩家間的習慣,在打眼睛或是進地圖打箱子前,都會在門前丟楓幣表示有人,避免重複進入浪費時間。而且未來做三轉技能楓幣炸彈時,也必然會用到丟楓幣的功能
兩者各獨立一篇文章的話感覺內容有點寒酸,所以就合在一起介紹了
(眼睛製作也有一篇文章,但跟麥塊比較無關所以只放在小屋,有興趣的可以去看看:連結)

戳箱子
在楓谷裡面其實把箱子歸類在 reactor 類別,這個類別包含常見的箱子、植物、椰子、石頭等等
戳箱子的邏輯跟攻擊怪物其實有很多雷同,原本也在考慮把箱子做成怪物的一種,但想了想還是獨立出來做,主要原因有幾個:
  1. 箱子有被攻擊的無敵時間 (大約 1 秒)
  2. 箱子只能用近距離普通攻擊破壞
  3. 箱子被攻擊的優先度高於一般怪物
  4. 箱子根據被破壞的程度,可能有不同樣貌 (不同破壞程度的受擊動畫也可能不同)
要把上述幾個特性合併到怪物的機制,實在有點太複雜了
而且箱子也不需要仇恨值、冰凍中毒等設定與檢查,獨立處理對效能也比較優化
於是乎這邊箱子獨立用一種生物(殭屍)處理

在進行普通攻擊時,優先檢查目前武器近距離的攻擊範圍內有沒有非無敵狀態的箱子(殭屍)
如果有,就跳到攻擊箱子的處理邏輯,並跳過原本接下來要執行的攻擊流程
這部分沒有什麼特別的,就是照搬原本攻擊特效、攻擊冷卻等等機制,然後對箱子作處理而已

而箱子被攻擊的流程,大致可以畫成這樣:
破損度增加 (HP - 1) 若被破壞(HP歸零)時噴掉落物 or 執行特殊事件 計時 1 秒無敵 播受擊動畫 動畫結束後切換成對應破損度的樣貌

破損度用 HP 的概念處理非常簡單,不過受擊動畫怎麼呈現就值得思考了
最直覺的做法是,判斷目前箱子是哪一種、以及當前破損度是多少來播對應的動畫
但是這樣做的話,箱子種類一多就要做很多冗長的判斷,不論是製程還是效能都不太理想
那麼要怎麼不用額外判斷,就能播放對應的動畫呢?

其實只要把需要的資訊存在動畫裡就可以了
簡單來說,播放動畫的原理,就是快速切換顯示模型對吧
而切換模型最簡單的方式就是更改 CustomModelData,每 tick 把他的值增加,來依序顯示不同模型
(順帶一提,塞 CustomModelData 時只要塞關鍵畫格就好,詳細說明可以回去看第四步介紹)
那麼我只要把箱子在破損度 N 時的受擊動畫模型,接在該箱子破損度 N 的模型後面,是不是播動畫時就只管增加 CustomModelData,不用在乎你是什麼箱子什麼破損度了呢?

舉個例子,玩具城箱子敲 4 次會破
假設我們把初始箱子模型的 CustomModelData 設成 1
每次箱子受到攻擊,就連續 20 tick 把 CustomModelData 加 1
(因為無敵時間是 1 秒,所有受擊動畫長度都不會超過這個時間,至少我還沒找到特例)
也就是把 CustomModelData 分配成這樣
1:初始模型2 ~ 20:第一次受擊動畫
21:破損1模型22 ~ 40:第二次受擊動畫
41:破損2模型42 ~ 60:第三次受擊動畫
61:破損3模型62 ~ 80:箱子破壞動畫
如此一來,每種箱子只要分配 HP x 20 數量的 CustomModelData 區間,就可以統一用一套邏輯播放動畫以及切換破損度模型了

而最後剩下的破壞事件其實就跟打怪掉寶的邏輯差不多,這邊不多做贅述
(獎勵關卡那種寶物噴泉我還沒實作,之後有機會再說)

照慣例來看看製作的成果吧

楓幣丟丟樂
其實丟楓幣的部分比較沒有什麼技術成分,能講的並不多,所以就在這篇附帶介紹了
首先是讓玩家決定丟多少錢的功能,在 Minecraft 中要讓玩家自由輸入數字,基本上只有 trigger 這個做法了 (scoreboard 需要 OP 權限,而寫在書裡的話只能看,沒辦法當作分數計算)

所以這邊的做法就是讓玩家輸入 trigger 指令設定要丟多少錢,連閃偵測玩家的丟錢分數不為 0 就執行丟錢功能,並重設玩家的丟錢分數與重新開啟 trigger
需要注意的是,即使玩家使用 trigger 將分數設定為 0,同樣會消耗掉玩家使用 trigger 的權限 (然而因為分數為 0 我們不會偵測到變動),所以必須讓玩家有手段重啟 trigger 權限,或是定時幫玩家重啟權限

設定好要丟的數量後,剩下的就跟怪物掉錢是差不多的邏輯
首先用 CustomModelData 控制楓幣顯示的模型,利用前面提到的關鍵畫格的機制,只要設定 1, 50, 100, 1000 四個 CustomModelData 對應銅幣、金幣、鈔票、錢袋即可

接著是處理撿起來的數量計算,為了知道玩家到底撿了多少,必須列舉玩家身上的道具並存取 CustomModelData 的值,而不能單純用 clear 計算道具數量
先利用撿取特定類型道具的記分板偵測,當玩家撿起楓幣道具時,先將身上所有該類型道具資料用 data modify append from 抓到 storage 中,如此一來就能對 storage 內容做遞迴取值了 (當道具可以堆疊時,記得取完 CustomModelData 後要乘以該格道具數量)

最後是誰可以撿起這些楓幣,畢竟如果單純把道具生出來,玩家一邊丟一邊就把東西撿回來了,所以勢必要給道具加上 PickupDelay 的設定
簡單的做法可能就設個久一點的 PickupDelay,讓玩家丟完趕緊走人。但玩過楓谷都知道,這樣對玩家操作來說實在太不切實際了,光是要引爆楓幣炸彈都有障礙
那如果要讓玩家想撿才撿,就會遇到兩個問題:用什麼方式撿?怎麼確保會是自己撿到?

在楓谷可以設定撿取按鍵,但 Minecraft 中能用的按鍵實在少得可憐
雖然可以像攻擊做一個按鍵道具給玩家放快捷欄,但玩家放技能喝藥水就很吃緊了,實在不想再為了撿東西占用一格空間
最後取捨一下決定利用蹲下的按鍵來當撿取鍵,畢竟目前 MapleCraft 必須用到蹲下的情況並不多,只有爬繩子往下、以及向下瞬移。這兩個情況應該不太會跟誤撿東西有所衝突

然後就是怎麼確保地上的東西會被自己撿到,相信大家都有遇過,想丟東西給別人卻被自己吸回來的情況,這是因為撿東西是透過實體排列的優先序,也就是登入順序來撿東西的
而解決這個問題的做法其實很簡單,就跟 give 指令做的事情一樣
可能有些人不知道,當 give 指令給東西給到滿出來時,掉出來的道具就只有 give 的目標可以撿
其中利用的就是物品實體的 Owner 這個 nbt 標籤
因此我們只要同時把地上道具的 PickupDelay 設為 0 並將 Owner 設為自己的 UUID,就能確保東西只被自己撿到了,另外還能限制每 tick 只撿一個物品,達成楓谷裡面撿取按住狂吸東西的愉悅音效
(後來不只楓幣,我也把這機制套用到玩家丟出的道具上了,避免玩家整天撿到自己丟的垃圾)

最後來看一下成果吧



現在擠 GP 感覺像在擠最後的一點牙膏一樣
再...再捏一點,下一篇門檻捏個 560
然後下一篇就正式進入 shader 領域了,期待的話可以找親朋好友幫忙按 GP,或把前面幾篇文章都點一下,這種用愛發電的東西全靠大家支持來維持動力

然後我們還在徵人喔!會蓋東西的不要害羞,趕快來加入製作團隊吧!
27
-
LV. 41
GP 2k
13 樓 貓狗喵 kevin22152
GP22 BP-
第十二步:打開 Shader 的大門 - 怪物淡入
淡入可說是最被廣泛運用的特效了
瞬間出現的東西有很強的視覺吸引效果,通常是在強調速度或引起觀眾注意的時候使用
也就是說,如果想讓東西比較自然的出現,就需要有漸變效果的存在,包含從畫面外漸漸移入、由一團粒子聚集而成、以及本篇要講的淡入效果等等

而在楓之谷中也廣泛運用淡入的效果,除了少數有登場動畫的怪物之外都採用淡入的方式登場

那麼在麥塊中有辦法做到這樣的淡入效果嗎?(廢話如果沒有我還會寫這篇文章嗎)
首先第一種暴力的作法是,手動對淡入過程的各種透明度輸出不同透明度的材質模型,然後依序切換模型來達到淡入效果
很顯然這是一項吃力不討好的作法,所以之前超級綠水靈版本中我沒有做這個淡入效果

然而到 1.17 之後情況不一樣了,在 1.17 新增了 core shader 的設計
所謂的 core shader 就是把各類型物件渲染的 shader code 外調到資源包,供玩家自由修改
在 1.17 之前只有畫面後製 shader 有開放給玩家調整,這部分我之前有寫過一篇文章介紹
而 core shader 是更底層,直接控制了包含方塊、實體、模型、名條等等非常多種東西的渲染
這些全部都放在資源包的 minecraft/shaders/core 資料夾底下
關於各種 core shader 個別負責哪些東西的渲染,可以看 ShockMicro 整理的這份資料:連結

如果要完整介紹 core shader 的功能,一來太花篇幅,二來我也沒有完全理解所有東西在做什麼
這邊就展示一些別人使用 core shader 做出的效果,然後切入今天的主題吧

GodLander 的 objmc 動畫模型 (整個動畫只用了一個模型)
官方人員 Felix 的範例,搖晃的樹葉:https://twitter.com/Xilefian/status/1369677620561014784
(欸不是,巴哈怎麼一堆影片都嵌入不了)



那麼要用 core shader 做到怪物淡入的效果,具體需要哪些東西呢?

首先是可以控制透明度的參數,目前 core shader 可以在遊戲內傳入並自由控制的參數,原則上就只有硬著色的顏色了,也就是藥水的 CustomPotionColor
(沒錯,辣個藥水又回來啦!堪稱 MapleCraft 系統的 MVP)

有了可以控制的參數後,下一步就是找到對應的 core shader
我們的怪物模型都是戴在頭上的藥水道具,對應的是 rendertype_entity_translucent_cull 這份 shader
這時你會發現有三個同名檔案,副檔名分別為 json, vsh, fsh

▼ 以下個別介紹這三個檔案,內容比較繁瑣,只想知道怎麼應用的可以跳過 ▼
在介紹這些東西之前,要先對 shader 的渲染有基礎的認識
首先 shader 是透過我們的 GPU 來執行,也就是俗稱的顯卡
GPU 可以理解成是由上千個小型 CPU 組合而成,我們都知道越多核心的 CPU 可以同步處理的東西越多,而 GPU 就像是超多核心的 CPU,雖然個別的運算速度不快,但是可以同步運算非常大量的東西,因此經常被拿來處理繪圖與矩陣計算

舉個例子,解析度 1920 x 1080 就代表畫面上有 2,073,600 個像素點
今天我們要顯示一張圖片在這個畫面上,就要知道每個點分別要顯示什麼顏色,也就是說要重複兩百多萬次「這個像素對應到圖片哪個點,那個點顏色是什麼」的動作
如果我們用單核心的 CPU 來做這件事的話,他就必須孤單寂寞的跑兩百多萬次這樣的處理
然而這兩百多萬個點誰先算出要顯示什麼顏色,看起來沒有影響對吧?
也就是說如果我有 1000CPU,把每個像素當成一份工作平均分配給他們處理,平均每個 CPU 就只需要跑 2000 多次這樣的處理,這就是 GPU 繪圖為什麼可以的主因

解釋完 shader 計算的原理,首先來看到 json 這個檔案
這個檔案是用來定義這個 shader 渲染的流程與設定,包含指定要使用什麼 vsh (vertex shader) 與 fsh (fragment shader),以及要讀取的 sampler (材質圖片資訊)、頂點位置、貼圖對應點、uniform (唯讀參數) 等等 (這些名詞的意思需要有一點 shader code 的背景知識,這裡不做詳細的解釋)

vsh 是所謂的頂點著色器,簡單來說,麥塊的模型是由一個個方塊堆疊而成的,每個方塊都各別有六個面,每個面各有四個頂點,這些頂點要顯示在什麼位置,以及他對應到貼圖材質的哪個點,都是透過 vsh 計算出來的

vsh 算完各個頂點的資訊之後,就會傳到 fsh 計算每個像素要顯示什麼顏色
其實vshfsh 之間還有其他複雜的運算,像是把頂點連接成線,再把線連接成面,並且考慮哪些東西會被遮擋,最後透過插值法計算出畫面上每個像素點對應到材質貼圖的哪個點,以及光源、陰影等等的資訊
最後 fsh 就能根據上面的資訊,具體算出每個像素點要顯示什麼顏色

其中我們的 CustomPotionColor 其實就是設定了 vsh 檔案中的 Color 這個參數
這邊很容易被忽略的一個點是,在第四步中有提過 CustomPotionColor 只對有設定 tintindex 的材質有效,同理只有設定了 tintindex 的材質面,在 vsh 中的 Color 才會有對應的值,否則預設都是全白

這邊的 Color 變數是由四個 0 ~ 1 的小數組成,紅綠藍依序可以用 Color.r / Color.g / Color.b 取值
其中 0 ~ 1 就對應了我們設定顏色的 0 ~ 255
舉例來說,如果我們要設定 (255, 100, 0) 的顏色
換算 CustomPotionColor 就是 (255 x 65536) + (100 x 256) + 0 = 16737280
此時 vshColor 值大約會是:
Color.r = 255 / 255 = 1.0
Color.g = 100 / 255 = 0.39215
Color.b = 0 / 255 = 0.0

知道怎麼換算之後,就是把我們需要的數值區間拿來使用
在 MapleCraft 中使用到 CustomPotionColor 的東西,只有怪物冰凍、中毒等等染色,以及不同熟練度武器的揮動特效
於是我決定拿 (0, 0, 0)(99, 99, 99) 的灰色,來當作透明度 0% ~ 99% 的設定
這 100 種灰色不會跟其他需要用到的顏色衝突,可以放心使用
(當然你可以自己根據需求,指定 N 種顏色來代表 N 種透明度)
跟 rgb 一樣,在 shader 中透明度也是 0 ~ 1 的小數,觀察一下可以發現 Color 是由四個小數組成,前三個數對應 rgb,而最後一個其實就是對應透明度,也就是 a
不過因為 CustomPotionColor 沒辦法設定透明度,所以 Color.a 永遠只會是 1.0 (完全不透明)
為了接下來計算方便,我們算出來的透明度也以 0 ~ 1 的小數來表示

接著在把透明度實際套用上去之前,還要先處理一個東西,那就是 minecraft_mix_light
這個 function 把 Color 當成一個參數,在計算陰影之後套上 Color 這個顏色,這就是一般情況下 CustomPotionColor 套色的方式
然而這邊我們設定上述的 100 種灰色時,只是想做不同的透明度,並不是真的要染成灰色
因此我們必須把 minecraft_mix_light 傳入的 Color 替換成白色 vec4(1.0, 1.0, 1.0, 1.0)

終於來到最後一步,要把我們的透明度套用到顯示上
由於顯示什麼顏色是由 fsh 控制的,我們必須把 vsh 中算出的透明度傳過去
傳過去的方法也很簡單,只要在 vsh 宣告一個 out 變數,然後在 fsh 宣告相同名稱的 in 變數
就可以在 fsh 中讀取 vsh 宣告的那個變數了

這邊 fsh 要用到的變數是 color (注意跟 vshColor 大小寫不同)
可以看出他是透過 texture 這個 function 取值的,也就是對應材質圖片在該位置要顯示的顏色
上面算出來的透明度傳到 fsh 後,只要把 color.a 乘上我們算出來的透明度就大功告成了 (因為前面已經把算出來的透明度用 0 ~ 1 的小數表示了,等於數學意義的 0% ~ 100%)
因為最後顯示的顏色是存在 fragColor 這個變數,所以 color.a 記得要在算出這個之前設定
(建議是在 if (color.a < 0.1) 這個檢查之前就設定,這是麥塊不顯示 10% 透明度以下的顏色的機制)

終於把整套做法講解完了,最後來看一下效果吧



這篇的內容斟酌了好久,因為需要的背景知識很多,全部講解又太過枯燥繁瑣
這邊是盡量用通俗白話的方式來解釋,建議想接觸這塊的還是要研究一些相關知識
不過即使不懂 shader 還是有可以應用這功能的地方,但這就要等到之後 objmc 篇再介紹了

話說上一篇催 GP 好像真的有一點效果,這篇再請大家多多支持了
下一篇是番外篇,找時間我會直接更新上來,至於下下篇的門檻就訂在 620 GP 吧

最後慣例徵人,不用擔心技術不夠好,只要願意溝通蓋東西就歡迎加入我們!
22
-
LV. 41
GP 2k
14 樓 貓狗喵 kevin22152
GP11 BP-
番外二:移動速度大解密
之所以會有這篇冒出來,是因為我原本對楓谷跟 Minecraft 的移動速度單位都有所誤解
導致我對積木泥人 (楓谷設定 -60 移動速度) 測試緩速術時,體感速度一直差很多
不過網路上有沒有看到詳細速度跟實際移動距離轉換的公式 (楓谷跟 Minecraft 都沒看到)
所以只好拿出我們的科學精神,來做實驗囉!

注意:本篇內容以實驗為主,技術內容較少,沒興趣的可以跳過


首先是楓谷的移動速度,我們從資料可以看出,緩速術滿等的效果是減少 40 的移動速度
並且根據描述,這應該不是百分比,而是一個固定的數值
也就是說原本移動速度越快的怪物,被緩速的比例應該越小
這點拿 鱷魚(預設 -40 移動速度) 跟 青蛇 (預設 +10 移動速度) 體感比較一下就很明顯了
鱷魚體感移動速度幾乎只剩一半
青蛇雖然也變很慢,但比例上明顯沒有鱷魚差這麼多

確定緩速術的效果是定值而非比例後,接著就是要推算實際移動距離跟速度的關係
首先假設速度跟距離成正比,我們可以列出一個公式:距離 = 移動速度 x 時間 x C
其中 C 是一個定值常數,作為正比的未知係數
令原始移動速度為 X,當移動時間相同時,理論上被緩速怪物的移動距離就是原本的 (X - 40) / X

這時候拿出我們的錄影程式,引誘正常跟被緩速的怪物
透過逐幀的播放器取固定的時間差,看看兩者個別移動了多少距離
這邊找了幾種不同移動速度的怪物做比較

青蛇(+10) & 小幽靈(0)
青蛇有無緩速分別移動了/色框的距離,緩速的移動距離約為 0.64
小幽靈有無緩速分別移動了/色框的距離,緩速的移動距離約為 0.6
套到上面公式 距離 = (X - 40) / X 可以得出,基礎移動速度大約等於 100 (也就是說青蛇是 110)

積木泥人王(-40)
同樣比較有無緩速的移動距離,比例大約為 0.34
如果帶入上述公式並且基礎速度為 100 的話,期望的比例應該是 0.33,感覺在合理的觀測誤差內

積木泥人(-60)
值得注意的是,假設基礎速度是 100 的情況下,緩速的積木泥人移動速度應該是 0
然而很明顯,雖然慢但他確實是會移動的,所以這個答案肯定不是正解
這邊同樣計算出比例,大約為 0.23
如果套公式回推原始移動速度,大約是 52,也就是 -48 的表定速度,比實際快了將近 10 的移動速度
看到這邊我遊戲程式設計的經驗告訴我:是不是有速度下限?
在遊戲設計的過程中,為了避免極端數值出現問題,做一些上下限處理是很正常的事情
如果設定移動速度下限是 10 的話,套用基礎速度為 100 的假設
緩速後的泥人移動速度就是 10,正常情況下則是 40,計算比例會是 0.25
原本 0.23 這個比例是用 13 / 56 算出來的,如果考慮 1px 的誤差變成 14 / 56 就是完美的 0.25

雖然暫時沒有找到移動速度介於 -40 ~ -60 之間的怪物驗證上下限的猜想,不過我想應該八九不離十了
綜上所述,可以推測基礎移動速度為 100,並且下限速度為 10

(什麼?你說要去哪邊測試這些東西?不要問你會怕)


接著是 Minecraft 的移動速度 (再不講 Minecraft 的東西觀眾都要懷疑跑錯版了)
在 Minecraft 測試就簡單的多,不用在那邊錄影截圖比大小看到眼睛脫窗
首先我們可以簡單準備一套測試機器
其中 AB 是壓力版,兩個距離 10 格,底下接指令方塊執行 time query gametime 取觸發的時間
C 是召喚殭屍的地方,跟 A 保持 2 格距離避免起步加速度產生誤差,並且對齊方塊正中間召喚
D 是玩家站的地方,同樣對齊正中間站立,避免怪物移動路線不是完美的直線導致偏差
接著只要在 D 點站好,切成冒險或生存模式並召喚殭屍在 C 點,等他為了咬你依序踩過兩個壓力版後,再觀察兩個指令方塊紀錄的時間差即可 (注意不能召喚出小殭屍,否則實際移動速度會是 1.5 倍)

這邊直接上統計資料
移動速度 花費時間(tick) 單位時間移動距離(格/秒)
0.1 463 0.4319654427645788
0.2 116 1.724137931034483
0.3 52 3.846153846153846
可以明顯看出移動速度跟單位時間移動距離不是線性關係,而是平方關係
也就是說 Minecraft 的移動速度根本不是字面上的移動速度,應該是動量,也就是加速度

備註:上述統計資料僅限怪物,玩家的移動速度就真的跟移動距離幾乎呈線性關係
大略為每 tick 移動 移動速度 x 2.15 格 的比例

(奇怪怎麼 Minecraft 文章講 Minecraft 的比例反而這麼少)


確定了兩個遊戲的移動速度公式後,接下來只要找到一個基準點就能在兩個公式間做轉換了
這邊我選擇的是怪物移動速度約等於玩家移動速度(走路情況下)
此時楓谷的速度就是基準速度 100,而 Minecraft 的速度大約是 0.3
舉例來說楓谷移動速度 +10 (110) 的青蛇,換算到 Minecraft 為 sqrt(110/100) x 0.3 = 0.31464
(之所以要取開根號是因為 Minecraft 速度跟距離是平方關係,而楓谷是線性關係)

預先算好對應的速度很容易,然而遇到緩速術就沒這麼簡單了
畢竟玩家的緩速術等級不同,緩速的強度也不同,不適合預先算好結果去替換(要準備 20 種)
這時候有兩種做法,兩者都需要先紀錄怪物的楓谷表定速度
  1. 怪物預設移動速度為 0,用楓谷表定速度計算緩速術效果後,換算 Minecraft 速度並寫進身上裝備的 attribute modifier 中 (使用 operation: 0)
  2. 怪物預設移動速度為原始速度換算的結果,計算緩速前後速度比例,將差距百分比(應為負值)寫進身上裝備的 attribute modifier 中 (使用 operation: 2)
第一種作法相對直覺,不過每次召喚的怪物都要經過一次轉換運算
從上面公式可以看到,轉換運算中包含了根號計算,在 Minecraft 計算根號本身就是很複雜的操作
為了節省多餘的計算,我這邊使用的是第二種做法

根據轉換公式,令緩速前的表定速度為 X
緩速後的 Minecraft 速度為原始速度的 sqrt((X - 緩速值) / X)
因此只要取前後值開根號計算比例差距,也就是 1 - sqrt((X - 緩速值) / X)
寫進 operation type 2 的 modifier 運算就能得到我們要的結果

至於 Minecraft 中如何計算根號,可以參考牛頓法的公式
記得要保持的數字精度 (記分板計算只有整數,要保留一位小數就要先乘 10 倍再做計算)
算完的結果寫回 modifier 之前也記得要把計算精度用的倍率還原回去
想直接參考指令的可以看我寫的版本:連結


這次隔了將近一個月才更新,除了在擠怪物模型出來以外,還做了兩個基礎功能的大改動
有在注意目錄的可能會發現 objmc 被移到更後面的篇章了,主要是因為我也還沒研究完全 (之所以想用 objmc 是因為 101 的 BOSS 太大了,普通模型塞不下去,必須用這種方式突破限制),再加上最近做的兩個改動都有很多值得介紹的東西,所以就把這部分延後了

另外關於 GP 門檻的部分,經過一個月的觀察,基本上只有剛發文的一週內會有增加,如果這段期間沒達標的話放再久都沒用
所以想了想之後決定還是把 GP 門檻拿掉,等有空閒的時間再慢慢更新了
(是說我懷疑現在每篇文是不是都只有差不多 10 幾 20 個人看而已)
當然覺得這些內容有用的話還是不要吝嗇你的 GP,這種用愛發電的東西就只能靠大家的回饋當動力了

最後慣例徵人,不用擔心技術不夠好,只要願意溝通蓋東西就歡迎加入我們!

# 101 的建築進度還幾乎是 0 啊
# 然後有在想弄成社群計畫之類的,但是不知道怎麼搞
11
-
LV. 41
GP 2k
15 樓 貓狗喵 kevin22152
GP14 BP-
第十三步:拒絕霸凌,從怪物 AI 做起
有玩過超級綠水靈版的應該會注意到,以前的怪物 AI 會有集體圍毆的狀況
而且在非攻擊狀態,大部分怪物是不會有碰撞傷害的
如果再仔細觀察一下,會發現超級綠水靈在你扁他之前,會待在原地一動也不動
這篇就來解釋這一切前因後果跟優化方式吧!



舊版 AI
首先舊版的 AI 其實就是殭屍豬人,所以才會有集體圍毆的特性
而非攻擊(憤怒)狀態,不會主動攻擊玩家,所以即使貼到玩家臉上也不會有碰撞傷害
至於超級綠水靈是在豬人上面騎熔岩史萊姆,藉此達到比較大的攻擊範圍跟碰撞箱
由於熔岩史萊姆沒有憤怒狀態,看到一個扁一個,所以會有類似碰撞傷害的效果
說到這裡你可能會問:那為什麼不所有怪物都騎熔岩史萊姆來做碰撞傷害呢?
原因其實也很單純,因為尺寸不合

如果騎乘的史萊姆太大,會導致攻擊的碰撞範圍很高,玩家基本上沒有辦法從怪物頭上跳過去
對那些小型怪物來說,用小殭屍豬人 + 尺寸 0.4 的史萊姆都還嫌太高 (這已經是最矮的組合怪了)
然而史萊姆的攻擊範圍並不是單純從眼睛高度延伸,而是再更高一點
也就是說上述組合,在玩家不跳躍的情況下依舊是不會被打到的
所以我就果斷放棄碰撞傷害的效果了

那麼開扁超級綠水靈之前會一動也不動是什麼原因呢?
在解釋之前要先了解騎乘怪的 AI 邏輯



騎乘怪 AI 邏輯
讓我們來看 Minecraft 最惡名昭彰的騎乘怪之一
小雞騎士
應該很多人都有被小雞騎士窮追猛打的經驗,儘管雞本身並不會有追逐玩家的行為
從這裡我們可以知道第一項最基本的邏輯,也就是 上層生物可以控制下層生物的移動

接著我們來看上面超級綠水靈不會動的情況
稍微交叉測試就可以發現,只要上面騎乘的是史萊姆,下層是非攻擊性生物,平常就不會有移動行為
要解釋這個狀況,首先要把移動 AI 分成兩種:閒晃模式攻擊/逃跑模式

閒晃模式
顧名思義就是沒有任何目標的亂走
平常的友善、中立生物,以及沒找到目標的敵對生物都是這個行為模式
這個模式下每隔幾秒就會隨機生成一個移動目標
如果 被騎乘 生物是這個模式,則他不會產生任何移動目標 (即完全由騎乘者控制,即使騎乘者是盔甲架之類根本不會移動的實體。不過騎乘者是深海守衛的話,很神奇的不會有這個限制,我不知道怎麼解釋)

攻擊/逃跑模式
也如名稱所述,就是生物追擊或逃離目標的狀態
其中攻擊狀態幾乎每 tick 都會設定一個移動目標,前往攻擊目標所在位置
逃跑狀態則是每隔一小段時間(根據觀察通常在半秒內)設定一個移動目標,看起來是以遠離攻擊者為主,但會參雜一些隨機值進去
這個模式下騎乘關係就沒有絕對性的影響,無論是騎乘者或被騎乘者,都可以設定新的移動目標

看到上面移動目標被我亮色這麼多次,他到底是什麼東西?
簡單來說,騎乘怪都是朝著最後設定移動目標在移動的
一旦決定了目標,被騎在最下面的生物就會開始跑尋路演算法,計算往目標點的路徑
另外那些天上飛地上跳水裡游都不會產生移動目標,他們的移動應該是另一套邏輯

解釋完這些就能理解超級綠水靈 被攻擊後才會移動 的行為了
平常被騎乘的殭屍豬人處於閒晃模式,不會產生任何移動目標
而上面的熔岩史萊姆是地上跳的生物,也不會產生移動目標
所以平常不會進行任何移動,直到殭屍豬人產生敵意,進入攻擊模式後才開始有移動目標產生



新版做法 ver.1
最一開始研究這個主題,是因為 101 組隊任務中有個撞一下 8000 的戰神
但是我做出來之後才發現他不會閒晃,這樣就沒辦法發揮他的威攝力了
那有了上面騎乘 AI 的理解後,要讓他能夠閒晃最簡單的做法是什麼呢?

就是上面疊一個友善生物來做閒晃擔當

由於雞會下蛋、羊會吃草、豬會被胡蘿蔔釣竿吸引、村民 AI 太複雜比較耗效能,所以我選擇了牛
這個牛要跟熔岩史萊姆放同一層或更上一層都可以,邏輯上沒有影響
如此一來平常閒晃就有牛產生的移動目標,而攻擊時豬人的移動目標會持續產生,不用擔心被牛影響



新版做法 ver.2
講到這邊終於要跟標題的 slogan 扯上關係了
怪物霸凌的情況我已經看不爽很久了,不生氣沒事,生氣起來整天搞群毆 (畢竟是殭屍豬人的天性)
再加上為了控制碰撞箱,有的殭屍豬人會做成小隻的,不只模型比例要分兩種規格,移動速度也要用兩種公式計算(小殭屍豬人有 1.5 倍速的自帶屬性)
還有小怪物沒辦法做碰撞箱也是一直讓我耿耿於懷
既然有了上面的研究,趁這個機會把這些問題一次解決吧!

首先要確定我們有哪些需求
  1. 無論是否在攻擊模式,都要有碰撞傷害
  2. 統一的移動速度與模型比例
  3. 可以隨意切換 攻擊 / 閒晃 模式的 AI

1. 既然需要碰撞傷害,就代表每隻怪物都需要配備熔岩史萊姆來做攻擊(碰撞箱)擔當
殭屍豬人作為決定攻擊目標的存在固然不可或缺 (要能指定 AngryAt 還能顯示模型的也就只有他了)
不過殭屍豬人本身其實根本不用進行攻擊,我們需要的只是他攻擊時的移動目標,當貼到玩家臉上後實際造成傷害的是熔岩史萊姆

2. 至於統一的移動速度與模型比例,都代表要統一規格,不使用小殭屍豬人 (反正攻擊擔當不是豬人,碰撞箱大一點也沒影響,大的豬人模型尺寸上限比較高)

3. 最後最重要的 AI 模式切換,透過上面的原理解說,應該比較容易想到一種騎乘結構
      B    
殭屍豬人 友善生物 熔岩史萊姆
       
     友善生物

這種結構下,下面的 C 不會產生移動目標,也就是說移動是由 AB 進行控制的
這樣的話只要分別開關 ABNoAI 標籤,就可以控制由誰主導移動行為,達成切換模式的效果
然而四個實體組合實在是有點多了,切換模式時也需要用兩次 @e 個別找到 AB 來開關 NoAI
難道效能上就沒有優化空間嗎?

有!

接下來就讓我來介紹另一種結構
      
殭屍豬人 熔岩史萊姆
    
    友善生物

沒錯,跟上面的結構完全相同,只是把 B 給拿掉了
你說那這樣誰負責閒晃的移動?
答案是殭屍豬人本身

你可能會想說 不對啊殭屍豬人生氣之後就不會消氣了(除非目標消失),要怎麼讓他閒晃?
其實只要讓他找不到目標就可以了,也就是把他的 follow_range 屬性調成 0

不過正常情況下,把 follow_range 屬性調成 0 會讓生物再也無法移動,原因是尋路演算法每次就是計算最長距離follow_range 的一段路徑,作為接下來的移動路線,因此 follow_range0 時自然就算不出任何移動路線了
(我沒實際追原碼,但從結果觀察應該是這樣沒錯,如果 follow_range 設太大遊戲會直接 crash,沒意外就是尋路演算法計算太複雜導致的,畢竟距離越長可能的路線越多,要決定路線的複雜度指數成長)

然而騎乘狀態並不在 正常情況 的範疇內
如果你有仔細的看上面的內容,會發現我是說 被騎在最下面的生物 執行尋路演算法
移動目標只是一個目標點,並沒有受到 follow_range 的限制
也就是說上面的殭屍豬人即使 follow_range0 仍然可以產生移動目標,並且因為找不到攻擊目標,此時殭屍豬人是閒晃模式。而下面的友善生物只要有正常的 follow_range 就可以正常執行尋路演算法
因此只要開關殭屍豬人的 follow_range 就可以達到切換 攻擊 / 閒晃 模式的效果

另外這個做法會有一個小問題
強制把憤怒殭屍豬人的 follow_range0 雖然會讓他進入閒晃模式,但是他偶爾會朝玩家抽動一下(就只是一下,不會一直追著跑)
關於這個現象我只能歸因於他的 DNA 忍不住動了一下,反正影響不大就放著不管了

那麼最後下面的友善生物 C 到底要選誰呢?
為了讓熔岩史萊姆可以碰到人,這個生物自然是越矮越好,讓我們翻開碰撞箱大小列表
最矮的小海龜連半磚都走不上去所以不行,小兔子是用跳的也不行,第二矮的鱈魚更不用說
而小雞、小(山)貓、河豚同為第三矮的生物
河豚自然不必說,小雞掉落時會有緩降效果所以也不行,最後自然就選擇我們的小山貓
因此我們最終的騎乘組合是長這樣
     

這下終於可以擺脫怪物霸凌惡夢,又把每隻怪都加上碰撞傷害了,可喜可賀
至於怪物頭上血條會被埋在模型裡面看不到的問題,這又是之後的內容了



分工設計
確定了我們的騎乘怪物組合後,還得讓他們各司其職
此時各自負責的項目大概可以拆成
殭屍豬人
  • 模型顯示
  • 設定憤怒對象
  • 切換 閒晃 / 攻擊 模式
  • 跟小山貓同步面向 (避免發生 雙軌飄移 面向旁邊移動的狀況)
熔岩史萊姆
  • 扁人
  • 詛咒術、降魔咒等等傷害減少的設定
  • 承受玩家攻擊的碰撞箱判定 (玩家攻擊是以 dx dy dz 來抓範圍,碰撞箱有重疊到就會抓到)
  • 怪物死亡但還沒消失(動畫演出中)時停止攻擊 (開啟 NoAI)
小山貓
  • 移動速度控制
  • 血量、經驗值、防禦力、擊退等等原本殭屍豬人負責計算的數值
  • 判斷模型狀態(靜止、移動、跳躍、受擊)並同步資訊給殭屍豬人做對應模型顯示
這些功能幾乎都是原本就有的,只是現在要更換執行的目標而已
值得一提的是:如何讓他們同步一些資訊?

傳統的編號配對法自然不可或缺,但每次使用這個做法,就意味著要執行 怪物數量^2 的實體搜尋
假設有 100 隻怪物要把小山貓的模型狀態同步給殭屍豬人
就要執行 100(小山貓數量) x 100(殭屍豬人數量) = 10,000 次的實體搜尋
預設指令上限也才 65535,儘管上限可以調整,但 10,000 聽起來不是個小數字對吧

於是這邊我要來介紹一套把 O(N^2) 降到 O(N) 的做法
在介紹這個做法之前同樣要先理解幾個性質

實體順序
實體選擇器中 sort 的選項有一個 arbitrary (同時也是沒有指定 sort 時的預設值)
字面上的意思是隨意,聽起來有點像隨機的意思,實際上是指該實體在實體列表中本來的位置
而實體列表的順序其實就是實體出現的順序

騎乘怪的生成
生成騎乘怪的順序是採用 DFS 的邏輯,由下往上依序生成
舉例來說,下面這組騎乘怪的生成順序為 A -> B -> D -> E -> C
D E
\ /
 B C
 \ /
  A

結合上面兩個特性,我們可以知道騎乘怪的各個部位在實體列表內是依照一定順序緊密排列的
上述例子當 @e 不指定 sort 的時候,必定會依序抓到 A -> B -> D -> E -> C,且不會穿插其他實體
換成我們的騎乘怪結構就是 小山貓 -> 殭屍豬人 -> 熔岩史萊姆

假設我們現在有個 function funcA
# 除了小山貓外,顯示當前 #TEST 的 number 分數
execute if entity @s[type=!ocelot] run tellraw @a {"score":{"name":"#TEST","objective":"number"}}

# 小山貓把 #TEST 的 number 分數設成 123
execute if entity @s[type=ocelot] run scoreboard players set #TEST number 123

# 殭屍豬人把 #TEST 的 number 分數設成 456
execute if entity @s[type=zombified_piglin] run scoreboard players set #TEST number 456

當我們執行 execute as @e run function funcA
必定會依序顯示 123(殭屍豬人) -> 456(熔岩史萊姆)
無論有幾組騎乘怪,都會以組為單位,每組依序顯示上面的內容

也就是說小山貓計算出來的數據,我們可以透過這種方式存到 計分板 storage 內,讓殭屍豬人熔岩史萊姆同步資料
這樣一來假設同樣有 100 隻怪物,要讓小山貓同步資訊給殭屍豬人只需要 100(小山貓數量) + 100(殭屍豬人數量) = 200 次實體搜尋就能達成
(要排除熔岩史萊姆的話,只要把山貓殭屍豬人加到同一個 實體TAG 內,使用 @e 時增加 type=#實體TAG 的設定即可)

當然這個作法並不是萬能的,顯而易見的問題就是,順序較後面的實體沒辦法同步資訊給前面的
也就是只能單向由小山貓同步給殭屍豬人熔岩史萊姆
要想反向同步資訊還是得用老方法編號配對

再來是這個做法使用上要特別小心,例如當小山貓沒有資訊產出時,那些 記分板storage 還是要做設定,讓殭屍豬人知道沒有要同步的資訊,而不是同步到上一次不知道誰產出的資訊

最後慣例來看一下新版 AI 的效果吧!



終於趕在連假最後一天打完這篇了
這篇的內容特別長,主要是想講騎乘 AI 的邏輯跟資訊同步的最佳化
然後有在關注目錄的應該會發現 objmc 又被往後延啦!
沒辦法最近進度就還沒搞到 BOSS 模型,所以一直沒機會摸熟
再加上遇到問題靈光一閃又有值得分享的解決方式,所以又生出了新的主題

不過接下來馬上就會著手 BOSS 模型製作,所以 objmc 應該不會再被延後了
最後祝大家新年快樂!

慣例徵人,不用擔心技術不夠好,只要願意溝通蓋東西就歡迎加入我們!
# 希望今年能把 101 做完 ......
14
-
LV. 43
GP 2k
16 樓 貓狗喵 kevin22152
GP15 BP-
第十四步:進階 shader - 楓谷式的受傷效果
在 Minecraft 中受傷的無敵時間只有 0.5 秒,然而楓谷的無敵時間長達 1.5 秒左右
也就是說被怪物卡牆角之類的情況下,在 Minecraft 扣血的速度會是楓谷的 3
雖說 MapleCraft 的 HP 是記分板數值,要設定較長的無敵時間很容易,但會變成有受傷特效 (角色變紅螢幕晃動受傷音效) 但沒傷害的情況,這點從隱身術就可以看出來

其中受傷音效改個音效檔案就能解決,楓谷本身也沒有受傷的音效,所以超綠版本我就拿掉了
剩下的角色變紅螢幕晃動的效果在 1.16 時代還是無解問題,然而現在有了 core shader 這項工具,是時候來處理掉這些玩意兒了



角色變紅
首先從比較簡單的開始下手,照慣例從上次提到的 shader list 中找到我們要改的東西
從列表中可以找到,負責玩家渲染的是 rendertype_entity_translucent 這份 shader
既然我們是想把顏色拿掉,首先就從 fsh 檔開始下手,看看那層顏色是怎麼套上去的

找到那份 code 打開後馬上就看到一個名稱非常可疑的變數 overlayColor
取完材質對應的顏色後,會先跟這個變數做混色才做顯示
心動不如馬上行動,把這個混色的操作註解掉,果然被打就不會變紅了

螢幕晃動
其實我一開始沒有想特別處理這個東西,只是想起楓谷被打會有黑白閃爍的效果,想要把 Minecraft 變紅的效果替換掉而已
結果做一做突然想到,欸啊正交投影都可以做了,這東西應該有辦法弄掉吧?
(而且我測試正交投影時發現,他是沒有受傷晃動效果的)
於是乎就開始了我拿掉晃動的通靈之路

先說我這方面的背景知識是不太夠的,很多東西都是各種亂試才隱約理解他的意思 (能做出成果也是有運氣成分在)
如果有這方面的專業人士再麻煩幫忙補充正確的觀念

在嘗試亂改東西之前,要找到我們的切入點
這邊先讓我簡單介紹一下物體怎麼算出呈現在我們畫面上的樣子

首先是物體的位置、旋轉、縮放比例,決定它放在這世界上的哪個位置 → Model to World
再來要算出我們的眼睛相對於世界的視角與位置 → World to View
最後把我們眼前這堆 3D 的東西投影到 2D 的畫面上 → View to Projection

這三個東西都可以透過矩陣來表示,只要把物體的 3D 座標依序經過這些矩陣計算,就能一步步轉換成我們螢幕上的 2D 座標,這邊建議可以看這兩部影片解釋得比較清楚
(想知道這些神奇的矩陣怎麼來的,可以看這個網頁的介紹)

其中 Model to WorldWorld to View 在 Minecraft 中被整合成了 ModelViewMat
2023/02/26 修正:
在 Minecraft 中的 ModelViewMat 其實平常沒有作用,只有在 UI 介面顯示時做特殊處理
下面 code 看到的 Position 本身其實就已經是 View 空間的座標了
因此幾乎在所有 vsh 檔中都可以看到這麼一行 code
gl_Position = ProjMat * ModelViewMat * vec4(Position + ChunkOffset, 1.0);
到這邊就跟前面科普的知識對上了

那麼究竟要從哪邊下手呢?
從上面的邏輯可以知道,產生晃動效果的肯定是 ProjMatModelViewMat 其中之一
這裡決定先拿 ProjMat 開刀

由於不確定這些變數的定義是不是跟我們想的相同,先拆解上面正交投影的作法
可以發現他的正交投影矩陣是我們所知矩陣的轉置矩陣
為什麼是用轉置矩陣我也不清楚,有人知道的話再麻煩補充說明一下
除此之外,其他矩陣的內容原則上跟我們認知的相同

那麼是什麼地方產生旋轉的效果呢?
這邊我毫無頭緒,想說照著透視投影矩陣中,不為 0 的部分隨便填些常數或原本的值進去,然後


沒錯,受傷晃動就這樣沒了
雖然我也不知道發生什麼事,反正我要的效果已經達成了,好欸收工!

沒有啦為了寫製作紀錄,我還是稍微研究了一下到底發生了什麼事
簡單來說畫面搖晃是透過 ProjMat[0][1] 跟 ProjMat[1][0] 的值控制的

我後來又找到一篇介紹投影矩陣怎麼來的文章:OpenGL投影矩阵(Projection Matrix)构造方法
從這篇文章可以看出 ProjMat[0][1] 跟 ProjMat[1][0] 會讓物件的 X 跟 Y 座標互相影響 (要注意我們的是轉置矩陣)
已知原點是畫面正中心,且 X 軸與 Y 軸都被標準化成 -1 ~ 1 的範圍
假設 ProjMat[0][1] 是 1.0ProjMat[1][0] 是 -0.5,大約可以畫成這樣的關係圖
(黑點是原始位置,灰點是受這兩個變數影響位移後的位置)
只要帶入畫面的長寬比,就能透過控制這兩個變數達成旋轉畫面的效果
而這就是 Minecraft 實現畫面晃動的原理
因為標準透視投影矩陣沒有這兩個變數的值,所以當我們照著填空的時候,自然就沒有晃動效果了

最後就是把這套修改後的透視矩陣套用到各種 shader 裡面了
不過如果直接全部套上去的話會發現 UI 介面變得亂七八糟
其中的原因我也沒深入研究,不過大概是因為 UI 介面使用的不是透視投影,而是正交投影處理吧 (不然在畫面邊緣應該會有變形的效果)

這邊我是參考上面正交投影的作法,把 UI 介面排除,讓他們套用原始的 ProjMat
Onnowhere 的 vsh_util 中就有判斷是否為 UI 介面的功能,感謝前人的努力

鎖定 FOV
先別急著把上面的矩陣知識從你的腦袋移除
讓我們看向 ProjMat[0][0] 與 ProjMat[1][1] 這兩個變數
透視矩陣的這兩個值分別代表了 FOV (視角廣度) 寬與高的影響
相信大家都知道 Minecraft 中移動速度的改變會影響到視角廣度,也就是在這邊影響畫面的顯示

如果有乖乖看規則的人應該知道,我都會要求玩家關閉視角廣度效果吧
因為施放技能時要限制玩家移動,會給玩家很高的緩速,玩家視野就會忽大忽小的很不舒服

我在此宣布:不會再有人沒調設定又哭說畫面很暈了

既然上面說那兩個變數是 FOV 對畫面的影響,那我把它填常數進去,是不是就不會受影響了?
直接填常數會讓長寬比怪怪的,所以要讓其中一個乘上原始 ProjMat[0][0] 與 ProjMat[1][1] 的比例
如此一來就有不受玩家設定、移動速度影響的 FOV 了

2023/03/12 補充
在 1.19.4 更新了受傷的畫面晃動公式,讓畫面根據攻擊的方向去傾斜
大致邏輯還是相同的,但是這次多了畫面縮放的效果,所以原本那套沒辦法完美關閉晃動效果了
為了解決這個問題我把各種參數調來調去,最後發現把 ProjMat[2][3] 固定成 -1 的話,就只會剩下畫面長寬比例改變的拉伸效果
由於我們沒辦法知道原先的畫面比例是多少,要避免這種拉伸效果只能把長寬比固定
於是我抓了最常見的 16:9 來寫死 ProjMat[0][0] 與 ProjMat[1][1] 的值
雖然當畫面比例不是 16:9 時會有變形的效果,但是正常情況應該是感覺不太出來的

楓谷式的受傷效果
把角色變紅跟畫面晃動拿掉後,玩家被攻擊的回饋變得很不明顯,最後就來把楓谷那套搬過來吧
楓谷的受傷特效是黑白閃爍,並且會持續到無敵時間結束為止
所謂的黑白閃爍其實就是高頻率 (約每秒 15 次) 給玩家加上半透明黑色遮罩

那麼怎麼在 Minecraft 中做出這種黑色遮罩的效果呢?即使想改玩家變紅的 shader 效果,持續時間也只有半秒,跟無敵時間對不上

這邊讓我們請來我們的老朋友:生物發光後製特效

發光其中一個最大的特色就是,他會自己描繪發光實體的輪廓 (連身上裝備都包含在內)
因此我們可以很輕鬆地找到要套黑色遮罩的部分

大部分原理我在之前那篇文章都解釋過了,這邊只補充一下 final 這個 buffer 的內容
原本文章中提到 final 是最後顯示的畫面內容,實際上在發光後製特效的 shader 中,他一開始就包含了發光的顏色資訊,也就是說這個 buffer 中 alpha 不為 0 的部分,就是發光的範圍
如果想避免誤判,還可以把玩家隊伍加上指定顏色,然後只找特定顏色的發光範圍

由於這個閃爍動畫是規律的動畫,可以用 Time 這個參數控制就好
用 mod 算出一個想要的頻率,定期在發光範圍套上黑色遮罩,寫回 final 這個 buffer 即可

當然一些發光的老問題,像是會穿牆看到閃爍特效之類的還是會有,不過我覺得不會太明顯就不管吧
照慣例最後來看一下效果



終於打完這篇了,每次寫 shader 內容深度都要琢磨很久
雖然已經快要開工了,不過這邊還是祝大家新年快樂~

兩年前的現在正準備發布超綠體驗版,不過今年想看到 101 還得要一段時間啊
如果你也想貢獻一點心力,不限能力,沒有進度壓力,來加入我們的製作團隊吧!
15
-
LV. 43
GP 2k
17 樓 貓狗喵 kevin22152
GP15 BP-
注意!
本篇內容可以被未來 1.19.4 的新功能 100% 完美取代
有機會之後更新我再發一篇文介紹新的做法
後來發現新功能的高度設定不會更改旋轉中心,所以這篇調高度的部分還是有用的

第十五步:進階 shader - 自訂血條顯示高度
如同第十步中提到的,玩家頭上血條的顯示是用實體名條達成,這點在怪物上也不例外
然而怪物的模型有大有小,儘管史萊姆有不同大小的碰撞箱,用他的名字來顯示血條可以有不同的顯示高度,還是免不了多數怪物模型會把血條遮住的情況

那麼有什麼辦法可以解決這個問題呢?
如果是舊時代的做法,可能會想用連閃 tp 把名條實體黏在怪物指定高度的位置
但是在這個有 core shader 的時代,讓我來介紹一個不用連閃也不用額外實體的做法吧
(現代問題需要現代手段.jpg)

如何傳值
既然提到由 shader 控制顯示位置,自然要有參數能夠給 shader 知道
而名條能夠自由設定的參數是什麼?當然就是文字的顏色啦!
在開始對 shader 下手之前,先讓我們把前置作業搞定
一如往常我們只能改 RGB 三種顏色,總共 16,777,216 種組合,不過我們也沒必要用這麼多
這邊只選用藍色的數值當高度使用 (0 ~ 255)

那要怎麼把對應高度的顏色套到名條上呢?又不能像藥水寫 CustomPotionColor 進去
當然你可以每次要刷新名條,才根據實體設定的高度數字,窮舉對應的顏色
但是有更簡單的方法
只要利用 JSON 繼承結構,就可以事先把帶顏色的空字串存在怪物身上
需要的時候從怪物身上 nbt 直接取值繼承就好

例如我想設定高度是 14,對應的顏色就是 #00000E
我就在怪物設定裡面放一個指定顏色的 JSON 空白文字字串 '{"text":"","color":"#00000E"}'
之後要設定名條時,都用 [{"nbt":"上面變數的路徑","entity":"@s","interpret":true},(原本要顯示的血條)]
就可以輕鬆套用指定高度對應的顏色了

shader 套用高度設定
照慣例讓我們找到要改的 shader 檔案
由於要改的是名條的位置,所以要改的是 rendertype_text.vsh 這個檔案
一如往常的顏色是由 Color 這個變數設定

但是在套用顏色計算高度之前,我們要先判斷什麼情況要做這個處理
否則的話所有文字顯示都會吃到這個高度設定飛的老遠
而最簡單的判斷方法,就是在圖片塞特定顏色來給 shader 判斷

怪物淡入那篇文章中有稍微提到,材質圖片的資訊是存在 sampler 裡面的
實際上你會看到 sampler 不只一個,通常材質圖片是存在 Sampler0 這個變數中
這時你左顧右盼沒看到他的身影,原因是他只在 fsh 中被拿來顯示
我們在 vsh 要使用記得要補上他的宣告
uniform sampler2D Sampler0;

接下來就是決定我們要判斷哪個點的顏色
還記得 vsh 是處理每個頂點的資料吧?因為文字顯示是一個平面,也就是四個角各一個頂點
所以這邊的 shader code 會由那四個角各執行一次
其中 UV0 這個變數就是該頂點對應到的材質貼圖位置

由於我們不知道材質的大小,我們能確定的點就只有頂點的像素
因此最簡單的作法就是把材質圖片四個角落的點都改成判斷用的顏色
為了避免跟其他東西撞色,請避免用一些常用的顏色,如黑灰白這些 RGB 都相同的顏色
有需要的話也可以加上一些透明度差異來區分,不過大多數圖片編輯器的透明度只能用百分比,不能用數值的 0 ~ 255 來計算,所以比較難控制一點

把材質圖片加工完成後,就可以在 vsh 中透過
ivec4 vertexTexel = ivec4(texture(Sampler0, UV0) * 255);
取得頂點顏色的 RGBA,接下來就是如何讓顯示位置提高了

在上一篇中有提到 ProjMatModelViewMat 的功能
vsh_util 中有一個 getWorldMat 的 function 可以讓我們以世界座標做位移
使用方法如下,將 WorldMat 乘以我們要在世界座標中位移的量:
mat3 WorldMat = getWorldMat(Light0_Direction, Light1_Direction);
gl_Position = ProjMat * ModelViewMat * vec4(Position + WorldMat * vec3(0.0, 1.0, 0.0), 1.0);
備註:
說實話這邊重新看過一遍後我也搞不太懂 ModelViewMat 到底是指什麼了
原本我認知上 Position 是物體空間位置,而 ModelViewMat 會把他先轉成世界再轉相機空間
WorldMat 應該是把世界空間轉換成物體空間的矩陣,也就是 Model to World 的反矩陣

然而這個 function 的註解卻說這是把相機空間轉換成世界空間的矩陣???
如果有人知道這東西到底是什麼意思再麻煩幫我補充解釋一下

2023/02/26 補充:
感謝 Peter 協助釐清 ModelViewMat 的定義
這邊 ModelViewMat 平常其實沒有作用,只有 UI 介面上的東西顯示才會用到 (確切做了什麼我也不清楚)
Position 本身就是相機空間的座標,所以 getWorldMat 是將相機空間轉到世界空間的矩陣沒有問題

這邊用到的 Light0_DirectionLight1_Direction 在這個 shader 同樣沒使用到,要手動宣告
而且 rendertype_text.json 那邊也要補上對應的 uniform 宣告
接下來就能結合上述功能,在世界座標中把血條顯示高度提高了
記得 vertexColor 要特別處理一下,不然他就會套用我們為了調整高度設定的顏色

另外背景海苔條其實會有顯示文字的殘影,所以也要同步修改 rendertype_text_see_through.vsh
這邊要處理的東西就很簡單了,只要該點顏色符合判定顏色,就直接讓輸出顏色變成透明的就好了
而這邊要影響輸出透明度最簡單的方法就是改 vertexColor,畢竟這東西會在 fsh 跟輸出顏色相乘
所以只要改這個變數的 alpha 值,就可以等比例的控制輸出顏色的透明度

如此一來就可以直接用底下的貓的名字來顯示血條,並且指定任意高度了

上述內容比較繁雜,想看 code 的可以直接看下面連結



加碼傷害數字淡出 Ver. 1
既然我們能控制名條的顯示位置,那控制透明度肯定不是什麼大問題
讓我們回顧這系列第一篇文章,也就是傷害數字的部分
雖然當時沒有特別提,但是楓谷的傷害數字其實除了往上飄以外還有淡出效果

另外當時為了做數字往上飄動的效果,是採用道具實體設定 Motion 而非藥水雲
也就導致當道具實體生成在方塊內時,會被擠壓而有劇烈抖動或是噴飛(以及變黑)的情況
這邊就來一口氣把兩個問題解決掉吧!

首先來做出透明度的控制,跟高度控制一樣,我們可以傳一個參數來代表透明度
又或者是你可以傳一個參數當作進度百分比,畢竟數字的顯示時間只有 1 秒,只要 20 個數字就能代表 20 tick 的生命週期
不過這邊先選用前者來增加更多的彈性

為了方便計算,這邊就用 RGBG 來當透明度的表示
接著同樣找到上面那份 rendertype_text.vsh,同樣指定一種判定顏色來給傷害數字使用
然後就是決定藍色如何控制高度,以及綠色如何影響 vertexColor 的 alpha 值
這些操作上面都已經做過一次了,這邊就不再細講

最後就是讓傷害數字套用對應的高度與透明度
這邊沒有什麼好做法,只能讓顯示實體更新名字時,依據當前分數設定對應的顏色 (也就是窮舉啦)
套用顏色的方式就跟一開始生物設定血條高度的方式相同,同樣不再贅述

另外因為不需要真的用 Motion 來控制飄移,所以直接用藥水雲來顯示就可以了
這樣一來一口氣就解決了淡出跟卡牆兩個問題
然而你會發現,向上飄的效果變得不這麼絲滑柔順了......

加碼傷害數字淡出 Ver. 2
之所以飄動效果會變得比較卡,是因為現在的顯示位置是每 tick 更新一次,每秒只會更新 20
原本的 Motion 會每個 Update 做插值顯示,也就是更新頻率取決於你的 FPS

那麼有什麼辦法讓我們的數字套用 FPS 的更新頻率去顯示呢?

這個部份其實是從下一篇 objmc 的技術解析而來
在 core shader 中有一個 uniform 變數 GameTime
這玩意的定義是什麼呢?答案是遊戲中的 game tick / 24000,範圍從 0 ~ 1 代表遊戲的一天
因此我們只要把 GameTime 乘以 24000 就可以得到遊戲中的 tick 數,並且是精細到小數點的
(由於 /time query gametime 得到的是累積時間,要除以 24000 取餘數才會跟這裡相同)

不過要注意 tick 是由 server 端計算的數值,然而 GameTimeclient 端的資料
所以 tickGameTime 有可能發生不同步的問題,為此 server 會持續跟 client 同步 tick 的數值
如果 clientserver 誤差過大時,這個值就會被修正,也就會發生數值跳動的情況
(不過單人基本上看不到就是了)

讓我們先忽略怎麼控制動畫開始的時間,先想個辦法讓動畫循環播放
也就是我們要有一個數值持續在我們指定的區間循環,其實就是取餘數的概念
假設我們的動畫持續時間是 20 tick
我們就把 game tick 除以 20 取餘數,就會得到 0 ~ 19 循環的數值
mod(GameTime * 24000, 20.0)

接下來我們要決定動畫從哪邊開始播放,可以透過給那個數值一個 offset
回到上面的例子,假設現在的 game tick38763
此時那個控制動畫的數值為 (38763 % 24000) % 20 = 3
如果我們想讓他現在從 0 開始播放,我們應該把這個數值扣掉 3 對吧
也就是說我們在遊戲中用 game tick 去做同樣的取餘數計算,就可以得到我們要扣掉的值

得到要扣掉的值後,就是老方法用顏色傳進 shader 讓他知道了
同樣是用窮舉找出數值對應的顏色,不同的是這次不用每 tick 更新,只要生成的時候計算一次 offset 即可,無論動畫流暢度還是效能都大幅提升

然而!

如果你直接套上去會發現,動畫的開頭跟結尾有可能會有跳動的情況
因為 GameTime 紀錄的是小數,當我們用遊戲中的 tick 推算 GameTime 時,可能有 1 tick 的誤差
所以開頭可能是從上個動畫的結尾開始,結尾也可能跳到下個動畫的開頭
為了避免這個誤差導致的跳動情形,我們的動畫要做成頭尾可以銜接的循環動畫

如果本身就是循環動畫的沒什麼問題,但是像我們這種往上飄到頂又直接從下面出來的傷害數字,要怎麼處理呢?
其實很簡單,因為數字是憑空冒出來又憑空消失的,我們可以讓頭尾都是透明的狀態
頭尾各保留 1 tick 的透明狀態,這樣不管動畫提前還是延後,頭尾都會在透明的區間內
這樣就成功讓我們的動畫變成另類的循環動畫了

最後慣例看一下成果



這次的內容真的是超長一串
原本只打算寫血條高度設定的部分,但是傷害數字優化跟這個功能太接近了
而且他的原理跟後面 objmc 有關,放在那之後講又感覺不太適合,結果就變成這堆又臭又長的東西了
大家如果覺得內容太多就自己切分一下各個主題慢慢看吧

最近剛把 BOSS 模型捏完,意思是功能面已經幾乎完成了,然而場景的部分進度還是 0
有興趣又有閒的來幫幫忙吧 _(:3 」∠ )_
15
-
LV. 43
GP 2k
18 樓 貓狗喵 kevin22152
GP16 BP-
第十六步:objmc,參戰!
眾所周知,原版 Minecraft 的模型限制是出了名的多
除了旋轉角度以 22.5 度為單位外,還一次只能旋轉一個軸
另外還有大小限制,除非使用 1.19.4 的 display_entity,否則模型的極限大小就差不多 7 格左右
而本次目標 101 組隊任務的 BOSS 就是會超標的那種

要想突破這些限制就只能搬出 shader 黑魔法來解決了
而當中最著名的工具就是由 GodLander 開發的 objmc
這篇就讓我來簡單介紹一下它的用法跟基本的原理吧!
▲ 之前 shader 入門篇貼過的這張圖就是 objmc 範例的動畫模型



什麼是 objmc
objmc 顧名思義就是把 obj 檔轉換成 Minecraft 模型的工具
而 obj 檔是常見的 3D 模型輸出格式之一
意思是說你可以透過 objmc 把一般精細的 3D 模型匯入到 Minecraft 中
同時也支援多個 obj 檔組合成的動畫模型,並且是以 FPS 的頻率播放,也能指定當前動畫時間

如何安裝
objmc 的網頁,找到綠色的 code 按鈕,點下去後選 Download ZIP 就會開始下載
點開下載的 zip 檔,把裡面的資料夾拖出來,會看到裡面有 objmc.pyobjmc 資料夾
其中 objmc 資料夾就是專用的資源包,裡面包含一些範例模型與最重要的 shader 檔
如果要在你自己的資源包使用,只要把 shaders 資料夾與 LICENSE 貼進你的資源包即可

接著為了執行 objmc.py,必須先安裝 python 與其 Pillow 套件
原則上 python 直接去下載頁面挑最新的下載就行,點 Download 後拉到下面,看你作業系統點對應的 檔案下載即可 (大部分應該都是 Windows installer (64-bit))
點開後勾選 Add Python.exe to PATH 並點擊 Install Now 即可

等它安裝完成之後,對 Windows 鈕點右鍵 → 執行 → 輸入 cmd 按確定
在跳出來的小黑窗中輸入 pip install Pillow
完成後理論上就能執行 objmc.py 這個檔案了

基本操作
首先是左上角 Select Objs 的按鈕,點下去就可以選擇 obj 檔案,如果一次選擇多個,則這些 obj 檔會被視為一組動畫,依照播放順序從上到下排列
接著是右邊的 Select Texture 按鈕,就是選擇 obj 檔對應的材質貼圖,由於 objmc 只支援使用一張貼圖,如果有使用到多張圖片的必須組合成一張大圖來使用
下面一點的 Offset 就是模型的位移,不過我建議直接在模型裡面設定就好,這邊就不要動它
再下面的 Scale 則是模型縮放比例,同樣建議在模型裡處理不要動它
然後 No Shadow 如果打勾,遊戲中模型就不會有不同面陰影不同的色差

稍微往下跳一點先看到 Auto Rotate 的部分
Auto Rotate 是模型要不要跟著面向旋轉
Off 的話無論穿戴的實體面向哪邊,模型都不會旋轉,永遠面向同個方向
Yaw 的話只會水平旋轉,實體抬頭低頭是不會影響顯示的 (這個滿好用的)
Pitch 的話則是垂直旋轉,跟 Yaw 相反只有抬頭低頭會有效果,不會水平轉動
Both 就跟一般模型一樣兩個軸都會轉動
不過如果是盔甲架的第三種旋轉軸,這個就沒辦法處理,必須用 Color Behavior 自己計算

剩下的東西都是跟動畫控制相關的功能
Frame Duration 是每個 obj 模型要停留多少 tick
Easing Method 是各個 obj 模型間的銜接方式
None 的話就像幻燈片一樣時間一到就跳到下個模型,完全沒有銜接
選其他的則會以 FPS 為頻率自動計算兩個模型切換過程,在這個時間應該長什麼樣子
至於那三個有什麼差別,就留給大家自己體驗並選擇適合的方式了
Auto Play 如果有勾選,動畫就會自動循環播放
Color Behavior 由左到右對應藥水顏色的 rgb
x 代表水平方向的旋轉
y 代表垂直方向的旋轉
z 代表左右旋轉 (就是歪頭燦笑那個旋轉方向)
各自的值從 0 ~ MAX 代表 0 ~ 360 度,至於 MAX 是多少下面再說
(這個旋轉會跟 Auto Rotate 的效果疊加)

t 代表控制動畫播放位置的參數,以第一個畫格為 0 tick,詳細計算公式如下:
(([/time query gametime] % 24000) - (想跳到哪個 tick 的時間點)) % (動畫總長的 tick 數)
注意如果三個顏色都選擇 t 的話,上面 Auto Play 就會自動失效

o 代表控制模型的顏色,由於只剩一個參數可以控制顏色,沒辦法像原版模型那樣愛染什麼顏色就染什麼顏色,這邊使用的是 HSV (or HSL?) 的 hue 值,至於基底是什麼數值我也懶得研究,反正 0 就是紅色,到 255 轉一圈就對了

至於上面說的 MAX 值,則是看你選了幾個顏色用來代表該變數
例如選擇 x y x 的話,紅色跟藍色就是代表水平旋轉的值,實際值為 (紅色 x 256 + 藍色),此時的 MAX 值就是 65535
如果選 x x x 的話就是三個顏色都代表水平旋轉,實際值為 (紅色 x 65536 + 綠色 x 256 + 藍色),此時的 MAX 值為 16777215

都設定好並選好檔案後,就可以點 start 進行輸出了
輸出的 模型 json 與 材質 png 都會在執行 objmc.py 的資料夾出現,按照需求放在對應的路徑即可
要注意設定的 png 路徑同時也是模型讀材質的路徑,記得把圖片放到那個位置

還有很重要的是,只有藥水或皮革裝這些可以染色的道具才能使用 Color Behavior 的設定
否則那些數值永遠都會被當成 255 來看

編輯 obj 檔的工具
如果你只是想要做比較大的模型,直接使用 Blockbench 就能輸出 obj 檔
輸出之後再使用 objmc 的 Scale 功能調整大小即可
如果你想要輸出動畫,可以使用 OBJ Animation Exporter 這個插件
他可以幫你把每個畫格輸出成一個 obj

而如果你想做的是精細的模型,常見的作法是使用 Blender 這套開源工具
只是 Blender 的入門門檻真的有點高,我到現在也只是學到一些基本的東西,可以把模型硬湊出來的程度而已,當然也不可能在這邊做 Blender 的教學,要靠各位自己摸索
不過這邊可以分享一個 Blender 頂點動畫插件的教學,相信在做動畫的時候會很方便
實際用 Blender 做下去可能會踩很多雷
例如內外面設定相反,導致 Minecraft 中看起來像破圖
或是 UV 不小心超出邊界一點點,就讓整個材質顯示大爆炸之類的
總之有問題可以在下面留言提問,我知道的可以回答,不知道的就留給大家討論
至少比自己一個人通靈還好 _(:3 」∠ )_

使用 Blender 輸出 obj 檔時,記得勾選輸出動畫的選項,並設定動畫畫格數量
之後 Blender 會把每個畫格輸出成一個 obj(記得輸出到指定資料夾,因為他會爆出一堆檔案)
每個畫格都會輸出 objmtl 檔,我們只需要用到 obj 檔而已,可以直接把 mtl 檔移除

生成出動畫需要的一堆 obj 檔後,就可以回到 objmc
選擇剛剛輸出的那堆 obj 檔,再根據每個畫格停留時間去設定 Frame Duration 即可

使用限制
  1. 會覆蓋部分 core shader 的內容,有做相關修改的必須自己想辦法把它們合併在一起
  2. 因為有調整 core shader 所以跟光影模組很容易發生衝突,基本上要禁用 optifine
  3. 製作動畫時,每個畫格的頂點數量與順序必須相同,也就是不能新增或刪除頂點
  4. 材質中若包含特定顏色,可能導致被誤認成 objmc 格式,但機率低到跟丁特有的比。就算真的發生了,只要把顏色換成相近的不同顏色就能解決
  5. 匯入 objmc 的材質不能是動畫材質,因為它會把整個動畫當成一張圖片加工再輸出
  6. 方塊模型會有頂點數量上限(取決於電腦硬體),超過時會導致遊戲直接當掉,但實體模型沒有這個限制 (一般 Minecraft 模型不太可能遇到是因為這上限很高,但如果你用 objmc 匯入一個又大又精細的模型,就不是不可能碰到了)

與 Animated Java 的優缺點比較
既然提到模型動畫,就不得不提也才正式公開一年左右的工具: Animated Java (以下簡稱 AJ)
板上也有一篇文章在介紹這個工具:【攻略】免模組製作實體動畫 Animated Java

去年我在做不朽之魂2的時候就是用 AJ 來處理其中兩隻 BOSS 模型,算是兩種工具都用過了
這邊就來比較一下兩者的優缺點,讓有需要的人選擇適合的工具吧

AJ 優點
  1. 跟一般模型一樣可以使用多個材質圖片,材質設定更自由 (也可以使用動畫材質)
  2. Model Variant 功能,可以在任意動作畫格中切換材質
  3. 任意動作播放過程強制切換動作,仍會有平滑轉換的效果 (不過視情況也可能是缺點)
  4. 可以設定時間軸事件,在指定畫格時間觸發特定指令,不必自己額外計時
  5. (未實裝)未來似乎會加上可以控制動畫播放速度的功能
AJ 缺點
  1. 效能低下,主要是使用了大量實體且連閃 tp (1.19.4 出了各種新功能後似乎有在做效能優化,但仍沒辦法跟 objmc 比,畢竟人家就只要一個實體而且什麼連閃都不用)
  2. 沒辦法使用縮放動畫
  3. 需要輸出資料包配合控制,有新增動作或改設定都要打指令重新安裝,很不方便
  4. 前置設定繁雜不易使用 (而且很多設定的意思很難看懂)
  5. 各節點模型仍有大小限制,儘管可以組合節點成一個大模型,但使用上很不方便。且雖然有提示框,但使用起來還是很難判斷有沒有超線 (未來配合 1.19.4 的優化可能會移除這個限制)
  6. 動作狀態的切換很麻煩,如果前一個動作還沒結束,必須先 stop 才能正常播其他動作

objmc 優點
  1. 可以無視 Minecraft 限制製作高度精細的模型 (不過要適度考慮一下跟背景會不會很突兀)
  2. 能夠製作頂點變形、以及 UV 移動的動畫
  3. 能夠設定只做水平旋轉,不用擔心生物抬頭讓模型角度變得很奇怪
  4. 操作直覺簡單,如同控制原版道具模型一樣
objmc 缺點
  1. 產出的材質圖非常的大,大量使用會造成資源包加載負擔 (可以透過減少頂點數與動畫畫格優化)
  2. 沒辦法只替換某個部件的材質,因為整個材質都被綁在一起
  3. 只能製作循環動畫,且計算的開始播放時間可能有誤差,要播放一次性動畫需要在前後保留靜止畫格,避免播到上個循環的結尾或下個循環的開頭
  4. 套在生物頭上顯示的話,生物死亡動畫需要特別處理,因為原版的側倒動畫沒辦法顯示

儘管各有優缺,但整體考量下來我比較偏好 objmc
主要效能的差異實在太大了,而且突破 Minecraft 模型限制真的無可取代
不過 Blender 真的很難學......

基本原理解釋
本篇的重點主要放在怎麼使用 objmc 上,所以這塊會簡單的帶過
畢竟如果要詳細分析,用一整篇也寫不完,對多數人來說會用工具就很足夠了
真的想知道細節的再自己看它原始碼,看不懂也可以在下面留言提問

在嘗試理解之前,要先建立一個觀念
正常製作 Minecraft 模型都是以 cube 為單位,每個 cube 必定都是有 6 個面的長方體
但是對 3D 模型來說,實際上基礎的單位並不是 cube 而是頂點,cube 只是一種頂點的組合
2 個頂點可以組成線,3 條以上的線可以組成面,而 6 個長方形的面就能組成一個 cube

而每個頂點除了位置外,還對應到了材質圖片上的某個點,也就是所謂的 UV 資訊
長方形的面有 4 個頂點,並且一個 cube 由 6 個面組成,也就是說 cube 其實是由 24 個頂點組成的
之前也介紹過 vsh 就是在處理頂點,你也可以清楚的看到它輸出位置 (gl_Position) 與 UV 資訊

而 objmc 在做的就是:攔截原始模型頂點資訊,替換成他算出來的頂點資訊

因此無論你的 3D 模型長什麼奇形怪狀都可以,因為它只在乎你有多少個頂點
由於每個 cube 都能產生 24 個頂點,因此它會幫你生成 頂點數 / 24 (無條件進位) 個 cube 的模型
然後把 obj 檔的各個頂點資訊用特殊的方式記錄在輸出的材質圖片中

接下來再把那些 cube 的材質對應到圖片上指定的位置
注意這邊對應到的並不是原始貼圖的頂點位置,只是某個帶有特殊資訊的像素點
這個點的顏色可以讓它計算出貼圖的最左上角在哪裡
如此一來就能根據它與左上角的距離,得知它是負責顯示第幾個頂點
而一些設定等等的資訊,也都在左上角固定的某幾個位置,因此只要知道左上角在哪就能讀取設定
該有的資訊都有了之後,就能把頂點資訊偷天換日,改成 obj 檔中某個頂點的資訊了

而動畫的原理,其實就是把好幾個 obj 檔的頂點全部依序塞進去
由於每個畫格的頂點數都是一樣的,只要預先紀錄好總共有幾個頂點,就能算出當前畫格要取的是第幾個頂點 (例如每個畫格都有 N 個頂點,第 X 個畫格的第 Y 個頂點其實就是第 X * N + Y 個頂點)
接著再透過我們上一篇提到的 GameTime 換算當前畫格的方式,就能做到動畫播放了


最後慣例來看看做出來的效果吧



最近都在用 1.19.4 的黑科技搞一些優化 (效能跟效果都有),一不小心就拖了一個月
距離這個主題出現在目錄都三個月去了,如果有人一直很期待 (會有嗎?) 的話先說聲不好意思
objmc 真的是很簡便又強大的工具,期望大家都能用好工具做出好東西

慣例徵人,有閒想幫忙的趕緊手刀加入我們 (尤其是建築 RRR)
16
-
LV. 43
GP 2k
19 樓 貓狗喵 kevin22152
GP10 BP-
第十七步:你有多猛?找出打我的怪物等級與命中率
說實話這篇本來是想刪掉的,因為用到的概念其實很單純
而且 execute on attacker 出現之後,我已經沒再用這個做法了
不過台灣這邊幾乎沒聽過有人提到這個東西 (雖然沒提到的好像多了去)
就讓我簡單介紹一下吧

注意:以下內容建立在 execute on attacker 還沒出現的前提下



對數值很敏感的人可能會發現,上次的超綠版本中,玩家是沒有迴避率設定的
其根本原因就是,玩家只知道被打了多少血,不知道是誰打的,自然也不知道怪物的命中率跟等級
沒有這些資訊就沒辦法計算玩家的 MISS 機率,所以就索性都不做了
  < 埋藏在資源包裡一直沒用到的素材

然而在攻擊公式中,玩家被攻擊的傷害其實也跟怪物等級有關
這就導致我之前測試東西的時候,堂堂 40 幾等的角色被綠水靈撞一下將近 50 滴
是可忍孰不可忍?這等級資訊我要定了!

那麼具體要怎麼知道攻擊玩家的怪物資訊呢?
這邊就要請到進度的功能,目標條件的設定
{
  "criteria": {
    "requirement": {
      "trigger": "minecraft:entity_hurt_player",
      "conditions": {
        "player": [],
        "damage": {
          "source_entity": {
            "type": "minecraft:zombie",
            "nbt": "{Tags:[\"special\"]}"
          }
        }
      }
    }
  }
}
以上面的例子來說,就是當玩家被含有 special 這個 tag 的殭屍攻擊時會觸發這個進度
不過這東西要怎麼拿來取攻擊者的等級與命中率資訊呢?

一個比較直覺的做法就是:對每個等級與命中個別設定一個進度
等級可能是 1 ~ 200,那就設定 200 個進度,然後 reward function 把對應等級紀錄在計分板上
不過這個數量聽起來就很可怕對吧?
除了製作麻煩以外,每次受傷都要檢查這麼多進度也是一種效能負擔

所以這邊要用另一種比較友善的方法:用二進位表示數字
二進位的世界只有 10 兩種數字,分別對應 (存在) 跟 (不存在) 兩種狀態
所謂的二進位數字其實就是 2 的各個次方項存不存在的意思
因此我們可以用 N 個進度是否存在,來表示 02^N - 1 的數字 (怎麼表示就自己看 wiki 吧)

舉例來說我們設定 3 個進度:lv1lv2lv4
分別判斷攻擊者身上是否有 lv1 / lv2 / lv4 的 tag
然後 reward function 個別把玩家的 攻擊者等級記分板1 / 2 / 4
接下來只要讓怪物根據等級,在身上塞對應的 tag,就可以在被攻擊時取得攻擊者的等級數值了
(由於我們只設定 3 個進度,所以只能判斷 0 ~ 7 等的範圍)

用這種作法只需要 8 個進度,就能判斷 0 ~ 255 的數值,比起全部窮舉要友善的多
不過記得在下次受到攻擊前要重置玩家身上的分數,否則取到的等級會一直被加上去



沒錯,這次的技術解說就只有這樣
尤其 1.19.4 之後,要抓攻擊者等級或命中率只要 execute on attacker 把某個全域變數設成自己的分數,之後再自己拿來用就好,又簡單又省效能

但是這個做法也不是完全就沒有存在價值了,因為進度其實還有很多種包含目標判斷的條件
例如 fishing_rod_hookedtame_animal、或是單純反過來變成 player_hurt_entity 的判斷
這些例子都不能用 execute on 來解決,這時候這篇文章的做法就能派上用場了

總之迴避率與被攻擊的等差傷害總算實裝上來了
這邊放個有展示到玩家迴避攻擊效果的影片
至於影片中可以明顯看出 MISS 還是有被擊退的效果
這個問題就等到下一篇 damage 主題來解決吧



最近終於開始有人加入製作群了,一直沒個影子的塔也終於有了外形
期待更多人一起加入,來把更多回憶中的畫面重現出來吧
10
-
LV. 43
GP 2k
20 樓 貓狗喵 kevin22152
GP9 BP-
第十八步:玩轉 damage 新世界
在 1.19.4 新增了 damage 這個指令,以及 damage_type 與相關 tag 的設定
不知道多少人已經敲碗這個功能敲了 10 個大版本以上了 (我當初甚至寫過建議,沒記錯的話是這篇)
儘管在 MapleCraft 中有自己一套傷害公式,這個功能仍能帶來全新的機制革新
這篇就讓我來分享 damage 相關功能的妙用吧

(備註:本篇不會詳細介紹語法與定義等等基本教學,只會著重在應用面上)



傷害免疫
本次更新將原版各種傷害類型都定義成了 damage_type 的資料包項目
同時也新增了傷害類型的 tag 用來判斷是否為 火焰、摔落、缺氧 等等的傷害
這也意味著我們可以把某種 damage_type 歸類成某種特殊類型的傷害

由於 damage_type傷害類型 tag 名稱有點像,以下分別用 類型標籤 代稱
其中 damage_type 是能讓 /damage 指令使用的傷害類型
傷害類型 tag 則是一種資料包的 tag,可以讓該 tag 中的 damage_type 具有該 tag 的特性

舉例來說:使用 tp 或騎乘時常常不小心讓玩家卡進牆裡導致窒息
這時候如果把窒息傷害類型寫進 摔落傷害標籤中,再用 gamerulefallDamage 關閉,玩家就不會受到窒息傷害類型
這種作法跟抗性不同,是直接免疫傷害效果,連受傷的特效都不會有

然而這玩意還有進階玩法
上面說的方式只能「全體」免疫某種傷害類型
然而還有一種做法能讓「特定對象」免疫某種傷害類型
沒錯就是利用 抗火 水中呼吸 (或皮革裝,但比較難控制) 這些可以免疫特定傷害標籤的效果
只要把我們想免疫的傷害類型,加入上述這些類別的傷害標籤中,就可以用上述效果免疫該傷害類型

當然這樣使用不是沒有限制
例如你想透過開關抗火來讓某些玩家免疫生物攻擊類型,但是系統本身也需要抗火來免疫火焰傷害類型之類的,此時關閉抗火就會失去免疫火焰傷害類型的效果

不過山不轉路轉,既然我們可以把生物攻擊類型寫進火焰傷害標籤,自然也能把火焰傷害類型寫進其他傷害標籤
假設你的火焰只是造景,抗火是為了避免玩家誤觸燒起來
同時你的地圖已經用 gamerule 關閉了摔落傷害
那麼你只要把原本的火焰傷害類型移到摔落傷害標籤中,就等於永久免疫火焰相關傷害了
而原本的抗火就能拿來做我們的生物攻擊免疫開關

類似的邏輯也可以用在 水中呼吸皮革裝 上,只要限制一部分功能就能很靈活的應用相關特性
儘管之前已經做了把受傷晃動拔掉的效果,現在利用這個功能,在我自訂的無敵時間內,連角色被攻擊的動作都不會有

玩家 Motion 方案
同樣被大家敲碗許久,然而只有 damage 指令成功加入遊戲,控制玩家動能的 motion 還是沒有消息
在之前版本升級優化那篇番外中也有提到,以往控制玩家動能的方式,要嘛效果不好、要嘛很難控制、不然就是很耗效能

但是 damage 指令出現後,這個問題已經可以很好的解決了

那麼具體 damage 指令是怎麼達成 motion 效果的呢?
簡單來說就是番外篇提到的 擊退效果 的優化
只要使用 damage 指令中的 by 參數指定攻擊者,就能讓 被攻擊者 根據 攻擊者 的相對位置做擊退
(同時這個也解決了 怪物技能攻擊玩家沒有擊退效果的問題)

由於不再需要煩惱誰來攻擊玩家、會不會攻擊到別人,現在的作法可控性變得相當的高
且攻擊者可以使用單純的 marker 來做,效能上也不需要太擔心
缺點是如果在一般地圖使用,會有明顯玩家被攻擊的特效,這點就要自己想辦法忽悠過去
不過在我們 MapleCraft 地圖,這個問題早在之前就被處理的服服貼貼,自然不必擔心

另外這個擊退是不會套用攻擊者武器附魔的,不過還是有方法提高擊退的程度,那就是:
打一下不夠你可以打兩下啊!

有個傷害標籤 bypasses_cooldown 可以讓指定傷害類型無視無敵時間
只要把我們 damage 用的傷害類型加進 bypasses_cooldown標籤內,就可以同 tick 造成多次傷害了
而造成的傷害越多次,擊退效果就會越強
不過這個方法也不是沒有極限,打越多次體感增加的擊退強度越少
我自己測試下來基本上超過三次增加的幅度就很有限了
儘管如此,搭配瞬間漂浮(提供向上動能)使用,也已經可以做到很大幅度的 motion 效果

這下終於可以做從繩子上跳出去的效果了,相信玩過 101 組任的都知道這個的重要性吧
附上大家打擂臺被妖魔搞到爬不上繩子的美好(X 回憶

選擇性擊退
上一篇結尾有提到,雖然玩家被打 MISS 卻還是有擊退效果的問題
原因是玩家必須被打了才能計算會不會 MISS,但擊退已經造成了
即使用 tp @s @s 消除動能,還是會有一點被推動的感覺

而如果常駐抗擊退,真的需要擊退時反而沒辦法把擊退的動能弄回來......
嗎?

沒錯,這個限制只存在於 damage 指令出現之前!
有了 damage 指令後,只要在需要擊退時,瞬間關閉玩家的抗擊退效果,用 damage 給無視無敵時間的傷害造成擊退效果,再把抗擊退重新打開就完成了

更穩定的自訂無敵時間
受傷特效那篇我把無敵時間從 Minecraft 的 0.5秒 改成了楓谷的 1.5秒
不過實際上這個時間是有誤差的,因為 Minecraft 的 0.5秒 無敵時間還是在
即使我自訂的無敵時間結束了,也不代表玩家能馬上再被攻擊

不過上面我們已經知道,可以把生物攻擊類型加進無視無敵時間的標籤
如此一來就能確保我們自訂無敵時間結束後,玩家還是能馬上被攻擊了
(僅限攻擊者是史萊姆類生物,剛好最近板上有一篇文章在講這個功能:連結)

但使用這個做法要注意,如果你跟我一樣是用 damage_taken, damage_absorbeddamage_resisted 的記分板統計傷害量,之後再根據數值做計算
有可能會發生同 tick 被多個目標攻擊,導致取到的數值是累加傷害的結果
為了避免發生傷害累加的問題,必須確保受到一次攻擊後,同 tick 就不會再受到其他攻擊

而能夠超越 tick 的頻率執行的東西是什麼呢?答案就是 進度
觸發進度的瞬間,就會執行進度的 reward function
即使連鎖指令執行到一半觸發了進度,也會優先執行 reward function 再接續後面的連鎖指令
也就是我們要在受到攻擊的進度(entity_hurt_player) 的 reward function 使玩家進入無敵狀態
至於進入無敵狀態的方式,相信看過上面傷害免疫的部分大家都已經知道了吧



儘管後面兩個主題寫了一堆東西,但其實後來我也沒有用到了
主要是考量效能之後,決定把熔岩史萊姆移除,改用碰撞箱偵測碰撞傷害
關於這部分就留待下篇或下下篇文章再提吧

話說這季新番追了好幾部,這週都沒做什麼進度
是不是該把楓谷每(坐)日(牢)放掉啊......

9
-
未登入的勇者,要加入 26 樓的討論嗎?
板務人員: