從零開始獨(dú)立游戲開發(fā)學(xué)習(xí)筆記(二十)-Unity學(xué)習(xí)筆記(八)M_Studio教程2D入門(一)

學(xué)完后過來提醒,整個(gè)教程是以教會(huì)你 unity 的一些基本操作,介紹各種功能為目的。并不能當(dāng)做 best pratice 來用,即使是修改了 3 次版本的手感,依然非常稀爛。整個(gè)教程充斥著,這里有更好的完善的解決方案,但是偏不用,就要用一些自己寫的,通過其他方式完成實(shí)現(xiàn)的代碼。但是其實(shí)很多時(shí)候換一種實(shí)現(xiàn)方式其實(shí)是為了介紹某個(gè)功能。比如巡邏功能,其實(shí)目的不是教你巡邏怎么做,而是教你如何在屏幕上顯示空物體的位置方便查找。
了解這個(gè)真正的目的我覺得對(duì)理解教程有很大幫助(避免踩坑吧也是為了)。這樣你看到迭代了 3 次的代碼的時(shí)候,就應(yīng)該把錯(cuò)誤的代碼也寫一遍,而不是直接用最新的。以及當(dāng)你看到一個(gè)功能實(shí)現(xiàn)的時(shí)候,你絕對(duì)不要去想噢原來這個(gè)功能要這么去做,而是去看用了哪些 unity 組件。
不過整體來講確實(shí)學(xué)的難受,也是比較久遠(yuǎn)的教程了。后來我看了下 brackeys 也有一個(gè)類似的教程,就流暢并且優(yōu)雅多了,如果有機(jī)會(huì)建議直接看 brackeys 的 2d 教程(去他的 ytb 頻道上找一個(gè)叫做 How to make a 2d game 的播放列表,國內(nèi)沒有搬運(yùn)(如果你在 b 站搜索 brackeys 2d 是可以搜到一個(gè)教程的,但是實(shí)際上 brackeys 有兩個(gè) 2d 教程,一個(gè)叫做 2d tutorial,還有一個(gè)就是剛剛那個(gè)。這兩個(gè)是不一樣的,b 站那個(gè)是分散的介紹各種 2d 功能,而不是一個(gè)完整的游戲流程))。
---------以下是正文--------------
開始學(xué)習(xí)著名的小狐貍了。
開頭相當(dāng)一部分是在 Brackeys 教程里已經(jīng)教授過的,就簡單提一下。
1. 新建項(xiàng)目,導(dǎo)入素材
新建一個(gè) 2D 項(xiàng)目。導(dǎo)入 asset store 里的素材(導(dǎo)入界面為 package manager)。
導(dǎo)入后在 asset panel 里可以看到導(dǎo)入后的文件。
1.1 Pixels Per Unit
在 asset panel 中點(diǎn)擊其中一個(gè)素材(在拖進(jìn)游戲畫面之前)可以在 inspector 里看到許多設(shè)置,首先講一下這個(gè) Pixels Per Unit。

其中,Unit 指的就是中央 scene panel 里的小網(wǎng)格。

因此 Pixels Per Unit 的含義就很明了,就是這個(gè)小網(wǎng)格代表多少個(gè)像素。
默認(rèn)的 100 很顯然太大了,畫面會(huì)非常小,不適合操作,因此在這整個(gè)項(xiàng)目過程中,我們統(tǒng)一使用 16 的 PPU。
操作之后把背景圖拖進(jìn)去。
1.2 TileMap
2d 場景設(shè)置中最常用的就是 TileMap。直接在 hierachy 中創(chuàng)建,在 2d object 子項(xiàng)中,選第一個(gè)矩形。新建后可以發(fā)現(xiàn)出現(xiàn)了一個(gè)網(wǎng)格。

進(jìn)行后續(xù)工作前可以把剛導(dǎo)入的背景圖隱藏掉。
1.2.1 Tile Palette
打開 window -> 2D -> Tile Palette。
名字和含義很直觀,把素材放進(jìn)這里,就可以在場景中用素材畫畫了。
在一切操作之前,首先要?jiǎng)?chuàng)建一個(gè)新的 palette,這是會(huì)彈出資源管理器頁面,因?yàn)?palette 相關(guān)素材都要放在一個(gè)文件夾里保管,因此這里可以新建一個(gè)文件夾用于存放該 palette。
導(dǎo)入 tileset 前,記得設(shè)置 PPU。
如果直接導(dǎo)入,會(huì)發(fā)現(xiàn)整個(gè)圖片都被放在一個(gè)格子里,也就是說整個(gè)圖片被識(shí)別為一個(gè)素材,這樣顯示燃是不合格的。因此除了更改 PPU,還得切圖。在更改 PPU 的那個(gè)地方,先把 Sprite Mode 改成 multiple(因?yàn)槲覀冇卸鄠€(gè) Sprite),然后進(jìn)入 Sprite Editor。
進(jìn)入之后再左上角選擇 slice,直接切割,會(huì)發(fā)現(xiàn)切成如下樣式:

