用Unity做一個萌萌噠游戲(附資源)

作者:Yumir
哈嘍大家好我是Yumir。
從現(xiàn)在開始,我會制作一系列有意思的游戲/功能/原型。這些小項目的共同特點,是實現(xiàn)難度并不復雜,但趣味性又很強,特別適合用來學習。
(本文搭配視頻:【游戲開發(fā)大視野】(搬運熟肉)做了30多款UNITY游戲,我學到了什么食用風味更佳)
這第一期是個考反應(yīng)的游戲,看看效果圖便能瞬間明白主要玩法:

首先需要準備游戲素材。對喜歡親自動(zhe)手(teng)的我來說不是個事兒,我使用了PhotoShop+數(shù)位板自行繪制。沒有數(shù)位板的同學只用PhotoShop也可以做到。
我繪制的素材和原版有些不同,具體如下:

這里將咱親手繪制的資源奉上:
網(wǎng)盤鏈接:https://pan.baidu.com/share/init?surl=f-crQYmgBbaRtO4CN_FqVA
提取碼:1lq6
還是得說一句:請不要用于商業(yè)用途哦!
將準備好的游戲物體導入Unity引擎,下面開始正式游戲制作。
首先我們制作游戲菜單,也是游戲的第一個場景。
從預覽圖中應(yīng)該能看出來:本游戲的一大特征,是活潑生動的動畫效果。如果缺少這些效果那么游戲的魅力可以說是減少了一大半。
這些動畫效果都是可以通過Unity的Animator實現(xiàn)的,這部分我用的都是2D圖片精靈(Sprite),用UGUI也可以實現(xiàn),這里需要分別需要兩個動畫狀態(tài)機以及兩個腳本:
第一個動畫狀態(tài)機:拼圖表情動畫狀態(tài)機只需要添加一個默認的縮放動畫即可,這樣一來在游戲中該游戲物體就會一直自己縮放,不用我們操心(動畫在Unity的動畫窗口錄制)。

第二個動畫狀態(tài)機:當鼠標滑過屏幕中間的拼圖時,會出現(xiàn)由小到大的氣泡,這里也就需要一個氣泡狀態(tài)機,這里需要對氣泡的圖片做預先處理——我們有四個氣泡,分別在氣泡圖片的Sprite Editor中設(shè)置圖片的中心點到合適的位置。

這樣一來同一個狀態(tài)機可以給四個氣泡重復使用,該狀態(tài)機有兩個狀態(tài),一個是默認狀態(tài)一個是氣泡放大的狀態(tài),分別是兩個只有一幀的動畫,這兩個幀分別是Scale(0,0,0)和Scale(1,1,1),用一個bool類型的數(shù)值控制他們的狀態(tài)轉(zhuǎn)換,利用狀態(tài)轉(zhuǎn)換的自動補間動畫實現(xiàn)我們需要的效果。

在表情游戲物體上添加一個控制鼠標滑過事件的腳本,當鼠標進入游戲物體時使氣泡轉(zhuǎn)為放大狀態(tài)并修改游戲物體的貼圖,否則氣泡為默認狀態(tài),游戲貼圖也是默認貼圖。
? ??private?void?OnMouseEnter()
????{
????????isTouch?=?true;
????}
?
????private?void?OnMouseExit()
????{
????????isTouch?=?false;
????}
????private?void?BubbleMove()
????{
????????if?(isTouch)
????????{
????????????myBubbleAnimator.SetBool("touch",?true);
????????????gameObject.GetComponent<SpriteRenderer>().sprite?=?Resources.Load<Sprite>("Textures/PuzzleSB2");
????????}
????????else
????????{
????????????myBubbleAnimator.SetBool("touch",?false);
????????????gameObject.GetComponent<SpriteRenderer>().sprite?=?Resources.Load<Sprite>("Textures/PuzzleSB1");
????????}
????}
?
菜單中的四個拼圖的效果基本一致,但只有第一個拼圖點擊后會跳轉(zhuǎn)到游戲場景。這里我將以上游戲物體復制了三份,并分別放置到正確的位置更改為對應(yīng)的氣泡圖片,再另外新建了一個控制鼠標點擊事件的腳本,觸發(fā)場景跳轉(zhuǎn),同學們?nèi)绻幌矚g,也可以重新寫一個單獨控制第一個表情的腳本。
到這里我們已經(jīng)將一個看起來很酷的游戲菜單場景制作完畢。
接下來我們制作游戲中的第二個場景:游戲進行中的場景,由于游戲中有鏡頭抖動的效果,所以只能使用Sprite作為游戲物體。
場景中的其他圖片不用多說,場景中的拼圖分為左邊的拼圖和右邊的拼圖,左邊叫玩家右邊叫敵人好像不合適,就叫目標拼圖吧。
玩家拼圖的制作非常簡單,只要添加控制根據(jù)按鍵切換賦值的腳本即可。
目標拼圖是最好玩的,你以為我要用代碼寫拼圖的運動和碰撞嗎?不,錄制一個從右往左的動畫,在動畫的最后一幀加入事件,該事件調(diào)用GameManager的碰撞觸發(fā)方法就可以了,因為目的地的坐標不同,兩個類型的目標拼圖要分別錄制動畫。

