從零開始獨立游戲開發(fā)學(xué)習筆記31--Unity學(xué)習筆記14--M_Studio 3DRPG教程 2 - 結(jié)束

哎,說真的,我真的很不想連續(xù)兩篇都戛然而止,搞得好像我很容易放棄一樣。但是不得不說這教程真的不適合我。(我其實想說的更狠點,但還是算了)。學(xué)的時候真的一鍋子火,太強行了。一個功能有他自己適合的用處,而不是用在毫不相關(guān)的地方告訴你還可以這么用,這樣是能做,可是有什么意義。
舉一反三,舉一反三,一都沒舉就直接給你反三,還把這三當成一來用,太難受了。我中間查閱了一大堆手冊。每次手冊上的官方例子都在打視頻的臉,我真的看不下去了。真想把當初找教程的我打一嘴巴子。
算了算了,也不是說教程多不好。只是不適合我,既有優(yōu)點也有缺點,我下面也分析了一下。大家看著辦吧。
-------以下是正文---------
學(xué)著學(xué)著逐漸開始意識到 M_Studio 的教程并不算很好。優(yōu)點在于有一套完整的流程,并給你介紹每一個功能,盡量面面俱到。缺點在于功能介紹的很淺顯,并且經(jīng)常為了介紹而強行做某個功能,代碼寫的也不行。
相比之下,國外教程的優(yōu)點在于功能介紹會附帶理論比較深入一些,代碼質(zhì)量也不錯。但是缺點在于要么就是完全分散的知識點,要么就是非常短的一個流程。
或者再極端點,官方手冊,真的完全分散,但是絕對正確。
1. 鼠標控制人物移動
1.1 mouseManager
鼠標作為之后將要經(jīng)常用到的操作(鼠標控制移動,控制攻擊等等),作者選擇在這里使用 mouseManager 來控制。
mouseManager 并不是 unity 自帶的某個東西和定義,而是完全由我們自己創(chuàng)建的一個控制器:創(chuàng)建腳本,再創(chuàng)建一個物體,將腳本掛載到物體上。所謂的 mouseManager 只是作者自己的一個命名而已,完全可命名為 mouseController 或者 cameraController 等,只要看得懂,隨你喜歡。
這里作者沒有講,因此我試圖自己理解:沒有選擇直接掛在 player 上,而是選擇掛載在一個單獨的空物體上,是因為鼠標并不只是控制對人物的移動,還包括其他和人物不相關(guān)的操作。
1.1.1 事件
假設(shè)我們要做鼠標移動,那么可以想象成這么一個過程。鼠標點擊觸發(fā)事件 -> 人物移動。還記得之前做 UI 的時候使用的 UI button 里面自帶的 on click 事件嗎,那里需要你綁定一個 object,然后選一個這個 object 里的方法,到時候觸發(fā)就會調(diào)用這個方法。事件就是這么個東西。
那么現(xiàn)在我們沒有 UI button 配置好的事件屬性,而是一個空物體,我們要如何使用事件呢?
可以想象,應(yīng)該會有一個類似的一個有參數(shù)的屬性或者方法,暴露出來后,在 unity 編輯器里看就會像是 UI button 顯示的那個樣子。
首先我們要引入事件類的 namespace,叫做 UnityEngine.Events。
新建一個繼承 UnityEvent 的類,因為我們要使用參數(shù)。

3. 代碼解釋:UnityEvent 后面跟著的是之后這個監(jiān)聽函數(shù)要使用的參數(shù),參數(shù)最多可以有 4 個,比如說?UnityEvent<int, Vecctor2, string, Vecctor3>
?這樣。然后創(chuàng)建的類繼承這個類,像是例子中的 EventVector 是一個很好的名字,這樣更容易區(qū)分類型。(注意,不要起類似 EventClick 這種代表動作的名字,因為這只是個類,以后所有只接收一個 Vector3 參數(shù)的事件都可以用這個類,并不一定是 click 相關(guān)。只有之后新建變量的時候才會起和動作相關(guān)的名字,而且命名方式也不是什么 eventClick,也應(yīng)該是 onMouseClick 這種命名習慣。)(至于 eventMove 這種命名就更偏了,正確命名和用法應(yīng)該是:繼承了 EventVector3 的 onMouseClick 事件變量綁定一個叫做 move 的方法)。
4. 既然創(chuàng)建了這么一個類,接下來就是在 MouseManager 類里新建這個類型的變量,這樣我們就可以在 inspector 里看到了。

5. 上面說的和真的一樣,但是其實并沒有看到。
6. 原因是數(shù)據(jù)格式問題,像是一些 int,string,gameObject 類型等確實可以被 unity 編輯器序列化并顯示在 inspector 中。但是很可惜,unity 無法序列化泛型。而我們通過繼承創(chuàng)建的類型又是高度自定義的(幾乎有無限種參數(shù)組合的可能性)。因此需要我們手動序列化使其起作用。
7. 解決方法是在前面加上?[System.Serializable]
。