切的很好,但是,有一個(gè)小問題,假設(shè)我們想切成下面這樣子呢:

切成這樣子的原因很簡單,因?yàn)榭梢詮?fù)用素材,更加靈活。 5. 方法就是在切的時(shí)候,不要選擇 automatic,而是 grid by cell size,這里我們選擇 16。切完后記得 apply。 6. 拖進(jìn) Tile Palette 中,導(dǎo)入剛創(chuàng)建的文件夾里。上方工具里選擇筆刷,然后下方點(diǎn)擊選擇 tile,然后鼠標(biāo)移動(dòng)到 scene panel 中就可以看到鼠標(biāo)變成 tile 了,接下來的操作就很直觀了。而且因?yàn)?tile 的大小和 PPU 都是 16,tile 可以和 grid 完全貼合,非常舒適。
1.3 成果
自由操作了一番后的成果:

2. 圖層
2.1 畫面比例設(shè)置
進(jìn)入到 game panel,這里是我們實(shí)際游玩的時(shí)候會(huì)看到的畫面。這里我們把比例調(diào)成 16:9。
此外,也可以對(duì) camera 進(jìn)行調(diào)控。比如說調(diào)控 size 的大小,也會(huì)影響游戲畫面的大小,也就是能看到的畫面變多(少)了。
2.3 sorting layer
也許你一經(jīng)發(fā)現(xiàn)了,之前導(dǎo)入的背景圖永遠(yuǎn)都在畫面最前方,遮擋住了主要場景。這很不好。解決方案有兩種:
改變 z 軸位置。(不易于管理)
使用 sorting layer。
之前 Brackeys 的教程里提到過 layer,layer 可以方便選取。比如說把一些無關(guān)緊要的東西分成 environment 層,鼠標(biāo)框選就無法選中這一 layer 的 object。
不過這里我們討論的是 sorting layer。
新建方式一樣,不過要在 sorting layer 里添加:


和其他設(shè)計(jì)軟件不一樣的地方是,在 sorting layer 的邏輯里,越靠近下方的 layer,其實(shí)是越靠近人眼的。(當(dāng)然,靠 index 來判斷就不容易出錯(cuò)了)因此我們這里新建兩個(gè)圖層:

我們給背景的 sorting layer 換成 layer 1(Background),tilemap 的換成 Frontground。(注意 tilemap 在 grid 的子項(xiàng)里,grid 沒法設(shè)置 sorting layer,要點(diǎn)開)
此外,即使是同一個(gè) sorting layer(比如說都是 background),也可以設(shè)置 order in layer 來改變前后位置。邏輯也是越大越靠前。

2.4 成果
成果如下,突然好看:

3. 角色建立
3.1 放入游戲
角色素材已經(jīng)有了,那么如何放到游戲中呢?
直接拖。
在 hierachy 里 create 一個(gè) sprite 的 object。然后把素材拖動(dòng)到 sprite renderer 組件中的 sprite 屬性里。
以上兩個(gè)的效果是一樣的。
如果發(fā)現(xiàn)角色比較小,仔細(xì)想一想原因,是之前提到過的內(nèi)容。
3.2 角色邏輯
雖然放進(jìn)去了,但是開始游戲后這個(gè)狐貍和背景沒有任何區(qū)別,沒有重力也沒有碰撞啥的,這些都要我們來添加。
3.2.1 重力
如果之前 Brackeys 的教程比較熟悉的話,這里應(yīng)該想到添加 rigidbody,不過這一次我們使用的是 rigidbody 2d。
3.2.2 碰撞
首先給小狐貍簡單點(diǎn)添加 box collider 2d,簡單地 edit 一下 collider 的范圍,不要那么大一個(gè)框。
給 tilemap 添加專用的 tilemap collider 2d。
3.3 成果
成果如下,草我放在了另一個(gè)沒有 collider 的 tilemap 上了。

4. 角色移動(dòng)
首先我們可以去 Project Setting -> Input Manager(老版本叫 Input) -> Axes 里看一下,這里是一些常用按鍵的設(shè)置。比如說進(jìn)到 Horizontal 里可以看到方向鍵的左右,a 和 d,以及一些重力的設(shè)置。
說看一下就真的就只是看一下,暫時(shí)不用更改這里的東西,先看第一步。
4.1 腳本
新建一個(gè)文件夾叫 Scripts,用于存放之后的腳本。
給 player 新建一個(gè)腳本,進(jìn)入 unity 編輯。
比起 Brackeys 的教程,這里給了一些不一樣的移動(dòng)控制方式,使用了剛剛看過的 Input Manager。

