更多更詳細的Bolt視頻教程可以去我B站的主頁觀看:https://space.bilibili.com/230809767
Bolt是什么线欲?
Bolt是一個比較新的Unity可視化編程插件一死,目前穩(wěn)定版本為1.4,alpha測試版本為2.0。
官網(wǎng)地址:https://ludiq.io/bolt
Asset Store購買地址:https://assetstore.unity.com/packages/tools/visual-scripting/bolt-87491
Bolt在設(shè)計理念和使用上都很類似于UE4的藍圖(Blueprints)坦报,屬于“流(flow)”式設(shè)計米绕。簡單地說款侵,“流”式設(shè)計就是“按順序依次執(zhí)行每一步”,這其實才是最符合程序代碼執(zhí)行邏輯的設(shè)計湃累,因為程序代碼的執(zhí)行邏輯就是“一行命令執(zhí)行完再執(zhí)行下一行命令”勃救。
市面上用“流”式設(shè)計的可視化編程插件其實也蠻多的碍讨,早期的uScript Professional,現(xiàn)在的flowCanvas都屬于此類蒙秒。
我們可以這么來理解這些“流”式設(shè)計的可視化編程插件:
- 每一個“節(jié)點”代表一項命令勃黍;
- 執(zhí)行完上一個“節(jié)點”所代表的命令之后再執(zhí)行下一個“節(jié)點”所代表的命令;
- 數(shù)據(jù)(或變量)可以從一個“節(jié)點”輸入給另一個“節(jié)點”晕讲。
在Bolt中覆获,其基本的“節(jié)點”(被稱為“單元(unit)”)是Unity的各種API命令。Bolt號稱支持所有Unity內(nèi)置命令(大概有23000多種)瓢省,還允許手動添加其他的第三方插件的自定義類(class)弄息。因此,Bolt自夸:“凡是可以用代碼實現(xiàn)的功能勤婚,都可以用Bolt來實現(xiàn)”摹量。
If it can be done with code, it can be done with Bolt.
這句話說得有一點夸張,但也代表了Bolt的特征馒胆。它本質(zhì)上就是直接調(diào)用Unity的API命令缨称,用和程序腳本差不多的執(zhí)行邏輯來運行,這在本質(zhì)上與c#腳本是一致的祝迂。比如下面這個Bolt的Graph睦尽,就完全等價于后面的那個c#腳本:
public class test : MonoBehaviour {
// Use this for initialization
void Start () {
Debug.Log ("Hello World!");
}
// Update is called once per frame
void Update () {
transform.Rotate (0f, Time.deltaTime * 30f, 0f);
}
}
可以看到,Start ()
和Update ()
函數(shù)變成了Event節(jié)點型雳,Debug.Log()
当凡、Transform.Rotate ()
、Time.deltaTime
也變成了普通節(jié)點四啰。有專門的String節(jié)點用來獲得一個String類型的值宁玫,也有Multiply節(jié)點來進行乘法計算。程序員如何“設(shè)計”一段代碼柑晒,我們就可以如何“設(shè)計”一個Bolt的Graph欧瘪。
當(dāng)然,從實際的使用感受來看匙赞,這樣的設(shè)計方式并不是很“人性化”会宪,那些“節(jié)點”的功能對于初學(xué)者來說也不是那么“一目了然”椰弊,還需要用戶具備一定的程序思維能力法严,才能將自己的需求轉(zhuǎn)換成流式的節(jié)點運行邏輯肥哎。坦白來講,Bolt的上手難度是要高于PlayMaker的坐榆。但是拴魄,一旦用戶跨過了“新手期”,就可以大量將已有的“代碼類”教程翻譯成Bolt流圖,獲得快速提升匹中。而不像PlayMaker夏漱,還需要將“流”式的代碼改寫成“狀態(tài)機”,然后再才能予以實現(xiàn)顶捷。
本質(zhì)上挂绰,PlayMaker中一個State中的Action也是以“流”的方式運行的(上方Action先執(zhí)行,下方Action后執(zhí)行)服赎,但一方面各種“Every Frame”和非“Every Frame”的Action混合在一起非常難以分辨葵蒂,另一方面很難表達“分叉”式的邏輯(比如“if... else...”這樣)。因此重虑,對于稍復(fù)雜一些的“流”式邏輯践付,就很難在一個State中實現(xiàn)出來,而要改寫成多個States之間的轉(zhuǎn)移變化嚎尤。
但實際上荔仁,“狀態(tài)機”其實是為了解決在一些特定問題上“流"式邏輯太復(fù)雜而提出的一種歸納整理的方案伍宦,將復(fù)雜的”流“拆成幾個相對簡單的”狀態(tài)“芽死,而到了PlayMaker里面,往往不得不將本來很簡單的”流“式邏輯改成甚至于更復(fù)雜的”狀態(tài)機“方案次洼,頗有點”脫了褲子放屁“的意思关贵。
Bolt本身也有狀態(tài)機(State Machine),但它的狀態(tài)機就是在流(Flow Machine)之上的卖毁,這才是符合程序設(shè)計邏輯的做法揖曾。
Bolt的安裝和設(shè)置
Bolt需要在Asset Store或者其官網(wǎng)上購買,購買后會獲得一個.unitypackage
文件亥啦,用于導(dǎo)入到Unity項目中炭剪。
導(dǎo)入完成后會自動彈出設(shè)置窗口:
首先是歡迎界面,點擊Next
按鈕設(shè)置Naming:
這里建議使用Human Naming
翔脱,比較不那么“反人類”奴拦,但依然還是需要經(jīng)常反查Unity的腳本幫助文件來了解哪些可用的Bolt單元/Unity函數(shù)。
Documentation欄是用來建立本項目的幫助文件的届吁,點擊Generate Documentation
可以建立編輯器內(nèi)顯示的幫助文檔错妖,如果出錯也不要緊,忽略即可疚沐。Inspectors欄也可以直接點擊Generate Inspectors
暂氯,讓Bolt自動完成這一步工作。
如果希望在Bolt中控制第三方插件亮蛔,則需要在Assemblies欄中加載對應(yīng)的第三方插件的DLLs文件痴施。
如果只是一些自定義的庫(class)或者構(gòu)造體(struct),則需要在Types欄中予以添加,然后點擊Generate
讓Bolt創(chuàng)建本項目所需要的codebase辣吃。這個codebase就是我們可以在Bolt中使用的全部單元的集合锉矢。
在使用過程中,我們也可以通過Bolt菜單中的相關(guān)命令齿尽,來添加新的Assemblies或者Types沽损,或重建單元數(shù)據(jù)庫。
-
Setup Wizard...
:重新設(shè)置本項目的Bolt -
Update Wizard...
:更新本項目的Bolt設(shè)置到最新(升級了Bolt版本之后使用) -
Configuration...
:打開偏好設(shè)置窗口 -
Unit Options Wizard...
:僅更新Assemblies和Types兩項Bolt設(shè)置(需要添加新的插件支持或新的自定義類時使用) -
Build Unit Options
:直接重建codebase和unit database(在修改自定義腳本之后使用) -
Update Unit Options
:僅直接重建unit database(在修改自定義腳本之后使用)
在偏好設(shè)置窗口中循头,我們可以對Bolt進行一些設(shè)置绵估,其中大部分選項都可以保持默認,但我個人比較建議在
Ludiq
一頁中把Snap To Grid
選項勾上卡骂,這樣強迫癥患者就不需要對著參差不齊的流圖而揪心了国裳。
Bolt的基本概念
Machine(機)
Machine(機)是我們添加給游戲?qū)ο螅℅ame Object)的Bolt組件,相當(dāng)于一個完整的腳本全跨。Bolt有兩種Machine:Flow Machine(流機)和State Machine(狀態(tài)機)缝左。前者是用一個個Unit串成完整的流式邏輯,后者則是用流機來描述一個個不同的狀態(tài)(states)以及狀態(tài)與狀態(tài)之間轉(zhuǎn)換的條件(Transition)浓若。
本文中不會多講State Machine渺杉,在剛剛接觸Bolt的時候,能用Flow Machine完成的交互設(shè)計都應(yīng)該盡量用Flow Machine來完成挪钓,一方面是因為沒必要是越,另一方面也是因為在Bolt中的State Machine設(shè)置起來其實還是蠻復(fù)雜的。
Embed VS. Macro
Machine可以內(nèi)嵌(Embed)于Unity場景碌上,也可以作為宏(Macro)保存在Asset文件夾里作為一個可以重復(fù)使用的游戲資源倚评。
建議最好在剛創(chuàng)建完Machine就決定究竟是使用哪種方式,因為雖然可以從一種方式轉(zhuǎn)換為另一種方式馏予,但這個過程并不是100%無損的天梧,復(fù)雜的Graph幾乎不可能不在轉(zhuǎn)換后出現(xiàn)各種各樣的問題。
我個人的建議是霞丧,如果一個Machine所代表的功能會被用在不同的地方呢岗,就請盡量使用Macro,或者說蚯妇,如果你準(zhǔn)備“復(fù)制/粘貼”某個Machine中的Graph到一個新的Machine中去敷燎,那么就將這個Machine轉(zhuǎn)換成Macro,然后在新Machine中調(diào)用箩言。
Graph(圖)
在Bolt中硬贯,Graph(圖)是程序命令執(zhí)行邏輯的可視化呈現(xiàn)。Flow Machine中呈現(xiàn)的是Flow Graph陨收,State Machine中呈現(xiàn)的是State Graph饭豹。
Flow Graph呈現(xiàn)出來的是一堆彼此相連的units(單元)鸵赖,其中必定有一個起點,這個起點通常是一個Event類型的unit拄衰,比如常見的Start
和Update
它褪。代表著“當(dāng)……發(fā)生時,開始執(zhí)行本Graph”翘悉。
比如前面示例中的左邊的Graph茫打,就是在說:“當(dāng)本游戲?qū)ο蟊惠d入時,在console面板中輸出一行字符串’Hello World!’”妖混。
一個Machine中可以有多個Graphs老赤,用來區(qū)分在不同事件發(fā)生時所觸發(fā)的不同程序命令執(zhí)行邏輯。通常還可以將同一事件所觸發(fā)的多段邏輯分開成不同的Graph抬旺,以方便調(diào)試。比如“行走”和“跳躍”都會被Update
所觸發(fā)振坚,但我們可以在一個Flow Machine中用兩個Update
打頭的Graph來分別制作這兩個交互行為传货。不過,如果這兩個交互行為互相之間有所關(guān)聯(lián),或者需要有嚴格的前后執(zhí)行順序,那么最好還是做成一個單獨的Graph,避免出現(xiàn)問題。
嚴格來說,Bolt其實是將Machine中的所有內(nèi)容定義成一個Graph栏饮,但我個人認為這樣反而不好伺通,所以在這里解釋成“一個graph代表一個完整的彼此相連的units執(zhí)行流程結(jié)構(gòu)”弓柱。希望不會對大家的理解造成誤會屁药。
Unit(單元)
單元的類型
Unit是Bolt中最基本的元素缭嫡,一個unit代表一個操作命令有勾,多個unit按照順序組合成Graph荤懂,從而實現(xiàn)某一個特定的程序功能节仿。默認情況下,Bolt有超過23000個不同的unit掉蔬,我們可以把它們分成幾個大的類別:
- 事件單元(events):決定“當(dāng)……發(fā)生時”的各種unit廊宪,通常都會顯示為綠色,被用在一個Flow Graph的起點
- 命令單元(actions):決定“做什么”的各種unit
- 數(shù)據(jù)單元(data):輸入各種數(shù)據(jù)的unit女轿,比如各種以“Literal”結(jié)尾的unit箭启。這些單元可以無需調(diào)用變量而獲得一個Unity數(shù)據(jù),比如浮點數(shù)(float)蛉迹、字符串(string)傅寡、光線(ray)、層遮罩(layer mask)等
- 計算單元(calculation):用來處理數(shù)據(jù)北救、計算數(shù)據(jù)的各種unit荐操,比如加減乘除,各種數(shù)學(xué)函數(shù)等
- 變量單元(variable):用來調(diào)用或修改變量的unit扭倾,主要是
Set Variable
和Get Variable
兩個 - 邏輯單元(logic):用來控制Graph的運行邏輯走向的單元淀零,比如
Branch
(相當(dāng)于“if... else...”條件語句)和Loop
(相當(dāng)于“for...”循環(huán)語句)等等
新建/替換單元
在Graph窗口的空白處點擊鼠標(biāo)右鍵,選擇Add Unit...
膛壹,就可以通過搜索關(guān)鍵字來創(chuàng)建新的unit。如果在一個已有的unit上點擊鼠標(biāo)右鍵唉堪,選擇Replace...
模聋,可以替換這個unit。
Fuzzy Finder(單元搜索器)
用來搜索unit的窗口叫“Fuzzy Finder”唠亚,目前還是比較好用的链方,搜索速度也比較快。但要注意的是灶搜,如果用多個關(guān)鍵字搜索祟蚀,這多個關(guān)鍵字的順序必須和結(jié)果中這些關(guān)鍵字的出現(xiàn)順序保持一致才行工窍,比如我搜“position” + “transform”就得不到“transform.position”,必須搜“transform” + ”position”才行前酿。
Connections(連接) & Ports(接口)
- 三角形線框圖標(biāo)指向的(綠色的寬體箭頭)叫Connections(連接)患雏,是用來連接不同units以決定其執(zhí)行順序的端口
- 圓形線框圖標(biāo)指向的(其他各種)叫Ports(接口),是用來傳遞數(shù)據(jù)的端口罢维,根據(jù)數(shù)據(jù)類型的不同淹仑,有不同的普調(diào)樣式,左邊的是接受其他數(shù)據(jù)的接口肺孵,右邊的是輸出數(shù)據(jù)的接口
并不是每個unit都需要用被Connection相連接才能起效匀借,有的unit根本就沒有Connection端口。一般來說平窘,執(zhí)行計算任務(wù)的unit都不需要連接Connection吓肋,比如下圖:
當(dāng)然,這個圖里面輸出的數(shù)據(jù)最終還是需要被傳遞給某個連接了Connection的unit才會真的被執(zhí)行瑰艘。
Overloads(分身)
一個Unity命令可以有多種調(diào)用方式蓬坡,叫做Overloads。Bolt中使用不同的unit來對應(yīng)各個Overloads磅叛,我個人將其翻譯成“分身”屑咳。
我個人不太喜歡這種將每個Overload都做成單獨節(jié)點的做法,每次都要找半天弊琴,而且很容易看錯兆龙。但貌似也沒有更合適的辦法了。
Variables(變量)
在Bolt中敲董,有5種不同形式的變量:
- 圖示變量(Graph):圖示變量是圖示例中的局部變量紫皇。它們具有最小的可調(diào)用范圍,不能在其圖示之外訪問或修改腋寨。
- 對象變量(Object):對象變量屬于游戲?qū)ο螅℅ameObject)聪铺。他們在該游戲?qū)ο蟮乃袌D示上共享。
- 場景變量(Scene):場景變量在本場景的所有圖示上共享萄窜。
- 應(yīng)用變量(Application):即使場景改變铃剔,應(yīng)用變量仍然存在。一旦應(yīng)用程序退出查刻,它們將被重置键兜。
- 存儲變量(Saved):即使應(yīng)用程序退出,存儲變量也會也會繼續(xù)存在穗泵。他們可以被用作一個簡單但功能強大的存儲系統(tǒng)普气。它們被保存在Unity的玩家選項,這意味著它們不能像游戲?qū)ο蠛徒M件那樣被引用佃延。
個人認為现诀,Bolt的變量形式被設(shè)計得很奇怪夷磕。很難將其與正常腳本中設(shè)置的變量對應(yīng)起來理解。
- Graph Variable有點類似private variable(私有變量)仔沿,但又必須提前定義好坐桩,遠不如腳本中那么方便使用;
- Object Variable類似public variable(公開變量)于未,可以被外部訪問撕攒,但Bolt的Object Variable是通過在同一個Game Object上的一個叫做“Variables”腳本來定義的,相當(dāng)于是在獲取另一個組件的變量在用烘浦,而不是在本組件中定義可以公開訪問的變量抖坪;
- Scene Variable類似global variable(全局變量),但其實是在場景中新建了一個叫“Scene Variables”的空游戲?qū)ο竺撇妫缓笸ㄟ^“Variables”腳本來定義擦俐,相當(dāng)于是在獲取同一場景中另一個游戲?qū)ο蟮淖兞吭谟茫?/li>
- Application Variable才好像是真的全局變量;
- Saved Variable實際上是在操作Unity的
PlayerPrefs
握侧,算是一個簡單實現(xiàn)“存檔”功能的方法吧蚯瞧,但還是顯得不倫不類的。
此外品擎,Bolt能夠用幾乎所有Unity數(shù)據(jù)類型做為變量埋合,比如:
- 浮點數(shù)(Float)
- 整數(shù)(Integar)
- 布爾型(Boolean):是/否
- 字符串(String)
- 字符(Char):字符串中的一個字符
- 枚舉類型(Enums):預(yù)先設(shè)定好的有限的枚舉選項,例如下拉菜單中的所有選項
- 向量(Vector):向量其實是一個struct萄传,Unity中的向量包括二維向量(Vector2)甚颂、三維向量(Vector3)、四維向量(Vector4)
- 游戲?qū)ο螅℅ame Object):Unity場景中的基本實體秀菱,每個游戲?qū)ο蠖加幸粋€名稱(name)振诬,一個位置和旋轉(zhuǎn)的轉(zhuǎn)換(transform),以及一個組件列表(components)
- 列表(List):列表是元素的有序集合衍菱。元素可以是任意類型的赶么,但大多數(shù)情況下,列表中的所有元素必須是同一類型的脊串”枭耄可以通過從0開始的索引位置(index)檢索和分配列表中的每個元素
- 字典(Dictionary):字典是一個集合,其中元素有一個唯一的鍵洪规,映射到它的值印屁。可以通過字典的鍵來檢索和分配字典的每個元素斩例。比如:可以按名稱(字符串鍵)創(chuàng)建年齡字典(整數(shù)值)
- 對象(Object):“對象”是一種特殊類型。各種Unity專有的數(shù)據(jù)類型都被歸為Objects从橘。
變量窗口可以通過菜單Window -> Variables打開:
我個人強烈建議將這個窗口dock到編輯器的UI里念赶,雖然Graph窗口中會自動出現(xiàn)Variables窗口础钠,但那需要Graph窗口足夠大,而大多數(shù)時候叉谜,我們都不會將Graph窗口放大到那么大旗吁。
最后很钓,我們可以通過將variable拖動到Graph窗口中來創(chuàng)建Get Variable單元,以使用這個variable董栽,也可以按住Alt
鍵拖動variable到Graph窗口來創(chuàng)建Set Variable單元码倦,以設(shè)置這個variable。
Bolt的限制
Bolt所號稱的“凡是可以用代碼實現(xiàn)的功能擒抛,都可以用Bolt來實現(xiàn)”其實是有水份的推汽。
雖然Bolt支持幾乎所有的Unity函數(shù)命令,但并不等于我們就能完全無縫地將所有c#腳本代碼翻譯成Bolt流圖歧沪。至少:
- Bolt目前還不支持Editor類的Unity函數(shù)命令歹撒。也就是說,沒有辦法用Bolt來做編輯器拓展诊胞。
- Bolt目前不能直接支持自定義類(class)或者構(gòu)造體(Struct)暖夭。不是不能用,而是需要先用c#寫出來然后再加載進Bolt厢钧,才能在Graph中創(chuàng)建相應(yīng)的單元(unit)鳞尔。
- Bolt目前還沒有完美的辦法實現(xiàn)自定義函數(shù)。Custom Event很類似了早直,但Custom Event不能定義輸出結(jié)果是在是很淡疼的一件事情寥假。而且Custom Event的本意是為了在c#腳本中調(diào)用Bolt的自定義事件。
這些缺失大多可以通過自己寫一些腳本來“彌補”霞扬,但如果自己都能寫腳本的糕韧,那還用Bolt干什么呢?況且喻圃,如果有專門的程序員幫我們寫擴展來“彌補”缺失萤彩,那我們干嘛不用PlayMaker呢,反正缺什么Action就讓人寫就是了斧拍。
另外雀扶,Bolt的Graph的復(fù)雜度其實要比程序代碼高很多,比如上面一條簡單的transform.Rotate (0f, Time.deltaTime * 30f, 0f);
就要用3層結(jié)構(gòu)4個單元來組合完成,可想而知一個復(fù)雜的程序代碼翻譯成流圖之后會有多么復(fù)雜愚墓。而且予权,Bolt的運行效率(尤其是編輯器中的運行效率)比起代碼來要低很多,項目小還好浪册,一旦項目大起來扫腺,估計電腦差了連調(diào)試都調(diào)試不動呢。
因此村象,Bolt并不是一個“完美”的可視化編程插件笆环,只是比現(xiàn)有的解決方案要好一些罷了。與其將其當(dāng)做一個“生產(chǎn)力工具”厚者,不如將其當(dāng)做一個“學(xué)習(xí)工具”或者一個“原型工具”躁劣。在學(xué)習(xí)Bolt的過程中,盡量去了解正常的程序設(shè)計思維是如何進行的籍救,去熟悉Unity的各種API命令习绢,去掌握不同交互邏輯的實現(xiàn)手段和技巧,才是這個插件對于我們的最大價值蝙昙。