是男人就下100層——Unity實現歡樂球球(下)鏈表對象池
來自專欄遊戲開發入門指南——Unity+34 人贊了文章
本篇難度:
前言
大家好。
我,來填上期挖出來的坑了。摸魚的日子過於舒服以至於差點就把更新給忘了。
進入正題,首先回顧下上期的內容。
上期傳送門:https://zhuanlan.zhihu.com/p/38546161
在上期的內容里我們只做了兩件事:
1.用Mesh實現了自定義的3D物體作為接下來遊戲內的素材。
2.用代碼計算簡易的模擬了小球的重力和反彈效果。
我們首先思考下還差哪些東西沒做:
1.控制邏輯與遊戲場景的生成復用。
2.分數的UI顯示與計算。
3.一些提升遊戲表現力的特效。
其中前兩項都是歸屬於遊戲邏輯的的範疇,並且也是遊戲的核心,所以我們放到一起實現。
本篇同樣會涉及到意想不到的知識點:用鏈表做對象池。
控制邏輯
原版遊戲是用手指滑動手機屏幕讓整個場景轉動,同樣的,我們可以用滑鼠拖動屏幕來控制場
景轉動,用代碼去實現其實非常簡單,每次Update獲取滑鼠這一幀和上一幀坐標的X值的變
化,用這個值計算旋轉。
using System.Collections;using UnityEngine;public class InputLogic : MonoBehaviour{ //記錄上一幀滑鼠的位置 Vector3 lastMousePos; void TouchRotate() { //滑鼠左鍵按下時轉動 if (Input.GetMouseButton(0)) { //計算這一幀滑鼠位置與上一幀的差值,然後以Y軸轉動 float moveX = (Input.mousePosition - lastMousePos).x; transform.Rotate(0, -moveX, 0); } } void Update () { TouchRotate(); lastMousePos = Input.mousePosition; }}
由於我們只想讓場景內物體橫著轉,所以計算出來的旋轉幅度後用這個值沿著Y軸旋轉。把腳本
掛在一個空節點上,場景內所有需要旋轉的物體掛到空節點下,則其下所有子物體都會跟著空
節點一起旋轉。
場景的生成與復用
上期內容中,我們已經能做到給定一個弧度,生成一個缺口大小與之對應的圓環了:
現在要做的就是在小球下落的過程中每隔一定高度就生成一個隨機弧度的圓環。但是為性能考
慮我們肯定不能無限制的生成,得在已經生成的環移動到攝像機的視野之外時重新拿來複用。
應該已經有聰明的同學想到可以寫一個對象池來實現這個功能,在小球與環的距離大於一個閾
值的時候把圓環加入對象池中,然後每次生成新的環時到對象池裡面去取。
這個做法當沒毛病,並且通常也是這麼做的。但我們今天用個更簡潔直觀的方法,用一個
鏈表來實現這個功能。
using System.Collections;using System.Collections.Generic;using UnityEngine;public class GameMode : MonoBehaviour{ public Circle PrefabCircle; public Transform Root; LinkedList<Circle> CircleQueue; LinkedListNode<Circle> curNode; public float gap = 8.0f; float lowestCircleY; Transform cam; Transform pillar; Ball ball; void Start () { cam = Camera.main.transform; pillar = GameObject.Find("Pillar").transform; ball = GameObject.Find("ball").GetComponent<Ball>(); CircleQueue = new LinkedList<Circle>(); CircleQueue.AddLast(GetNewCircle()); curNode = CircleQueue.Last; } Circle GetNewCircle() { Circle circle = Instantiate(PrefabCircle, Root); circle.Init(); return circle; } Circle GetNextCircle() { // 讓鏈表循環使用的演算法 LinkedListNode<Circle> next = curNode.Next; if (next == null) { // 如果達到了結尾就回到開頭 next = CircleQueue.First; } //如果圓環太高就隱藏等待復用 if (next.Value.transform.position.y > lowestCircleY + 2f * gap) { next.Value.gameObject.SetActive(false); } //如果鏈表的下一個環在場景中是隱藏的就返回這個環並在場景中顯示 if (!next.Value.gameObject.activeInHierarchy) { curNode = next; curNode.Value.gameObject.SetActive(true); } //如果下一個環在場景中顯示,生成新的環並添加到鏈表next之前 else { curNode = CircleQueue.AddBefore(next, GetNewCircle()); } return curNode.Value; } void Update () { while (lowestCircleY + gap > ball.transform.position.y) { var circle = GetNextCircle(); //每次得到新的圓環時改變高度 circle.transform.position = new Vector3(0, lowestCircleY - gap); circle.GenerateCircleByLevel(); lowestCircleY = circle.transform.position.y; } pillar.position = new Vector3(pillar.position.x, cam.position.y, pillar.position.z); }}
這段代碼的作用就是使用一個鏈表來達到場景復用的目的,就像這樣:
此處要注意的是鏈表並沒有成為一個環形,我們只是在下一個節點為null的時候把返回值賦值為
鏈表的第一個元素而已。
然後我們讓攝像機在球下落時跟著球一起移動,新建如下腳本掛載到主攝像機上。
public class ChaseBallCam : MonoBehaviour{ Transform ball; float baseY; float camOffsetY; void Start () { ball = GameObject.FindGameObjectWithTag("Player").transform; camOffsetY = ball.transform.position.y - transform.position.y; } void Update () { //當球的位置低於偏移值時讓攝像機和球一起動 float diffY = ball.transform.position.y - transform.position.y - camOffsetY; if (diffY < 0) { // 比必要的位置再低一些,可以防止抖動 transform.position += new Vector3(0, diffY -0.15f, 0); } }}
最後再把攝像機的Y軸坐標每一幀同步賦值給中間的圓柱,最後運行效果如下:
可以看到鏈表的總長度只有4個,但已足夠實現場景內物體的循環,如此一來場景的基本結構
就搭好了。
分數的計算與UI顯示
計算分數的演算法最關鍵的問題,是如果連續通過多個缺口且沒有觸碰到圓環,那麼獲得的分數是
累加的。
這裡的思路是用小球在圓環上反彈的時間來計算當次的下落獲得的分數。在Unity里通過調用
Time.time能夠獲取從遊戲開始到現在總共經過的時間。
我們在GameMode里每一次獲取下一個圓環的時間,就是小球經過當前圓環的時間。所以我們
可以把計分函數放在生成圓環的循環里調用。在球的腳本里記錄每一次反彈的時間,當最後一
次反彈的時間大於上一次計分的時間時,說明小球碰到了圓環,累加的計分需要重置。
void AddScore() { if (ball.lastBounceTime > lastAddScoreTime) { combo = 1; } else { combo++; if (combo > 9) { combo = 9; } } lastAddScoreTime = Time.time; var numObj = Instantiate(prefabNumber, canvas); numObj.GetComponent<ScoreAnim>().SetNumber(combo); totalScore += combo; totalScoreText.text = string.Format("Score:{0}", totalScore); }
然後在網上找一套數字的貼圖資源,從1-9按數字修改一下圖片的名字。
新建一個物體,在腳本里把圖片存入一個list中。在子物體中添加Image組件,每次新建時通過
傳入的分數來顯示與之對應的圖片,順便加上一些透明漸變和移動的UI動畫效果。別忘Image
組件只有在Canvas下才能正確的顯示圖片,所以場景中還需要新建一個Canvas。
sing System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;public class ScoreAnim : MonoBehaviour { Image numImage; public List<Sprite> nums; int num; void Start () { numImage = transform.Find("Image").GetComponent<Image>(); numImage.sprite = nums[num % 10]; numImage.CrossFadeAlpha(0, 0.5f, false); Destroy(gameObject, 1.0f); } public void SetNumber(int n) { this.num = n; } void Update () { transform.Translate(new Vector3(0, 50*Time.deltaTime, 0)); }}
最後在Canvas下新建一個Text組件顯示總共獲得的分數,在每次計分時更新分數。
遊戲表現力的提升
到這裡遊戲的主體基本就完成了,但是我們還有活干,適當添加一些特效或者擴展功能讓我們
的遊戲顯的更有趣一些。
比如可以在小球上加上一些特效:添加TrailRenderer組件實現尾跡,小球在圓環上反彈時
添加一張痕迹貼圖,改變小球的大小顯示反彈動畫等等。
也可以仿照原版加入小球經過圓環時掉落的效果,添加障礙物的圓環讓小球碰到就結束遊戲,
順便另外做個UI實現遊戲的重新開始。這些都可以在前面的基礎上稍微修改下實現,限於篇幅
就不詳細展開說明,可在後面的工程鏈接中下載下來研究。
大家完全可以按照自己的喜好來做一些自己認為更有趣的改動。
結束
這個小遊戲到這裡就做完了,這個工程難度總體來說是略微高於入門工程的,但是就算是初學
者慢慢來也能完整的做出來。希望能通過這個工程幫助大家減少一些初學者常有的畏難情緒,
即便缺少素材,我們也能用代碼去實現我們想要的功能。
本期文章工程地址:https://github.com/tank1018702/unity_002/tree/master/JumpBall
———大字報分割線———
如果有想系統學習遊戲開發的童鞋,不妨來圍觀一發http://levelpp.com/
遊戲開發交(jiao)流(ji)群也可以來一發:610475807
推薦閱讀:
※如何設計出好的MMORPG職業?(模板型文章)
※網頁遊戲開發引擎之戰,Adobe與Unity孰優孰勝? | GamerBoom.com 遊戲邦
※《不可思議之夢蝶》—安靜地修bug
※一:Unity ACT技能編輯器的製作(下)
※遊戲物語 - 開發團隊構成