在Unity3D的當前版本中,Animator Controller已經全面取代了原來(大概是4.x版本左右)的Animation系統。
從本質上來說,Animator Controller是一個內置在Unity3D中的,專門用于動畫控制的“狀態機”,所以學習Animator Controller和學習PlayMaker有一定的共通之處。
從實際的使用場景來說,Animator Controller雖然可以建立復雜的運動邏輯,但其本身并不具備任何交互能力,也就是說我們不能僅使用Animator Controller就達到“控制”目標對象動畫的目的,還必須撰寫Script(腳本)來操縱Animator Controller中的Parameter(參數)從而真正控制對象的動畫。或者,使用PlayMaker來代替C#腳本。
因此,本章內容主要分為三個部分:
- 從fbx文件中導入或直接在Unity3D中制作動畫片段(Animation Clips)
- 在Animator Controller設計動畫邏輯
- 在PlayMaker中制作交互控制Animator Controller
生成動畫片段(Clips)
導入FBX文件中的動畫
可以參看文章:為Unity3D創建素材(2):模型、綁定、動畫 中的相關內容
FBX中的動畫會被識別為動畫片段文件(.anim
),但存在于FBX中的動畫片段不可被編輯,所以如果需要修改或者重復調用這些文件,建議還是Ctrl+D復制一份之后再使用。
另外,FBX中的動畫是根據角色的Avatar設置而生成的,因此雖然理論上動畫可以重復使用,但其前提是兩個角色都使用了相同的Avatar設置。最簡便的方法是將所有角色都設置成Humanoid類型的Avatar,這樣就可以避免因為Avatar的不同而產生動畫映射出錯的問題。
比如"Unity-chan!" Model和HQ Fighting Animation FREE這兩個素材,雖然都使用了同一個角色模型,骨骼設置也都是一樣的,但由于Avatar設置不同,所以個素材中的動畫是不能直接共用的,必須將一個修改成另一個的Avatar(比如將HQ Fighting Animation FREE中的所有帶Animation的FBX都設置成Humanoid形式)之后才能通用。
animation_01.pnganimation_02.png
創建動畫
簡單的動畫可以在Unity中直接生成。
從菜單Window
-> Animation
打開動畫面板。如果選擇的游戲物體上沒有附加任何動畫片段,動畫面板上應該是這樣的:
點擊Create
按鈕并保存文件,則為該游戲物體創建了一個動畫片段(.anim),點擊左上方的“錄制”按鈕可以進入錄制狀態,也可以理解成進入“自動創建關鍵幀”的狀態。在這個狀態中我們對任何參數進行修改,都會自動為這些參數創建動畫關鍵幀,并記錄在動畫片段中。
除了Transform組件上的屬性,我們還可以手動點擊Add Property
為該游戲物體上其他組件的可動畫參數創建關鍵幀動畫。
關閉“錄制”按鈕可以將動畫保存起來。一個游戲物體上可以附加有多個動畫片段,比如下圖中的Cube就是當前正在編輯的動畫片段的名稱,而點擊這個名稱則可以切換到其他動畫片段或者創建新的動畫片段。
添加了動畫片段的游戲物體中會自動添加上Animator組件,并自動附上Controller。要注意的是,下圖中的Controller參數中的Cube和上面動畫片段的Cube并不是一回事,只是Unity自動創建了相同名稱的Controller和動畫片段而已。
雙擊Cube Controller打開編輯器:
這是最簡單的一個動畫邏輯:當被載入時,播放Cube動畫。
單擊這個橘色的Cube
,在Inspector中會顯示這個“狀態(State)”的詳細設置:
這個狀態所調用動畫片段(Motion
)是“Cube”,播放速度(Speed
)是正常1倍速,由于這個動畫片段中僅有一個關鍵幀,所以其實它是不包含任何“動畫”的,只不過是一個“姿勢”而已。對于Unity3D來說,“姿勢(Pose)”和“動畫(Animation)”本質上是一樣的。
雙擊這個橘色的Cube會直接選擇到Cube動畫片段文件:
下面我們給這個Cube創建一個自動旋轉的動畫片段:
這個動畫很簡單:3秒鐘以內,Y軸旋轉360度。有幾個細節值得留意一下:首先,Transform上的動畫關鍵幀,一加就是加3個(x、y、z),這是因為Unity對于位置數據是作為Vector3類型來處理的;然后,當處于“錄制”狀態時,Animator組件是處于“不激活”狀態的,這樣我們才可以在場景視圖中觀察到所制作的動畫。
打開Animator面板,可以看到多出來一個灰色的狀態“Rotate”,這就是剛剛制作的旋轉動畫。如果沒有自動出現,可以將Rotate.anim文件拖進來。
運行游戲時,這個Cube是不會旋轉的,因為當前被播放的是Cube動畫。
我們可以右鍵單擊Rotate
,選擇Set as Layer Default State
,這樣會讓Rotate
變成初始“狀態”,游戲運行時就會自動播放了。
設計動畫邏輯
Animator Controller的基本動畫邏輯是:一個游戲物體可以使用任意多個動畫片段,每個動畫片段都是一個獨立的“狀態”,而運動的變化則體現為從一個狀態到另一個狀態的過渡。
比較先進的是,Animator Controller中狀態到狀態的過渡,可以是“漸變”的,因此運動變化顯得非常自然。
用小方塊做一個簡單的例子
我們先從這個簡單的小方塊動畫開始。
這里我們有兩個狀態,靜止和旋轉。我們可以將初始狀態依然設置給Cube
,然后右鍵單擊Cube
,選擇Make Transition
,然后將箭頭指給Rotate
狀態。
這個意思是“當Cube狀態的動畫播放完畢,則過渡到播放Rotate狀態的動畫”。點擊這個連線,可以看到實際的Transition參數:
從這里可以看到兩個動畫片段是如何“融合過渡”的(其實跟Maya的Trax Editor,或者Premiere的軌道編輯器挺像的)。
我們可以看到這個過渡并不是一開始就發生了的,而是在Cube
狀態停留了大概1秒鐘的時候才在大概1/4秒的時間內過渡到Rotate
狀態。
我們可以在這個軌道圖上對整個動畫過渡進行修改。
不論怎么修改,整個過渡都依然是“自然過渡”,也就是前一個狀態播放完畢過渡到下一個狀態。除非我們為它提供一個條件(Condition)。
目前的Conditions一欄中是空的,點擊小+
號添加一個條件,但提示說:“沒有任何參數存在”。條件是需要用參數來指揮的。
回到Animator Controller面板,左上方Parameters一欄中點擊小+
號添加一個新的參數,這里我們選擇Bool類型,命名為“Rotate”。
然后指定這個Rotate
參數為剛剛創建的Condition,并設置當Rotate
取值為true
時,條件成立。
同理,我們再創建一個從Rotate
狀態到Cube
狀態的Transition,并添加一個條件,并設置成當Rotate
取值為false
時,條件成立。
運行場景,小方塊一直在Cube
狀態循環,手動點擊Rotate
參數使其為true
,小方塊開始旋轉。手動修改Rotate
參數為false
,小方塊又停了下來。
目前小方塊的旋轉和停止都有點“滯后”,這是因為兩個Transition中都勾選Has Exit Time
選項,這個選項保證必須播放到一定時間才能進行過渡,在有些情況下是需要的,但這個簡單例子中卻并不需要。
取消這兩個勾選,我們就作出了一個非常簡單的旋轉動畫控制邏輯。
將這個場景保存起來。
用Unity-Chan做一個復雜一些的例子
新建場景,導入"Unity-chan!" Model和HQ Fighting Animation FREE這兩個素材。
找到UnityChan
-> Models
中的unitychan.fbx
文件,拖到場景中。建議拖動到Hierarchy面板中松開鼠標,這樣會將模型生成在場景原點位置,如果直接拖到場景面板中,則很有可能不在原點位置。
在Assets文件夾中單擊右鍵創建一個新的Animator Controller資源,命名為UnityChan_AC
,并將這個Animator Controller配置文件拖到場景中unitychan
物體的Animator組件的Controller參數上去,這就為該角色的Animator指定了一個控制器。
雙擊打開UnityChan_AC
,目前是沒有任何狀態的,這是因為這個.fbx
文件只是一個模型文件,不包含任何動畫,當然也就沒有任何“狀態”。我們可以在目錄:UnityChan
-> Animations
中找到包含動畫數據的.fbx
文件,但這些文件的模型貼圖都不正確。
選擇某個動畫文件,并不能看到正確的動畫預覽,即便是點擊小箭頭展開之后選擇其中包含的動畫數據文件,也只能看到模型不完整的預覽動畫。這是因為這些動畫文件中根本就只有骨骼和面部多邊形,并沒有身體的模型數據。
我們可以將場景中的unitychan
模型拖動到預覽窗口,這樣就可以預覽到該動畫在我們希望的目標模型上的表現了。
點擊預覽窗口的播放按鈕可以播放動畫。
由于Animations文件夾里面動畫很多,我直接利用搜索功能來尋找所需要的動畫文件。
- 這里是搜索欄,搜索的范圍默認是整個Assets文件夾,但可以修改為搜索當前文件夾或者搜索Asset Store;
- 這里的滑桿拖到最左邊是以文件名方式顯示,適合同時查看較多的文件對象,拖到最右邊是最大的圖標顯示,適合查看文件對象內容;
- 這里可以看到當前選擇的文件所處的文件夾路徑,可以以此判斷是否是我們想要的資源,因為有可能在不同的文件夾下有同樣名稱的資源,比較容易發生混淆;
- 這種圖標就代表了動畫數據文件,選擇這樣的文件可以在預覽窗口中預覽到動畫效果。
WAIT00
是我現在所需要的靜止動畫,其他幾個wait動畫都過于動感了。
將WAIT00
拖動到Animator Controller中,設置為初始狀態。運行場景,可以看到角色已經不是T-Pose站立了。如果當前的攝影機角度不適合查看角色模型,可以自行修改一下。
如果我們使用“idle”作為關鍵詞來搜索,可以搜索到另一個素材包中所包含的Idle動畫文件,但這個文件不能直接應用在現在這個角色身上。
如果強行使用,會得到非常奇怪的效果:
找到這個Idle動畫數據所在的文件:FUCM05_0000_Idle.fbx
,修改其導入參數的Animation Type為Humanoid
(UnityChan的模型就是用的Humanoid類型),點擊Apply
應用該修改。
再次運行場景,這回動畫就顯示正常了。
從“站”到“走”到“跑”
下面我想實現這樣的一個動畫邏輯:角色根據其移動速度自動選擇是站立原地不動,還是慢慢行走,或者快速奔跑。這個動畫邏輯非常適合使用Animator Controller的Blend Tree來制作。
Blend Tree可以被看作是一個特殊的“狀態”。
在Animator Controller面板中點擊右鍵,新建一個Blend Tree,修改狀態名稱為Localmotion
,并設置其為初始狀態:
雙擊打開Localmotion
,可以看到:
- 默認Blend Type為
1D
,也就是說當前使用的一維模式,僅使用一個參數來控制動畫混合; - 自動為我們創建了一個
Blend
參數,數據類型是Float,同時指定Blend
參數為該Blend Tree的控制參數,也就是說,當Blend
數值從0~1變化時,角色依次從一個Motion變化到另外的Motion; - 當前Motion欄中是空的,需要點擊小
+
號來添加融合的動畫片段數據。
添加3個Motion,出現一個圖示表示這3個Motion是如何被混合的。
使用搜索功能搜出WAIT00
、WALK00_F
、RUN00_F
三個clips,分別依次拖到這3個Motion框中。
Animator Controller中的圖示變成下面這個樣子:
拖動Blend
滑桿,可以看到后面3個狀態依次高亮(高亮表明在融合中的權重較高),且Inspector面板中的圖示也相應發生變化。
運行場景,這時拖動滑桿就不是很有作用了,因為這個滑桿并不會影響到真正Blend參數的取值。但我們可以手動修改Blend值來檢查動畫混合是否正確:
- 當
Blend
= 0時,角色播放靜止動畫; - 當
Blend
= 0.5時,角色播放步行動畫; - 當
Blend
= 1時,角色播放跑步動畫; - 當
Blend
處于0 ~ 0.5,或者0.5 ~ 1之間時,角色播放的動畫是融合的。
跳躍動畫
跳躍動畫的一般設計邏輯是這樣的:當某個按鍵被觸發時,角色動畫立刻切換到播放跳躍動畫,同時利用動力學或者直接編輯位置屬性的方式讓角色模型沿Y軸發生上升下降的運動模擬跳躍,然后檢測角色是否落地(碰撞體與地面發生接觸),如果落地,則立刻切換回行走動畫或站立動畫。
在Animator Controller中,我們沒辦法去移動角色或者檢測角色是否落地,只能做動畫的切換。
回到Base Layer,搜索出JUMP00
動畫片段,拖到Animator Controller中,修改狀態名稱為Jump
。
新建一個Bool類型的變量IsJump
。
從Localmotion
創建一個Transition指向Jump
,再從Jump
創建一個Transition指向Localmotion
。
選擇從Localmotion
指向Jump
的Transition,取消Has Exit Time
的勾選,并新建一個Condition,設置條件為:IsJump
= true。
同理選擇從Jump
指向Localmotion
的Transition,為其設置條件為:IsJump
= false。
這時如果我們運行場景,當手動設置IsJump
為true
時,角色會馬上開始跳躍,然后如果手動設置IsJump
為false
,角色馬上轉回運動狀態(Blend
參數設置為0.5或者1)。
但如果一直處于IsJump
= true,Jump
動畫播放完以后不會自動轉換會Localmotion
。如果想要自動轉換,可以再創建一個從Jump
指向Localmotion
的Transition,不取消
Has Exit Time
的勾選,也不設置任何條件。
運行場景,發現并沒有如我們所想的轉換動畫到Localmotion
,而是不斷重復Jump
動畫,我一開始認為這是因為JUMP00
動畫片段被默認設置為Loop循環播放了,所以我找到JUMP00
動畫片段所在的JUMP00.fbx
文件,發現其Loop Time
并沒有被勾選。其實關鍵原因還在于IsJump
= true這個設置。Jump
跳轉回Localmotion
之后,因為IsJump
依然為true,所以立刻又跳轉回Jump
了。這不是我們在Animator Controller中能夠解決的問題,我們留到后面再解決吧。
受打擊動畫和死亡動畫
這兩種動畫的邏輯都是在滿足特定條件下即激活,但與跳躍動畫的實現有一些不同之處。主要表現在受打擊和死亡都不需要使用Bool類型的參數來控制,只需要一個“開關”就好了,所以通常使用Trigger類型的參數來進行觸發。
新建兩個Trigger類型的參數:Hurt
、Die
。然后搜索DAMAGE00
動畫片段作為受打擊動畫。
這次我們從Any State
新建一個Transition到DAMAGE00
,設置無退出時間,設置其條件為Hurt(因為是Trigger,所以并無取值,是最方便的一種條件了)。
然后從DAMAGE00
創建一個Transition到Localmotion
,使其受打擊完畢以后繼續返回正常休息或行走姿勢。
大家可以測試一下先讓角色跳起再讓角色受傷,動畫混合得還挺不錯的。從
Any State
開始設置Transition挺方便,但也蠻容易出現奇奇怪怪的混合結果,大家要小心一點。
這兩個素材中我都找不到合適的死亡動畫,勉強拿DamageDown
用用,大家別忘了修改其所在的.fbx
文件的Avatar設置,并確保動畫的Loop Time
未勾選。
從Any State
新建一個Transition到DamageDown
,設置無退出時間,設置其條件為Die
,這次就不用再設置跳出的Transition了,因為這是死亡狀態了嘛。
這里會有一個小bug,因為既然從Any State
都可以過渡到DamageDown,那么從DamageDown
本身也可以,所以我們可以一再激活Die
Trigger來讓角色“死了又死”,不過這個bug很容易在腳本中被修復,這里也暫時不管他。
連擊
很多格斗游戲或者動作游戲中都有連擊(Combo)的概念,大概意思是如果連續攻擊判定成立,角色依次播放不同的攻擊動畫,以達到華麗的視覺效果。
我們這里也利用Animator Controller做一個簡單的連擊動畫邏輯。
在Animator Controller中單擊右鍵,選擇Create Sub-State Machine
創建一個“子狀態機”,更改名稱為“Attack”。
“子狀態機”相當于把一個復雜的狀態機的一部分“打包”起來,方便我們的設置。
雙擊進入Attack
,然后搜索出Jab
(左輕拳)、Hikick
(高踢)、Spinkick
(回旋踢)、RISING_P
(升龍拳)四個動畫片段(都在HQ Fighting Animation FREE素材中),拖到Attack子狀態機中。
新建一個名稱為Combo
的Bool類型參數,以及一個名稱為Attack
的Trigger類型參數。
依次建立Jab
到Hikick
、Hikick
到Spinkick
、Spinkick
到RISING_P
以及RISING_P
到Jab
的Transition,統統設置成有Exit Time,觸發條件為Combo = true。
然后在依次建立從這4個狀態到Exit
狀態的Transition,統統設置成有Exit Time,觸發條件為Combo = false。
這樣設置的意思是:如果“連擊”條件成立,則依次輪換進行輕拳、高踢、回旋踢、升龍拳四種攻擊,否則,就在當前攻擊動畫完成后進入“退出”狀態。
然后返回Base Layer,設置從Localmotion
到Attack
的Transition,無Exit Time,觸發條件為Attack
Trigger,同時設置從Attack
到Localmotion
的Transition,不做任何修改。
因為Attack子狀態機中的所有狀態都回歸到
Exit
狀態,所以從Attack
到Localmotion
的Transition顯示為灰色,表明是自然過渡。我們在Attack子狀態機中還可以直接設置某個狀態往Base Layer中的某個狀態的Transition,那就要連接到
(Up) Base Layer
狀態并自行選擇目標狀態了,我們這次不搞得這么麻煩。animation_62.png
測試發現,從Localmotion
過渡到Jab的時候幾乎看不到輕拳攻擊的動作,這是因為默認的過渡時間太長,導致出拳動作被“洗”掉了。修改Transition中的動畫過渡,讓其變得很短就可以了。
運行場景,設置Blend
為0.5,角色開始步行,然后勾上Combo
,再激活Attack
,角色開始“四連擊”,由于這幾個動作的動畫片段都沒有設置成原地動畫,所以實際在場景中是有位移的,看起來挺爽快。
取消Combo
的勾選,角色回歸步行動畫。
如果不希望有位移,可以為這幾個動畫所在的.fbx
文件設置Root Motion:在Animation設置中,找到Motion一欄,然后設置Root Motion Node
為<Root Transform>
,最后點擊Apply
確認修改。
控制Animation Controller
準備場景
- 安裝PlayMaker插件;
- 為場景添加一個地面Plane(10×10×10);
- 為unitychan游戲物體添加一個Rigidbody組件,一個Capsule Collider組件,修改參數以適合角色模型;
使用PlayMaker操控Animator Controller的參數
PlayMaker提供了一系列與Animator Controller有關的Actions:
我們暫時只關心與Animator參數有關的內容。
為場景中的unitychan添加一個Fsm,改名為AC_Test,我們用這個Fsm來測試一下如何控制Animator Controller的參數,從而操縱角色動畫。
在State 1中添加1個Set Animator Float
行為,設置使用speed
變量來控制Blend
參數:
然后在Variable面板中設置speed
變量在Inspector中可見,運行場景,現在就可以在Inspector中通過調整speed
變量來控制角色停走跑了。
可以再添加一個Get Axis Vector
行為,將方向輸入向量儲存為axis vector
變量,將Magnitude(可以理解為輸入向量的長度)儲存為magnitude
變量。修改Set Animator Float
行為的Value參數,改為使用magnitude
變量來控制Blend
參數值。
運行場景,現在可以通過“AWSD”或者方向鍵來操控角色從停到走到跑了。
Get Axis Vector
行為我們在設置角色運動控制中用得非常多,具體可以參見 PlayMaker簡單實例(2):角色與攝影機的運動控制 一文中的內容。在不做放大的情況下,Magnitude的取值會在0~1.414之間變化,單軸向最大值1,兩個軸向一起最大值為1.4左右(2的平方根)。
在State 1
中添加一個Get Button Down
行為,設置當“Jump”按鈕(默認設置是空格鍵為“Jump”按鈕)被按下時,觸發事件“Jump”,然后跳轉到State 2
。然后再添加一個Set Animator Bool
行為,設置Animator的IsJump
參數值為false。
在State 2
中添加一個Set Animator Bool
行為,設置Animator的IsJump
參數值為true,然后添加一個Wait
行為,設置為等待1.5秒之后觸發事件“Landing”,跳轉回State 1
。
一些解釋:
- 在
State 1
中添加Set Animator Bool
行為的目的是確保IsJump
參數值在State 1
中為false,否則不會停止跳躍動畫,這也解決了之前Animator Controller設置的一個小bug。Wait
行為中設置1.5秒的原因是Jump動畫大概在1.5秒的時候就完成了,這里這樣設置可以保證不會連續多次跳躍。更有效的做法是監測碰撞體是否碰到了地面,或者監測Rigidbody的Y軸速度是否大于某個數值(比如0.1)來確保跳躍動畫完成時切換狀態。
下面是關于攻擊的設置。首先在State 1
中添加一個Get Key Down
行為,設置當F
鍵被按下時,觸發事件“Attack”,跳轉到State 3
,同時在State 1
中用Set Animator Bool
行為設置Combo
參數值為false。
在State 3
中:
- 使用
Set Animator Trigger
行為,設置Attack
被觸發(所以一進入State 3
角色就開始攻擊); - 同時使用
Set Animator Bool
行為設置Combo
參數值為true,開始連擊; - 使用
Get Key Down
行為,設置當F
鍵被按下時,觸發事件“Attack”,重新進入State 3
; - 最后使用
Wait
行為,設置0.2秒后觸發Stop Attack
事件,跳轉到State 1
,結束攻擊狀態。
這樣做的效果是:如果F鍵被按得足夠頻繁(間隔 < 0.2s),則State 3
會不停重置,導致Wait
行為一直不能累積足夠時間來觸發Stop Attack
事件。
這只是一個非常簡陋的邏輯,大家可以自行探索更合理更有效的邏輯思路。
這樣,我們就制作了控制角色動作的交互邏輯,下面將這套交互與真正的角色運動結合起來。
刪除AC_Test或者使其不激活,為unitychan另建一個Fsm:
在State 1
中先設置角色的運動速度:
- 首先用
Get Axis Vector
行為獲得基于相機視角的方向輸入,儲存為input axis
變量,將Magnitude儲存為magnitude
變量 - 用
Vector3 Multiply
行為將input axis
變量放大,倍數參數使用新建的speed
變量(設置初始值為6) - 用
Set Velocity
行為給角色添加上運動速度,速度矢量使用input axis
,坐標空間選擇世界坐標(World
),勾選Every Frame
選項; - 在
unitychan
的Rigidbody組件中勾選x軸和z軸的旋轉約束,避免角色發生“側翻”。
然后在State 1
中設置角色的面朝方向:
- 用
Get Position
行為獲得Owner位置my position
; - 用
Vector Operator
行為將my Position
加上input axis
,儲存為aim position
(這是一個角色指向輸入方向的位置); - 用
Smooth Look At
行為讓角色平滑地朝向該位置,并確定Keep Vertical
選項被勾選以保持Y軸始終指向天空; - 最后使用
Set Animator Float
行為,設置使用magnitude
變量值來控制Blend
參數。
然后再按前面的做法依次添加上對跳躍、攻擊等動畫的控制。
最后對Animation Controller做一點小修改,刪掉從RISING_P
到Jab
的Transition,讓RISING_P
成為“終結技”。
受傷和死亡的動畫控制就留給大家自己完成吧。
實際應用中對于跳躍動畫的實現
真正要做個可玩的跳躍交互邏輯需要對角色的速度以及碰撞有所判斷。至少要像下面這樣有3個不同的狀態。我使用一個剛體(Rigidbody)小方塊來做演示:
State 1
中設置了小方塊的移動和自動轉向的交互邏輯,最后添加一個Get Key Down
來探測Space
鍵是否被按下,按下則跳轉到State 2
。
State 2
中利用Add Force
行為來實現跳起,也就是給角色一個向上的力量,我設置Force Mode為Impulse
可以保證這個力量的一致性和瞬時性。
在Get Velocity
行為中,我實時獲取Y軸速度,然后用Float Compare
行為監測其是否為負,為負代表小方塊在下降,這時候就可以跳轉到State 3
去判斷是否落地了。
State 3
中使用Collision Event
行為監測是否有標簽為“ground”的碰撞體與角色碰撞,如果有,則跳轉回State 1
結束跳躍。
最后把地面物體添加上標簽“ground”,這樣就可以用空格鍵控制小方塊跳躍了。