從零開始獨立游戲開發(fā)學(xué)習(xí)筆記(二十五)Unity學(xué)習(xí)(十一)M_Studio2D入門(四)結(jié)束

很抱歉,最近花了一點時間稍微玩了下某款熱門游戲。
更新慢很抱歉,但實在是太好玩了,雖然我不喜歡宏大敘事,但是無奈它探索感做的太好,美術(shù)實在太棒。
游玩時間,敬請見證。

不過不管怎么說,至少小狐貍教程還是做完了(
1. Class 調(diào)用
首先按照創(chuàng)建青蛙的邏輯創(chuàng)建老鷹,很簡單就不說了。
1.1 爆炸效果
我們想讓敵人在消滅前播放這個動畫,爆炸效果的制作就不說了。著重說一下 animator 里之后的做法。 在 Animator 里設(shè)置一個 trigger 變量,而不是之前一直使用的 bool 變量。
1.1.1 trigger
trigger 和 bool 不一樣的是,當 trigger 被觸發(fā)后,會立刻回到 false 狀態(tài)。
1.1.2 在 player 里改變 enemy 的狀態(tài)
我們的消滅敵人的代碼是在 player 的 script 里的。但是我們要改變的并不是 player 的動畫而是 frog 的動畫。因此需要獲取到對方的 animator。
當然,因為在 collider 里已經(jīng)給出了碰撞體的信息,可以通過??來獲取,不過此時我們換一種思路,讓死亡的邏輯寫在 enemy 上而不是 player 上。方法就是我們在 frog 里寫一個 public 函數(shù),然后在 player 里調(diào)用這個函數(shù)。但是如果這么寫的話會遇上這么個情況:

ide 會報錯,因為對方并沒有 Die 這個方法。所以應(yīng)該在上面獲取 frog 這個 class。

以及,在 frog 里,當然不能在動畫后立刻銜接 destroy,因為要等動畫播放完。因此 Destroy 要使用 event 在動畫播放完后調(diào)用,這個之前講過就不再提了。
1.2 繼承
不可能每一類敵人都要寫一次銷毀函數(shù)。因此我們新建一個 enemy 類來讓這些功能復(fù)用。 這里就需要使用代碼了:
給 Enemy 添加 Animator 變量,并在 Start 中賦值。全部給 protected 是因為這兩個屬性方法會在子類中被調(diào)用。其中 Start 會重名因此加上 virtual 以便 override。

2. 再回看 Enemyfrog 類,把繼承的類從 monobehaviour 改成了 Enemy,并引掉了之前的 animator 變量和賦值的代碼,此外,還調(diào)用了 base.Start() ,因為不調(diào)用的話 Enemy 里的 Start 并不會調(diào)用。

3. 對 EnemyEagle 做同樣的事。
2. audio 音效
首先點開 main camera,會發(fā)現(xiàn)里面已經(jīng)有了一個 audio listener 的組件。這個是用來收集聲音的。
一般來說 audio 分為 listener 和 source。listener 自動集成在 main camera 上。然后在游戲里擺放各種 audio source 來讓 listener 聽。
2.1 人物自帶 BGM
給 player 添加一個 audio source 組件,然后將 BGM 拖進 audio clip 屬性中即可給 player 綁定一個隨時播放的 bgm。

2.2 消滅音效
要給敵人添加消滅音效。先添加一個音效,然后去掉 play on awake 和 loop 因為只想在爆炸的時候播放。 隨后在代碼里獲取 AudioSource 組件后調(diào)用 Play 方法即可。

2.2.1 快捷鍵重命名
ctrl + r (兩次),可以給當前變量批量改名。比 ctrl + f 的好處是會自動判斷是否同一個變量(畢竟是強類型語言)。
2.3 受傷音效,拾取物品音效
如果添加多個音效的話,GetComponent 如何分別呢。教程里給的方式是換用 public 變量,這樣在外面拖組件就行了。

3. 對話框 dialog
通過 panel 來寫,大部分之前基礎(chǔ)課程學(xué)過了,直接看效果:

大概流程為:
新建 panel,加上文字,修改字體。
在腳本里使用 setActive 來實現(xiàn) panel 的出現(xiàn)和消失。
UI 動畫通過錄制制作。
4. 趴下動畫
4.1 按鍵
Project Setting->Input 這里可以這樣添加新的按鍵:

動畫效果的制作就不再說了,直接略過。
4.2 碰撞體 DEBUG
有時候我們想在游玩的時候看到實時的碰撞體,這個時候就要啟用右上角 Gizmos 按鍵,并在 hierarchy 中選中想要觀察的物體。

4.3 碰撞體切換
我們可以看到蹲下的時候,碰撞體并不會改變。因此蹲下的時候,我們把頭部的碰撞體 disable 掉。

4.4 上方有障礙物的時候保持蹲下
當我們通過比較矮的地方時,如果松開下蹲鍵,人物會恢復(fù)站立,如圖:

因此我們需要添加判斷,這里我們采用 Physics2D.OverlapCircle 函數(shù),該函數(shù)會判斷一個點周圍是否含有某個 LayerMask。我們給 Player 添加一個點,然后判斷這個點周圍是否有障礙物即可:

5. 場景切換
這個在之前的基礎(chǔ)教程里講過,不過附帶一些小知識。
5.1 如何引入 GameManager
Unity 會把命名為 GameManager 的腳本圖標換成齒輪。GameManager 一般用于管理場景切換(重新開始,勝利,失敗,等等)。不過如何在其他腳本里引入這個腳本呢?
5.1.1 public
首先嘗試新 public 一個 GameManegr 類:

然后嘗試在外面把腳本拖進去,但是其實拖不進去,select 選項告訴我們整個項目都沒有能拖進去的東西:

原因就出在腳本只是腳本,是一個非靜態(tài)類,并不是實例。想要起作用還是要實例化。因此我們創(chuàng)建一個空物體,上面引入 GameObject 腳本:

現(xiàn)在我們終于就可以拖進去了。
(實際上也可以弄成靜態(tài)類或者靜態(tài)方法,這樣就不用在 unity 里拖來拖去了,但是我們要用 Invoke,Invoke 是非靜態(tài)方法,所以還是得實例化)

5.1.2 多個腳本的 object
一個 object 上可以掛載多個腳本,這種 object 同屬于多個類。比如說剛剛的空物體,我們可以添加一個 enemy 腳本,這樣 GameManager 也可以作為 Enemy 類被引入(當然沒有必要,這只是為了演示效果)。


5.1.3 tilemap 小縫隙
有些時候 tilemap 制作的地圖之間會有縫隙,此時將 grid 的 cell size 的 x,y 值調(diào)成 0.99 即可解決。
5. 2d 光效
項目創(chuàng)建的時候并沒有選擇 URP,這里就先做一個簡單的光效: 先創(chuàng)建一個 material,shader 換成 sprite/diffuse

將其賦與場景所有物體,會發(fā)現(xiàn)物體都變黑了,此時終于可以添加光效了,在 heirarchy 里直接添加一些簡單的即可,效果如下:

有時候光源不亮是因為其原理還是 3d 光照,到 3d 界面看一下是不是離界面太遠了,改一下 z 軸即可。
被光照的背景有了縫隙,反而需要我們把 grid 的 cell size 從 0.99 改回 1,如果還有可以去 project setting->graphic 里將抗鋸齒關(guān)掉。
6. 優(yōu)化代碼
6.1 敵人銷毀
銷毀動畫最后我們加了個觸發(fā)器來 Destroy 敵人。但是當時我們觸發(fā)器事件放在最后一幀后面的一些位置,我們應(yīng)該和最后一幀放一起,以讓動畫更加順滑。
7. 視覺差 parallax
接下來要做人盡皆知但又酷炫的視覺差效果。
7.1 cinemachine 更換跟隨
之前 cinemachine 的邊界是掛在 backgroud 上的,但是現(xiàn)在我們要讓 background 移動了,因此我們得換個地方。新建一個空物體,把 background 的 collider 組件和 position 復(fù)制過去即可。
7.2 代碼
新建一個 Parallex 腳本,以供復(fù)用。 public 一個 transform 用于記錄 camera 的位置。以及一個速率以供調(diào)整。最終代碼如下:

當 FollowRate 為 1 的時候,背景和人物同時移動,背景相對人物靜止。所以我們只要給背景和中景不同速率即可。
8. 主菜單
新建一個 scene,添加 UI panel。
如果不想用圖片,給 panel 的 background 設(shè)置為 None,再設(shè)置 color 純色背景。
如果要用圖片,將 background 換成我們想要的圖片,再調(diào)整 color 為我們想要的感覺。
想要給 button 綁定事件,就必須有一個 object,引用這個 object 上的函數(shù)。因此我們給 canvas 添加一個腳本,在腳本里寫函數(shù),然后從這個 object 里讀取函數(shù)。(無法直接讀取腳本,必須得有一個實例)
9. 暫停菜單
和主菜單區(qū)別不大,就是些 UI 操作。 不過涉及到暫停和音量調(diào)節(jié)。
9.1 暫停
通過改變 timescale 來暫停:

9.2 音量改變
通過 AudioMixer 來實現(xiàn):
創(chuàng)建一個 AudioMixer,發(fā)現(xiàn) inspecter 窗口屬性很少,因為主要在 window->audio->audio mixer 面板里。
可以看到,-80 是最小值,0 是正常音量。因此我們給之前 UI 里的 slider 調(diào)整下最小值和最大值。
在 player 當時創(chuàng)建的 audio source 里,給 output 里加上剛剛創(chuàng)建的 audiomixer,audiomixer 會自動創(chuàng)建一個 master 的 group,選那個就行。
然后我們想在腳本里修改,但是發(fā)現(xiàn) audioMixer 根本無法添加組件,也就無法添加腳本。那么對于這樣的情況,我們有一個辦法,就是將想要調(diào)整的屬性 expose 出去,這里我們想要修改 volume,因此右鍵 volume 屬性即可看到。

5. 然后在這里可以修改變量名:

6.隨后我們在代碼里寫一個函數(shù),這個函數(shù)即將作為 slider 的事件函數(shù):

SetFloat 用于設(shè)置某屬性的值,第一個參數(shù)為剛剛我們命名的變量,第二個為想要賦予的值。這里 value 是slider 事件提供的參數(shù),也就是滑動條的值。 7.添加的時候要小心,這里有兩個 SetMasterVolume,我們用的是上面的。如果使用上面的話,value 會由滑動條決定。如果選下面的話會讓你填一個固定值,value 為你預(yù)先設(shè)定好的這個固定值。
有些版本沒有上面的 dynamic float,此時可以在函數(shù)里直接獲取 slider 的值即可。畢竟 slider 也只是個組件。
10. 手感調(diào)整
10.1 FixedUpdate
之前提到過 FixedUpdate 是一定每一段時間執(zhí)行一次,但其實并不是。試想一下你電腦就是 literally 很卡,F(xiàn)ixedUpdate 怎么可能保證的了。它只能盡量去保障,并且如果你電腦不是經(jīng)???,而是某一段時間突然卡一下,那么接下來 FixedUpdate 會一下次執(zhí)行好幾次,以便保持 1s 內(nèi)執(zhí)行的次數(shù)是固定的。比如說本來應(yīng)該 0.2s 執(zhí)行一次,結(jié)果某次突然卡了 1s,那之后就會立刻執(zhí)行 5 次 FixedUpdate 來彌補。
10.2 跳躍手感調(diào)整,二段跳
用和下蹲的時候同樣的方法來檢測人物和地面的接觸,以此來判斷跳躍動作。
給人物添加一個地面監(jiān)測點。
代碼里添加 jumpPressed 和 jumpTimes,前者是為了在 update 里讀取在 fixedUpdate 里跳躍。后者是為了二段跳。

3. 檢測檢測點周圍是否有 ground 圖層。

4. fixedUpdate 代碼調(diào)整,添加了二段跳邏輯,以及 isGround 判斷來代替 isTouchingLayer。

5. 二段跳動畫處理

10.3 單向平臺
所謂單向平臺,就是可以跳上某一個平臺,并在跳躍的時候無視這個平臺障礙物。也就是說可以從平臺下方直接跳上來。
首先隨便創(chuàng)建一個平臺。創(chuàng)建 collider。
勾選 collider 中的 used by effector
添加一個 platform effector 2D,沒錯,單向平臺是一個內(nèi)置組件,不需要我們手動實現(xiàn)。
勾上 use one way 即可。
10.4 bug 修復(fù)
(這里內(nèi)容來自 Brackeys)
10.4.1 從高處跳下會被卡?。?/h1>
這是因為速度太大,碰撞的那一幀沒有判斷到,在 player 的 rigidBody 里將 collision detection 改成 continuous。
10.4.2 卡在平臺邊緣
這是因為頭部的 box colllider 比身體的 circle collider 大,這樣頭部就會被卡住。 修改方法就是把身體的 collider 調(diào)整為略微大于頭部即可。
11. 音效管理 SoundManager
作者覺得 player 身上掛了太多 audioSource 了。想解離出來:
首先和 GameManager 一樣,先創(chuàng)建一個 object,上面創(chuàng)建 SoundManager 腳本。
添加一個 AudioSource 組件,我們想讓之后通過更換這個組件的 clip,再播放,達到只使用一個 audio source 的效果。


把之前的 audio 相關(guān)組件代碼都刪掉。
添加播放 bgm 的方法。

5.那么之后想要播放 bgm 的時候,只需要找到 gamemanager 播放即可。
11.1 單例模式
之前使用 gamemanager 的時候,每次我們都要引入 gamemanager 那個 object,拖拽再調(diào)用。
我們當然也可以吧 gamemanager 或者 soundManager 設(shè)置為靜態(tài)類,這樣就不用每次拖拽了。(前者因為要用 invoke 還不能用靜態(tài)類)
不過除了靜態(tài)類,還有一種方式不用引入也能調(diào)用方法,那就是單例模式。 將代碼改成如下:
(注意:單例模式依然要實例化掛載一個空物體上,只是不用拖拽了)

注意標紅的位置:
首先創(chuàng)建了一個靜態(tài)變量,類型為自身。
在 awake 生命周期函數(shù)里讓 instance 等于 this。也就是 instance 為實例本身。
也就是說,這個類有一個靜態(tài)變量可以被外面調(diào)用,而這個靜態(tài)變量里儲存的值剛好是自身的實例化。因此可以通過這個 instance 來調(diào)用方法。因為 instance 就是一個 soundManager 的實例。
接下來我們就可以非常簡單地播放 bgm 了,如下圖,無需 public 后拖拽:

同樣的方式我們可以用來創(chuàng)建剩余的 audio,甚至還可以把之前的 GameManager 也優(yōu)化了。
11.2 字段優(yōu)化
剛剛創(chuàng)建的那么多 clip,其實不太想設(shè)置成 public,不然代補補全東西太多了,但是我們又想在外面拖拽,那么就需要用 SerializeField 字段了:

這樣即使是 private ,也可以在外面拖拽了,但是仍然能夠保持在別的代碼里無法訪問的限制。
11.3 BGM 同時播放
剛剛把bgm放進去其實并不好,因為只能同時存在一個聲音,如果跳起來的話,bgm 就沒有了。因此 bgm 應(yīng)該單獨拿出來。

11.4 鎖定 inspector
如果實際操作會發(fā)現(xiàn),每次換音頻,insepector 都會變一下,每次都要點回成 soundManager 的 insepector,很麻煩。可以選擇 inspector 右上角的鎖來鎖定住。
12. build
之前基礎(chǔ)教程里很詳細了,不再贅述。