目標拼圖有許多顏色,但是我又懶得畫很多個顏色的各種拼圖,看到這里你一定發(fā)現(xiàn)了目標拼圖的預制體是一個灰色帶陰影的圖,通過實驗我在這里用了兩個圖層(外圖層是拼圖的外框)通過對內(nèi)圖層的顏色更改實現(xiàn)不同顏色的目標拼圖。

其實這也是我畫的第一個素材,之后我哭著翻出了壓箱底的數(shù)位板。

說到核心游戲物體就不得不說核心游戲玩法,整個游戲的核心玩法是用左右按鈕(這里設(shè)置為A鍵和D鍵)改變玩家拼圖的狀態(tài)以接住屏幕右邊移動到玩家拼圖面前的目標拼圖,如果接不到就游戲結(jié)束,本文主要是對游戲效果進行復現(xiàn),并不對玩法進行改進。
那么問題是我們?nèi)绾闻袛嘤袥]有接到。這個問題很簡單,用一些數(shù)值類型枚舉值就可以搞定,但是我選擇用對象。

所以同學們這段如果理解不了可以不學,直接用枚舉值就好,好奇為啥這樣說的可以接著看一看。
這里畫了個不太規(guī)范的類圖來展示游戲中的腳本關(guān)系。

聲明一個拼圖類和三個不同拼圖子類,這幾個類不繼承MonoBehaviour,每個子類中重寫父類中的碰撞判斷方法,該方法返回一個布爾值,通過與GameManager中的靜態(tài)字段對比來返回結(jié)果,這里以左方向拼圖為例:
public class LeftPuzzle : Puzzle
{
????public override bool PuzzleIsTure(Puzzle puzzle)
????{
????????return puzzle == GameManager.Instance.rightPuzzle;
????}
}
?
另外三個腳本(玩家、目標拼圖、GameManager)分別持有拼圖對象,回憶上文,目標拼圖的最后一幀會調(diào)用一個方法,也就是圖中目標拼圖持有的“動畫幀觸發(fā)”,該方法調(diào)用整個游戲邏輯中的核心方法——“拼圖碰撞觸發(fā)方法”
public void PuzzleTrig(GameObject gameObject)
????{
????????Puzzle thePuzzle = gameObject.GetComponent<PuzzleMake>().puzzle;
????????//回收go
????????if (thePuzzle == leftPuzzle)
????????{
????????????leftPuzzleList.Add(gameObject);
????????}
????????else if (thePuzzle == rightPuzzle)
????????{
????????????rightPuzzleList.Add(gameObject);
????????}
????????//判斷對錯
????????if (player.myPuzzle.PuzzleIsTure(thePuzzle))
????????{
????????????AddScore();
????????????UpdateScoreText();
????????????winEffect.Play();
????????????SetPlayerFaceShow(false, true, false);
????????????scoreAnimator.SetTrigger("getscore");
????????}
????????else
????????{
????????????StopAllCoroutines();
????????????SpacePuzzle(puzzleList);
????????????dieEffect.Play();
????????????SetPlayerFaceShow(false, false, true);
????????????gameOver.Play("GameOver");
????????}
????????StartCoroutine(CameraShake());
????}
?
用GameManager腳本管理整個游戲的主要邏輯,將這個腳本設(shè)置為單例模式,
public?class?GameManager?:?MonoBehaviour{
????public?static?GameManager?Instance;
????private?void?Awake()
????{
????????Instance?=?this;
????}}
?
并聲明三個拼圖對象作為整個游戲所有拼圖所持有的對象,這三個對象分別是默認拼圖、左拼圖和右拼圖(默認拼圖很寂寞,因為只有一個拼圖要他),當拼圖碰撞時只要調(diào)用玩家拼圖持有的拼圖對象的碰撞判斷方法即可。
后面寫拼圖預制體的時候就遇到問題了,兩個預制體必須分別寫腳本,因為使用的是對象來標識拼圖,不能在Inspector面板中分別設(shè)置值(如果是數(shù)值就很簡單了),但問題不大,多寫兩個腳本就好了,這樣避免了很多if判斷,比較舒服。
---------------------------------------不想看的同學跳到這里-----------------------------------------
接下來我們來制作游戲中的動態(tài)效果,看圖分析,游戲中的動畫有玩家拼圖上的表情動畫和左下角的得分上的動畫以及游戲結(jié)束的破碎動畫,除此之外還有兩個粒子特效,加上每一次拼圖碰撞伴隨著的鏡頭抖動,都是很簡單的東西,組合起來有不簡單的效果。
1、動畫
玩家拼圖上的表情動畫:
原版是用眼睛放大縮小的狀態(tài)變化來表現(xiàn)方塊“未碰撞”“碰到對的”“碰到錯的”這三個狀態(tài),用Animator可以簡單搞定,為什么要特地提一嘴?因為我給自己挖了個坑。
我畫了三個表情。并且,第一個表情的眼睛是分兩張圖的,第二個表情眼睛是要放大的,第三個表情是可以不動的,我嘗試使用Animator管理他們,得到了一個鬼畜的方塊,然后我一拍大腿,寫了一個方法搞定了。


