PlayMaker是什么?
PlayMaker是Unity3D的一款 可視化 的 有限元狀態機(Finite-state machine,簡稱Fsm) 插件,用來進行交互設計。
有限狀態機(英語:Finite-state machine,縮寫:Fsm)又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。
我個人對Fsm的理解是這樣的:
Fsm將對象的復雜行為特征歸納為有限個不同的“狀態”,然后在每個狀態中分別指定一系列“行為”讓處于該該狀態的對象來執行,同時設置一些“條件”(在FSM中稱做“事件”),當這些條件被滿足時(按FSM的說法就是事件被觸發),對象的從當前狀態變換為另一個狀態,由此帶來其所執行“行為”的的變化。
很多人把PlayMaker等同于“可視化編程”,官方在宣傳時也自我標榜為“PlayMaker - Visual Scripting for Unity3D”。在我看來,這么說其實是不太恰當的。
Unity3D中真正可以稱作“可視化編程插件”應該是 uScript Professional,而PlayMaker只能算是一個“可視化交互設計插件”,因為PlayMaker只允許使用FSM這一種“編程策略”,而且Fsm這一策略的設計思路其實是有別于正常撰寫程序腳本時所采用的設計思路的。
準確的說,PlayMaker提供了一套可視化的解決方案,讓用戶可以無需撰寫腳本代碼,就能運用有限元狀態機的設計思路在Unity3D中設計并實現交互邏輯。
對于完全沒有編程基礎的用戶來說,了解PlayMaker和編程的區別沒什么太大的意義。但對于有一點點編程基礎(卻還沒有深厚到自己寫狀態機的程度),但又想要用PlayMaker來做交互的人來說,直接把以前寫簡單程序的思維習慣代入PlayMaker,是會吃苦頭的。
所以,我們學習PlayMaker,最主要的是去學習一種交互設計的思維方法。掌握PlayMaker本身的特性和功能當然很有必要,但最終的目的是建立一種思維習慣,這種思維方法和思維習慣,是可以帶到其他軟件工具中去的,比如UE4的 Blueprints(藍圖系統),甚至真正的Programming。
PlayMaker究竟能做什么?
PlayMaker曾被認為是能夠將游戲制作者從“代碼”的汪洋大海中拯救出來的“救世主”,這當然是一個笑話。
雖然PlayMaker官方網站上列舉了很多使用PlayMaker制作的游戲產品。但實際上,如果你希望通過幾周的學習,掌握了解PlayMaker本身用法之后就能夠制作一個功能完成的游戲產品,那是極為不現實的。
PlayMaker可以讓用戶擺脫“寫代碼”這項工作,但不代表可以讓用戶擺脫“程序設計”這項工作,而且由于PlayMaker完全基于狀態機這種“策略”,實際上增加了“程序設計”的工作難度。掌握PlayMaker這件工具本身的使用方法只是第一步,更重要的是利用這件工具去鍛煉我們思維,去解決實際問題。
PlayMaker真正的用途并不是去替代那些程序員(游戲開發)的編程工作,而是給非程序員提供一個快速制作玩法原型(prototype)的工具,讓他們能夠獨立把腦海中“想象”的玩法設計實現出來,無需花費好幾年的時間去專門學習編程,也無需去“跪求”程序員大大們的幫助。
當我們經過努力的學習,逐漸能夠使用PlayMaker實現一個相對完整的游戲交互邏輯的時候,其實我們的“編程思維”就不知不覺中建立起來了。那些使用PlayMaker制作出完整游戲的開發者,我相信大多數都是很優秀的程序員,因為不論是C# Scripting還是PlayMaker FSM,其背后的本質都是所謂的“計算機程序設計思維”。
所以,PlayMaker究竟能做什么?這個問題其實挺不好回答的。PlayMaker只是一個工具,有優勢,也有不足。我們能用這個工具做什么取決于我們自身,而不是這個工具。
PlayMaker的基本概念:Fsm、States、Events、Transition、Actions、Variables
Fsm(狀態機)
舉個小例子
前面已經講過了Fsm的基本定義,現在舉一個日常生活的例子。
我們一天中會做很多事情,但我們可以把這些事情歸納為幾個狀態:睡覺;吃飯;上課;打游戲等。如果把早上作為一天的開始,我們的生活邏輯是這樣的:
- 一開始我們在睡覺(初始狀態),但設了個鬧鐘(觸發條件);到7點了,鬧鐘響起來(滿足觸發條件),我們去吃飯(狀態轉換);吃飯完成(滿足觸發條件),我們去上課(狀態轉換);下課鈴響(滿足觸發條件),我們又去吃飯(狀態轉換);吃完(滿足觸發條件)看看時間(新狀態哦),如果還早(滿足觸發條件1),就打打游戲(狀態轉換);如果時間不早了(滿足觸發條件2),就趕緊去上課(狀態轉換);……
可以看到,如果我沒有添加“看看時間”這樣一個新狀態,只有吃飯睡覺上課打游戲的狀態機是多么無聊啊!當然,其實現在也挺無聊的,不過我們可以嘗試把“看看時間”這個狀態添加到整個生活邏輯的各個部分,就會有意思很多。
在不同的狀態下我們會做不同的事情,睡覺狀態下會循環進行“呼吸”操作。吃飯狀態下會依次進行“夾菜”、“送入口中”、“咀嚼”、“吞下”等操作。重要的是,不同狀態下的行為設計是彼此獨立的,而且與交互邏輯的設計本身也是彼此獨立的。我們可以在進行行為設計之前就完成完整的交互邏輯設計,然后再慢慢添加從簡單到復雜的各狀態行為。
創建Fsm
在PlayMaker中,Fsm是被作為component(組件)添加給GameObject的。因此,一個Fsm可以被看做是一個獨立的腳本程序,用以實現一個獨立的功能。
從菜單PlayMaker
> PlayMaker Editor
中打開PlayMaker編輯器。
選擇需要添加FSM的GameObject(這里我新創建了一個Cube),打開PlayMaker編輯器,在編輯器中按照提示點擊鼠標右鍵,選擇Add FSM
,就為該Cube附加了全新的FSM類型的組件(Component):
這時界面上已經提示我們的Fsm是被添加在Cube物體上了:“Cube:FSM”(注意,這里的“FSM”代表的是這個Fsm的名字,新建的Fsm的名字都叫做“FSM”、“FSM 1”、“FSM 2”,最好能夠修改一下,以免混亂)。
Inspector面板上我們可以看到這個Component:
沒有很多參數,可以修改名稱,可以點擊Edit
打開編輯器來進行詳細設置,可以添加一些簡單描述Description...
,還可以選擇一個Fsm模板以重復利用以前的工作成果。
PlayMaker的編輯器分兩欄,左邊叫Graph(圖表面板),右邊是參數面板。參數面板有4部分組成:FSM、State、Events、Variables,分別用來對狀態機、狀態、事件、變量進行設置。
在FSM欄中其實沒有什么太多需要修改的,名稱要改一下,我通常會使用FSM_*
的方式來命名,然后單詞首字母大寫,單詞間不留空格,比如FSM_Movement
、FSM_HealthControl
這樣。
當我們創建了一個新的Fsm時,會自動得到一個初始狀態State 1
,這個State 1
上方還有一個START
的圖標并有箭頭指向State 1
。
START
這樣位于狀態上方的黑底方塊叫做“Global Transition”(全局轉換),而且START
這個事件本身還是個“系統事件”。關于事件和轉換的問題等下再講,現在我們只需要知道這個圖示的意思是:當一個叫做“START”的事情發生時,這個Cube物體進入“State 1”狀態,由于“START”在Fsm組件被創建的那一刻就會發生,也就是這個Cube物體被創建的時候就會發生,所以當場景被載入時,游戲物體Cube就已經處于“State 1”狀態了。
State(狀態)
在空白處點擊鼠標右鍵,選擇Add State
,可以添加一個新的狀態State 2
。可以看到新的State 2
上是沒有觸發條件的,這說明State 2
完全沒可能被激活,也就是完全不起作用。
我們可以在State 2
上單擊右鍵并選擇Set as Start State
,那么State 2
就會變成我們的初始狀態,State 1
不起任何作用。
點擊Unity3D工具欄中間的Play按鈕運行場景,我們會發現State 2
上有一圈綠光,PM編輯器左下角也有提示當前狀態處于“State 2”。
這時我們是沒有辦法讓Cube返回“State 1”的,因為我們還沒有設置任何觸發條件。
Event(事件)和Transition(轉換)
下面我們來添加一些觸發條件,也就是event事件。
在State 2
上點擊右鍵,選擇Add Transition
> FINISHED
。(“FINISHED”也是一個系統事件,代表“本狀態已經執行完所有操作的意思)
這時候State 2
下面多出來一行FINISHED
,說明我們已經添加了一個Transition(轉換)給State 2
,同時這個Transition的發生條件是FINISHED
事件被觸發。
但這時出現了一個錯誤標志(紅色圓圈中間有白色感嘆號),這是因為我們給一個State添加了Transition,卻還沒有指定這個Transition的目標State,就好像我們上了一輛的士,卻還沒告訴司機往哪里開一樣。這個錯誤標志就是在提示我:你小子還有事情沒干完呢!
用鼠標把FINISHED
拖到State 1
上松開,我們就指定了這個Transition的目的地是State 1
,也就是說我們告訴電腦,當FINISHED
這個事件被觸發時,請將Cube的當前狀態轉換到State 1
,這時候錯誤提示消失了。
當我們選擇一個state的時候,編輯器右邊會自動切換到State欄,這一欄中應該是儲存該狀態下需要執行的所有Action(動作)的,但在這個例子中我們還沒有添加任何Action,所以這里是空的。
點擊State欄位右邊的Events
進入Events欄,我們會發現這里已經有了一個叫“FINISHED”的Event了,而且顯示被使用過1次。Fsm中所有被使用到的Events都會被顯示在這一欄內,我們可以通過查看Used
數挑出那些無用的Event并刪除掉。
如果需要添加新的Event,只需要在下方Add Event
中輸入名稱,點擊Enter
(回車)鍵就可以了。
Event最前面的小方框如果被勾選的話,這個Event就會變成一個“Global Event”(全局事件)。普通的Event只能在Fsm內部被觸發,而Global Event可以從Fsm外部被觸發。也就是說Fsm A中的global event可以在Fsm B中通過特殊的Action(
Send Event
)來觸發,但Fsm A中的普通event只可以在Fsm A內部被觸發。
前面在添加Transition的時候會看到除了可以添加普通Transition以外還可以添加Global Transition(全局轉換)。全局轉換的形態就是好像最前面的START
一樣,位于State的上方且黑底。所謂全局轉換的意思是不論物體當前處于哪個State,只要該Event被觸發,都會轉換到目標State。而一般的Transition,只有在其所在State是當前狀態的情況下才能夠進行轉換。
區分清楚事件/全局事件、轉換/全局轉換、變量/全局變量等概念對于掌握PlayMaker非常重要。
下面我們可以把之前的生活邏輯做成一個Fsm了:
在場景中新建一個空物體(Create
> Create Empty
),Reset位移,改名為MyLife
。
打開PM編輯器,建立如下Graph:
看起來,我們的生活還是蠻復雜的嘛!
點擊Play:當前狀態停留在Sleeping
。因為我們還沒有設置任何Action,所以也不會自動觸發任何非系統Event,但我們可以手動激活Transition。
按住Alt
鍵不放,點擊我們希望激活的Transition,會發現當前狀態發生了改變。當狀態改變到Getting up
的時候,會很快跳到Breakfast
然后又跳到Check time
,這是因為FINISHED
是一個系統事件,狀態中的Action都執行完畢就會自動跳轉。
“無限循環”錯誤:
下面我們建立一個簡單的Graph來演示一個PlayMaker中很常見的錯誤:
我們希望狀態1完成以后去完成狀態2,狀態2完成以后又回到狀態1,貌似是個很正常的邏輯,在兩個狀態間不斷循環。(底下的State 3
是這個錯誤的簡化版,也是不斷循環執行自身的意思)
但如果點擊Play
按鈕,會出現[DISABLED]
提示,Unity的Console欄也會出現錯誤提示:
Error : FSM : Loop cont exceeded maximum: 1000 Default is 1000. Override in Fsm inspector.
估計大家在自己實踐的過程中會經常看到這個錯誤提示。它表面上的意思是告訴我們,這個FSM循環了超過1000次,預設的最大循環次數是1000,請在Inspector面板中修改設置。如果大家真的跑去修改這個預設的最大循環數,可能會出現兩個可能,一是會超過你新設置的最大循環數繼續報錯,二是會導致死機。
為什么呢?我們在兩個State中都沒有任何Action,所以會立即轉換到目標State,于是在游戲時間1幀內,State 1
和State 2
會不斷轉換,直到達到1000次被強行停止,在這個過程中,游戲一直停頓在那一幀,如果你的電腦很快,也許這個錯誤信息不到1秒鐘就報出來了,但如果你的電腦很慢,可能會停頓四五秒才會達到1000次循環,在這四五秒中整個游戲是卡住的。
這個錯誤類似于編程中出現的“無限循環”錯誤。如果出現這個錯誤,首先要檢查一下我們的程序邏輯有沒有問題,如果邏輯上確實沒有問題,可以給
State 2
添加一個叫Next Frame Event
的行為,然后用這個行為觸發FINISHED
事件,這樣從State 2
往State 1
的轉換就會被強制在下一個游戲幀中發生。
System Events(系統事件)
-
APPLICATION FOCUS
:游戲運行時 -
APPLICATION PAUSE
:游戲暫停時 -
APPLICATION QUIT
:游戲退出時 -
BECAME INVISIBLE
:物體不可見時 -
BECAME VISIBLE
:物體可見時 -
COLLISION ENTER
:碰撞體進入時 -
COLLICION ENTER 2D
:2D碰撞體進入時 -
COLLISION EXIT
:碰撞體離開時 -
COLLISION EXIT 2D
:2D碰撞體離開時 -
COLLISION STAY
:碰撞體停留期間 -
COLLISION STAY 2D
:2D碰撞體停留期間 -
CONTROLLER COLLIDER HIT
:Controller類碰撞體被觸碰時 -
JOINT BREAK
:骨骼斷開時 -
JOINT BREAK 2D
:2D骨骼斷開時 -
LEVEL LOADED
;關卡載入時 -
MOUSE DOWN
:鼠標在物體上被按下時 -
MOUSE DRAG
:鼠標在物體上被按下然后拖動時 -
MOUSE ENTER
:鼠標滑入物體時 -
MOUSE EXIT
:鼠標滑出物體時 -
MOUSE OVER
:鼠標懸停物體之上時 -
MOUSE UP
:鼠標在物體上按下并松開時(單擊) -
MOUSE UP AS BUTTON
:鼠標單擊(作為按鈕) -
PARTICLE COLLISION
:粒子碰到碰撞體時 -
TRIGGER ENTER
:觸發器被進入時 -
TRIGGER ENTER 2D
:2D觸發器被進入時 -
TRIGGER EXIT
:觸發器被離開時 -
TRIGGER EXIT 2D
:2D觸發器被離開時 -
TRIGGER STAY
:觸發器被停留期間 -
TRIGGER STAY 2D
:2D觸發器被停留期間
普通的event必須由Action來觸發,而系統事件則無需通過Action來觸發。我們可以做一個簡單的例子,通過鼠標左鍵的按下和松開來控制兩個狀態之間的轉換:
新建場景,創建一個Cube,Reset位移。然后打開PM編輯器,創建FSM,并另外創建一個State 2
。在State 1
上點擊右鍵,選擇Add Transition
> System Events
> MOUSE DOWN
,同理在State 2
上添加MOUSE UP
,然后把它們連起來。
在Scene View中按然后點擊Play運行場景。f
鍵使Cube居中,
保持PM的編輯器可見,然后在Game View中按下鼠標左鍵。
如果是在Cube上按下左鍵的話,Cube會進入State 2,松開鼠標,Cube返回State 1。
我劃掉上面前半句的意思是這步操作沒什么用。測試場景必須在Game View中進行,在Scene View中改變視角并不影響Game View。
要注意的是,這個范例能夠做成功,是因為我們的Fsm是添加在一個Cube物體上的,
MOUSE UP
和MOUSE DOWN
這兩個系統事件是探測鼠標是否在當前物體范圍內被按下或者松開,因而都需要這個“當前物體”是具有Collider碰撞體設置的,而Cube創建出來就自帶Collider,這樣才沒有出錯。
Network Events(網絡事件)
-
CONNECTED TO SERVER
:連接上服務器時 -
DISCONNECTED FROM SERVER
:從服務器斷開時 -
FAILED TO CONNECT
:連接服務器失敗時 -
FAILED TO CONNECT TO MASTER SERVER
:鏈接主服務器失敗時 -
MASTER SERVER EVENT
:主服務器事件 -
NETWORK INSTANTIATE
:網絡重名時 -
PLAYER CONNECTED
:玩家連接成功時 -
PLAYER DISCONNECTED
:玩家連接中斷時 -
SERVER INITIALIZED
:服務器重置時
Action(動作)
如果說使用Fsm、States、Events和Transitions可以搭出一個合理的交互邏輯的框架的話,這個交互邏輯在添加Action之前就完全是一個空架子,一個設計而已。只有添加了Action,State才變得有意義,GameObject才會隨著PlayMaker設計的這個邏輯來行動。
PlayMaker有非常多的Action,而且還有很多開發者在為PlayMaker編寫各式各樣的第三方Action(可以理解成有人為PlayMaker這個插件開發插件),一個Action通常執行一項或幾項Unity3D的“操作”,比如獲取某個GameObject的位置,在場景中新建一個Cube,改變一個材質球的顏色,為一個變量賦值等等。
選擇一個State,點擊編輯器右下角的Action Browser
可以打開動作瀏覽器。第一眼看到這個瀏覽器我整個人是崩潰的,那么多Action找都找不過來,更別說使用了。好在這個瀏覽器提供了搜索過濾功能,我們可以輸入一些關鍵字來快速定位我們想要使用的Action。
大家可以到 這里 查看完整的行為列表手冊,特別特別多。
Ceeger上有 較早版本的PlayMaker漢化手冊 可供下載,不過和現在的1.8.4
版已經有些不同了。雖然PlayMaker可以設置語言為中文(
Preferences
>General
>Language
>Chinese Simplified
),但暫時并沒有漢化Action列表。
怎么學習PlayMaker的Action
這么多Action應該怎么去學?我們要不要把每個Action的用法都背下來?我個人的經驗是從實踐中去學習。
首先當然需要把整個Action列表瀏覽一遍,大致知道PlayMaker提供了哪些方便有用的Action給我們,又有哪些是很基礎性的功能,簡單卻常常會用到。
Action列表自身的組織結構是有分類的,但這個分類主要是依照Action所處理的對象來分的,而且主類就有幾十個,并不是特別適合我們學習。我這里提供一個我自己理解的依照Action所執行功能而做的大致分類給大家參考:
- 用來獲取參數/變量數值的
- 用來改變參數/變量數值
- 用來創建或刪除游戲物體
- 用來給游戲物體添加或刪除組件
- 用來執行某個組件(或腳本)中的特定功能函數(Function)
- 用來觸發Fsm事件
- 對整個游戲系統進行控制,比如暫停、退出、載入場景等等
其實,Action列表中有很大一部分是我們平時很少會用到的,甚至有一些是針對特定插件的支持,如果不用這些插件,就根本不會需要用到這些Action。
按照功能標準在腦海中對所有Action有了個初步印象以后,就要記憶一些關鍵字了,因為我們99%的情況下,都是通過關鍵字在Action Browser中去搜索需要的Action,而不是順著列表慢慢找。記住這些關鍵字可以極大的方便我們定位Action,節省時間。這些關鍵字包括:get
、set
、gameobject
、position
、fsm
、float
、bool
、vector3
、collision
、trigger
、ray
、compare
等等。
這個記憶工作可以隨著我們學習和練習的過程來做,看得多了,做得多了,慢慢也就知道要實現哪些功能需要用到哪些Action了,那么這些Action的用法就是需要熟練掌握的。總而言之,學習Action要按照“功能類”來一類一類地學,而不是一個一個Action地學。
對于我們用到的Action,花一點點時間去看看這個Action的具體用法。在Action Browser中定位到Action之后是可以看到一個簡單的描述的,英文好的同學可以直接閱讀一下,看不懂的就去找中文翻譯。我有時間的時候也會慢慢完成 PlayMaker Actions (未完成) 這一篇文章,以供大家參考。
對PlayMaker有個基本的掌握之后,網上的PlayMaker教程對你基本上就沒有什么難度(總體來說PlayMaker的教學資源還是太少,也太淺),這個時候可以開始看那些入門級別的Unity3D小游戲教程,看人家怎么寫腳本來實現交互設計的,然后把別人的思路轉化到PlayMaker里面,用PlayMaker去重現這些教程的內容。
關于 Every Frame
選項
有些Actions會具有一個Every Frame
的選項,通常都在最下方。勾選這個選項,會迫使這個Action每幀都執行一遍,直到游戲物體離開當前狀態。不勾選的話這個選項的話,每次進入這個狀態以后,這個Action的操作都會進行一次,不論游戲物體在這個狀態停留多久。
但這并不是說沒有這個選項的Action就都是只執行一次的,沒有這個選項代表這個Action要么只能執行一次,要么是必須每幀執行的。
怎么來判斷是否勾選這個選項呢?比如我們有個Action是監控是否有子彈擊中玩家的,那么這個Action當然要每幀都執行一遍咯,但如果是發射子彈的Action,通常就不能每幀執行了,僅在“發射”狀態被激活時執行一次就可以了。
如果一個State的列表中有至少1個
Every Frame
類型的Action,那么這個State就不可能自然終止,也就是說系統事件FINISHED
永遠不會被觸發,因為這個Every Frame
類型的Action永遠不會執行完畢。
這里有一個思維誤區,就是理所當然的認為從一個狀態轉換到另一個狀態的時候,游戲進入了新的一幀,這是不對的。如果沒有
Next Frame Event
的話,從狀態A轉換到狀態B然后再到狀態C是有可能發生在同一幀游戲時間以內的。因此在PlayMaker中利用狀態轉換做循環要非常小心,否則很容易在一幀里面無限循環。
變量(variables)
變量是用來儲存數據/數值的。
Unity3D自身有變量,不同的Component都有很多或私有(private)或公開(public)的變量,PlayMaker可以通過Action去調用它們(Get Property
)或者直接對其賦值(Set Property
)。
PlayMaker自身也有變量,我們叫做Fsm變量,以區別于Unity3D的變量。調用其他Fsm的變量需要用到Get FSM Variable
這個Action,為其他Fsm變量賦值要用到Set FSM Variable
這個Action。
對于本Fsm內部的變量進行操作是最簡單的,很多Action都可以讀取某個內部變量值或者將某個值儲存在內部變量中。
變量需要事先申明其“類型”,PlayMaker對于變量類型的要求非常嚴格,大家可以在 Unity3D的數據類型以及PlayMaker的變量 一文中詳細了解。