1.1.2 綁定鼠標點擊動作
雖然我們起了個名字叫做 onMouseClick,但這只是個名字,我們要真正的把它和鼠標點擊的動作聯(lián)系起來。
1.1.2.1 Camera.ScreenPointToRay
顧名思義,這個方法的作用就是把屏幕點轉(zhuǎn)化成射線。
具體含義就是,把屏幕上的某個點的位置(也就是一個 2 維的坐標),轉(zhuǎn)化成從攝像機射到這個點的位置的射線。具體到游戲中就是,你點了某個地方,然后 unity 會自動計算你在屏幕上點的這個地方,對應(yīng)在游戲中的三維坐標系的位置,然后從攝像機發(fā)射一條射線到這個位置,把射線返回給你。
可以在 unity 手冊里找到這個方法,不過基本上是不說人話的。但理解了大概意思后可以在手冊里找到一些細節(jié),比如說它是怎么判斷你點的是哪個物體的
1.1.2.1.1 global rotation vs local rotation4
題外話,去 scene 里看一下,選取 camera,調(diào)整左上角的 global 為 local。意思是工具調(diào)整的坐標系的角度是什么樣的,下面兩張圖可以直觀看到區(qū)別:


上圖人都埋進土里了,平移工具還是一如既往保持和世界坐標一致。下圖則是會根據(jù)人物的旋轉(zhuǎn)改變角度。
1.1.2.1.2 繼續(xù)之前的話題
這個方法雖然好,不過依然要你去提供屏幕點,并不能讀取鼠標點擊的位置。所以我們要通過 Input 給參數(shù)。
看文檔可以知道這個方法返回一個 Ray 類型,因此我們創(chuàng)建一個變量來儲存。

私有變量命名使用 _ 前綴更容易區(qū)分。
Camera.main 獲取當前場景中被 tag 為 MainCamera 的攝像機。
雖然老師說直接在 update 里使用 MainCamera 的性能在 2020 版本后被優(yōu)化過,但在 tarodev(ytb 上的一個頻道) 的測試下緩存的速度依然要比不緩存快很多。
使用 Input.mousePosition 獲取鼠標位置。
1.1.2.2 Phsics.Raycast
不過射線只是射線,這條射線只是單純從攝像機到你點的地方之間的一條線。但是實際上我們還需要知道這條線上有沒有什么障礙物。想要獲取碰撞物體的信息,就要使用物理方法。
那么這里使用 Phsics.Raycast 方法(看文檔記得往下翻,有重載): 找到一種一個重載接受 ray 參數(shù),并且有一個 out 修飾的 RaycastHit 類型的參數(shù)(RaycastHit 查看文檔可以看到包含很多碰撞相關(guān)的信息),我們就使用這個:

out 修飾符作用可以查文檔,總之就是讓其變成引用傳遞,可以被修改。感覺是一個類似指針的概念。
需要提前創(chuàng)建一個 Raycast 變量,作為參數(shù)來儲存信息。
方法調(diào)用的時候也要使用 out 修飾。

1.1.2.3 鼠標點擊
剛剛雖然在 if 里沒有放代碼,但是 Phsics.Raycast 執(zhí)行了,就會更改 _hitInfo 的信息。
此外,剛剛做的事情,是在每幀的時候,讀取鼠標位置信息并拿到碰撞體的信息。不過我們其實沒必要每一幀的信息都要。我們只是想在鼠標點擊的時候需要。那么為什么剛剛要這么做呢,其實這是為了后面改變鼠標樣式的時候需要(也就是在鼠標移到不同物體上,會變成不同的樣子來告訴你這個物體是可以互動的)。
這就是為什么我們把 _hitinfo 放在外面,因為這樣我們鼠標點擊的時候就可以直接讀取 _hitinfo 了。 新建一個 函數(shù),用于處理之后的鼠標操作。

GetMouseButtonDown 的參數(shù),0 指的是鼠標左鍵。
如果射線沒有碰撞,則 this._hitInfo.collider 返回 null。
這里檢測如果碰撞到的是地面,就會 invoke OnMouseClick 事件。
?. 這個語法表明,只有前面不為空的時候才會進行后面的調(diào)用或者取值,這樣避免了事先判斷一次空值。可以參考文檔。
這里代碼的含義是,每當點擊并且碰撞體為地面的時候,執(zhí)行一次 OnMouseClick,參數(shù)為碰撞點。
然后我們回到 Unity Editor,添加一個事件,屬性設(shè)置為 NavMeshAgent.destination,NavMeshAgent 是下載素材自帶的一個組件,也可以手動添加??吹?Nav 就大概明白這個和上一篇文章中的 Navigation 自動導(dǎo)航相關(guān)。而 destination 也顧名思義就是目的點。這里我們設(shè)置后,每次鼠標點擊的時候,就會執(zhí)行 NavMeshAgent.destination,不過由于 destination 是一個屬性不是方法,所以此時 unity 執(zhí)行的操作為將 destination 設(shè)置成 this._hitInfo.point 的值,然后自動導(dǎo)航系統(tǒng)就會把人物移動到對應(yīng)位置。
2. 換教程
見前言。我是真的不理解為什么要用事件來做點擊。