以上代碼為判斷水平方向是否移動(dòng),如果是 0,則說明沒有按 Input Manager 里設(shè)置的按鍵(默認(rèn)是左右方向鍵和 a,d)。-1 到 0 表示按了向左移動(dòng)的方向鍵,0 到 1 反之。
注意這里用的是 GetAxis,還有一個(gè) GetAxisRaw,后者只返回 -1,0,1。前者之所以會(huì)有小數(shù),這個(gè)其實(shí)是靈敏度,因?yàn)橛行┯螒蛴脫u桿玩的時(shí)候會(huì)有輕推和重推的區(qū)分,這是用來干這個(gè)的。不需要判斷輕重的時(shí)候就使用 GetAxisRaw。
寫好的代碼如下:

通過添加速度來移動(dòng)玩家。不過現(xiàn)在有一個(gè)問題,那就是移動(dòng)著移動(dòng)著玩家突然倒下了。
這是因?yàn)?tilemap 的 collider 不規(guī)則,人物很容易撞到。
為了解決這個(gè)問題,則去往 player 的 rigidbody 里,固定 player 的 z 軸旋轉(zhuǎn)。(倒下是沿著 z 軸旋轉(zhuǎn)的)。
小 tips:在游玩的過程中改變組件的屬性,退出后會(huì)還原。但是有一個(gè)方法,可以在組件的右上角齒輪處選擇 copy component,然后退出游戲后 paste component values。這樣方便游玩的時(shí)候快速 debug
5. 角色方向(重點(diǎn),包含一些重要的 C# 的語法知識(shí))
回到 unity,調(diào)整一下 scale 的 x 值為 -1,發(fā)現(xiàn)人物就這么朝著左邊了。(當(dāng)然追求細(xì)節(jié)的話,這樣做肯定是不夠的的,比如說異色瞳要換顏色,發(fā)型,背包的位置等等),不過現(xiàn)在就這么用。
上一小節(jié)提到了 GetAxisRaw 返回的正是 -1,0,1,正好可以用在這里,不用再做 if 判斷。

有幾點(diǎn)需要注意:
scale 不叫 scale 叫 localScale,在 transform 里。
不能直接給 localScale.x 賦值(y,z 同理),必須給外層的 localeScale 賦值。原因可以參考這篇文章。
類似地,有一個(gè)方法是 PlayerRigid.transform.localScale.Set(1,1,1)。看起來沒問題,也不會(huì)報(bào)錯(cuò),但是實(shí)際沒有效果。因?yàn)?localScale 返回的是一個(gè)類型為 velocity 的 copy 值,更改這個(gè) copy 值并不會(huì)影響原始值。為什么會(huì)有這種怪異的表現(xiàn)可以參考這篇文章,根本原因是 transform.localeScale.Set() 會(huì)先用 get 得到一個(gè) localeScale 的副本再用 set,而不是直接使用 set。實(shí)際上和第二點(diǎn)是一樣的,不過這里 C# 沒有給你報(bào)錯(cuò),因?yàn)檫@里是調(diào)用方法了,C# 還沒有那么智能判斷出方法里的邏輯錯(cuò)誤。
如果硬要寫的話可以這么寫:
var localeScale = PlayerRigid.transform.localScale; localeScale.Set(horizontalDirection, 1, 1); PlayerRigid.transform.localScale = localeScale;
總結(jié)下來其實(shí)主要還是理解兩點(diǎn):
Vector3 是個(gè)結(jié)構(gòu)體,而結(jié)構(gòu)體是值類型,無法被引用,調(diào)用方法的時(shí)候傳遞的只是一個(gè)副本。
執(zhí)行
transform.localScale = **
?的時(shí)候,調(diào)用的是?set
。而當(dāng)執(zhí)行?transform.localScale.**
?的時(shí)候(如?transform.localScale.**.x
),**
?是通過?get
?得到的,一旦 localScale 后面跟著一個(gè)點(diǎn),那就是使用了 get,如果這個(gè)屬性是值類型的話就要小心了,尤其是?struct
?這種值類型卻還可以使用點(diǎn)語法的。
6. 跳躍
跳躍和移動(dòng)差不多,只不過判斷改成了 GetButtonDown。GetAxis 也是可以的,但是如果一直按著跳躍鍵(默認(rèn)為空格鍵)不放,那么 GetAxis 會(huì)一直返回 1,是一個(gè)一直按住的概念。很明顯跳躍是一個(gè)一次事件,因此不能使用 GetAxis。代碼如下,結(jié)合了上一小節(jié)學(xué)到的知識(shí)點(diǎn)。

目前代碼有很大問題,因?yàn)榘存I判斷放在了 fixedUpdate 里,因此很多時(shí)候按下去了沒有被 catch 到,這個(gè)問題之前?Brackeys 的學(xué)習(xí)筆記里有講過,不再贅述。暫時(shí)只是為了方便跟著教程走這么寫。