所有表情的動畫默認播放就行。我真佩服我自己。

左下角的得分上的動畫:
這個游戲物體是整個場景目前唯一使用到UGUI的地方,并且使用的是Unity新加入的TextMeshPro-Text,這里使用的是默認字體,感興趣的同學可以自行深入了解。
之所以在這里使用新組件主要原因當然也是因為動畫,該組件支持許多原版Text沒有的操作,比如“自動匹配大小”就是當前需要使用的功能,將“AutoSize”勾選并將字體最大值設(shè)置到足夠大,這樣一來錄制動畫就非常容易了。
該物體有兩個狀態(tài),默認狀態(tài)是普通的放大縮小動畫,得分時的動畫比較特別,這里用圖片展示。需要注意這里動圖里面有個錯誤就是沒有設(shè)置中心點,導致動畫記錄了中心點移動,這也是開始菜單的氣泡動畫強調(diào)的注意點,而我等到游戲做完寫文章的時候才注意到。
細心的同學還會發(fā)現(xiàn),游戲中的字體是有投影的,這個用新版組件是否能夠做到呢?其實和組件沒什么關(guān)系,復制一份Text調(diào)整顏色和位置,讓他們雙宿雙飛共同進退就好了。


游戲結(jié)束的破碎動畫:
當拼圖碰在一起時玩家按了錯誤的按鍵(或者沒有按按鍵),這時候需要播放死亡動畫,該動畫通過調(diào)節(jié)圖片的透明度和顏色完成,這里我用了UGUI的Image,理論上用Sprite應(yīng)該也能實現(xiàn),聰明的同學一定能自己做出來。
2、粒子特效
圖包里有對應(yīng)的貼圖,拖拽到粒子的TextureSheetAnimation上,Shape設(shè)置為Circle,然后調(diào)整其他數(shù)值把兩個粒子特效做出來就行了,這里我也沒法說,畢竟我都是試出來的。
3、鏡頭抖動
在GameManager寫一個控制鏡頭抖動的協(xié)程,在碰撞觸發(fā)方法調(diào)用他就是了,要什么自行車,沒什么好說的。
public IEnumerator CameraShake()
{
???Vector3 OrigPosition = shakeCameraTransform.localPosition;
???float ElapsedTime = 0.0f;
???while (ElapsedTime < shakeTime)
????{
???????Vector3 RandomPoint = OrigPosition + Random.insideUnitSphere * shakeAmount;
???????shakeCameraTransform.localPosition = Vector3.Lerp(shakeCameraTransform.localPosition, RandomPoint,
?Time.deltaTime * shakeSpeed);
???????yield return null;
???????ElapsedTime += Time.deltaTime;
???}
???shakeCameraTransform.localPosition = OrigPosition;
}
?
每一個動畫和特效都非常的簡單,但是綜合起來卻能讓人覺得效果特別炫,這正是這個游戲真正值得我們學習的地方。原游戲還有音效加持,效果更好,美術(shù)上的加持也是必不可少的。
最后簡單制作游戲的結(jié)束場景,這個場景里的游戲物體全都是UGUI,最終成績的顯示動畫和上一個場景的動畫類似,由于Game Manager是單例模式,所以存儲在該腳本的“得分”在游戲結(jié)束的場景中也能被調(diào)用哦~
新的東西是按鈕動畫效果——新建按鈕之后將組件的“Transition”設(shè)置為“Animation”,再點擊“AutoGenerateAnimation”,這樣Unity會自動幫你生成該按鈕的狀態(tài)機,再在對應(yīng)的狀態(tài)中錄制兩個不同的動畫(默認和鼠標懸停兩個狀態(tài)有不同動畫),就可以完成了。
Ctrl+D復制上文制作按鈕,換個貼圖再各自放到合適的位置,哦嚯!場景搭建完畢!游戲做完了?不!我們還要把三個場景連接起來,在對應(yīng)的地方寫上場景跳轉(zhuǎn):SceneManager.LoadScene("目標場景名稱")。
但是場景跳轉(zhuǎn)的時候,似乎只是屏幕一黑,沒有可愛的拼圖開合動畫啊,這個動畫當然也是要自己做呀。
當我們做好了一個過渡動畫之后又會發(fā)現(xiàn),在不同的場景中,過渡動畫結(jié)束后要做的事情都有些微的不同,那么我們要每個場景都寫一個腳本,做三套動畫的游戲物體嗎?當然不!這時候就是委托大展身手的時候啦。
public?delegate?void?SceneEvent();
????public?SceneEvent?SceneOpenEvent;
????public?SceneEvent?SceneCloseEvent;
????public?void?CloseGo()
????{
????????SceneCloseEvent?.Invoke();
????}
????public?void?OpenGo()
????{
????????SceneOpenEvent?.Invoke();
????}
?
這樣一來這個游戲就真正完成啦,是不是簡單而不枯燥,感覺自己又變強了呢~
下面開始技術(shù)總結(jié)(敲黑板)
第一、鏡頭有抖動的場景背景圖要畫大一點。
第二、動畫的幀數(shù)只要12幀就可以流暢播放,但是部分動畫效果會不好,這里設(shè)置的不太好。
第三、用動畫幀事件實現(xiàn)跳轉(zhuǎn)前播放動畫比用協(xié)程實現(xiàn)更靈活方便,但是如果狀態(tài)機過渡動畫沒有走到設(shè)置事件的幀就會不觸發(fā)。
第四、得分動畫本來是想在游戲進行場景做預制體在游戲結(jié)束場景中復用的,但是問題很大,重新做了,說明在制作原型的時候考慮的不到位。
第五、原游戲場景跳轉(zhuǎn)做了異步跳轉(zhuǎn),但是我沒有。
該游戲源碼已上傳到Github,總結(jié)完畢:https://github.com/peiyl/Puzzling
——神秘分割線——
歡迎加入游戲開發(fā)群歡樂攪基:869551769
有意向參與線下游戲開發(fā)學習的讀者可戳這里:http://www.levelpp.com/