Unity快速上手系列之3:《反應堆》

Unity快速上手系列之3:《反應堆》

來自專欄遊戲開發入門指南——Unity+30 人贊了文章

《反應堆》(STACK)是手機上一款獨特而又中毒性超強的遊戲,在taptap上的頁面地址如下:

反應堆 | TapTap發現好遊戲?

www.taptap.com圖標

遊戲的規則極其簡單,就是將板子一層一層的往上堆,堆的過程中需要盡量與下層板重合,沒重合的部分就會被削掉,隨著失誤越來越多,堆積的面積越來越小,最終game over。

如果能連續做到完美重合,音效會和諧地逐步升高音階,讓人從生理上欲罷不能。

今天我們就來實現一下這款遊戲。開局一堆圖,先看看效果:

方塊在塔頂上方來回移動

通過玩家控制讓方塊在適當時機落在塔頂,出去的部分將被切割,塔的高度+1,頂面積會越來越小

中途可以隨時按暫停,方塊將變成一個重啟按鈕供玩家點擊(文本寫錯了,請不要在意)

方塊落在塔頂之外則遊戲結束。大俠請重新來過

通過以上幾張效果圖,大概可以看出來這個遊戲的主要功能:

--方塊移動:方塊會輪流從兩邊輪流來回移動;

--方塊切割:方塊落在底座上時,出去的部分會被切割掉,落上去的部分會成為新的塔頂;

--重置方塊:每次落下後重置方塊的尺寸位置,尺寸和塔頂相同,位置比塔頂高一個身位;

--鏡頭跟隨:相機隨時跟隨最上層底座;

--顏色漸變:方塊的顏色會隨機往某個方向漸變;

--底座銷毀:遊戲結束時所有底座會從上往下被銷毀;

大概知道了流程後就開動起來吧。

場景搭建

一、創建一個Cube作為底座,位置放在(0,0,0),縮放尺寸設為(5,0.5,5),取名為TopFloor,因為目前只有它一層,所以它就是頂層;

尺寸可以自定義

二、創建一個空物體Pedestals來管理運行後的所有底座:

將來Pedestals可以掛上底座管理的腳本

三、創建分數Text:

四、創建移動方塊MoveCube,尺寸和位置無需設置,代碼裡面再來初始化,再創建一個3DText取名Texter,做方塊的子物體,調整好位置:

3DText的創建: Hierarchy面板點擊右鍵==>3DObject==>3DText

Texter創建後設為禁用狀態

預製體導入:遊戲中產生的各種粒子特效和方塊切割下來的邊角料都是做的預製體

撞擊特效:AT_Field

爆炸特效:Boom

誕生特效:NaissanceEffect

落下特效:SpecialEffect

邊角料預製體:Cffcut

粒子特效可以自定義

代碼編寫

一.初始化:

遊戲開始的時候,需要初始化移動方塊MoveCube的位置和大小,不僅如此,在效果圖裡也看到,每次方塊底座往上疊加一層,移動方塊位置都要重置一下,而每次重置後都比頂層底座高一層。

就是說,初始化移動方塊是少不了頂層底座的信息的,想到這代碼也就不難寫了:

GameObject top; //聲明頂層void Initialization() //初始化移動方塊 { //獲取頂層,及頂層的位置 top = GameObject.Find("TopFloor"); Vector3 pos = top.transform.position; //將頂層的大小賦予自身 transform.localScale = top.transform.localScale; size = transform.localScale; //記錄自身大小 //初始化位置 transform.position = new Vector3(pos.x, pos.y + size.y, pos.z - 10); }

上面的方法是用查找名字TopFloor的方法尋找頂層。在之後的代碼中如果每次產生新的頂層,就把新頂層的名字改為TopFloor,而原先的頂層改為其它名字,以保證查找的準確性。

二.方塊平移

方塊初始化之後,接下來就是往前走。

在遊戲中方塊是以前後來回移動的方式運動,如果用腳本來實現,我們可以在每次初始化後記錄一下當前位置為初始位置,判斷當方塊往前走到一定距離,則往回走,走到初始位置後,又往前走,如此循環。可以使用一個Bool值來決定方塊的前後移動狀態:

Vector3 initialPos; //初始位置(誕生點) bool forward; //是否往前走 public float speed; //平移速度 void TranslationFun() //平移方法 { if (forward) //如果確定往前走 { transform.Translate(0, 0, Time.deltaTime * speed); //前進 if (Mathf.Abs(transform.position.z - initialPos.z) >= 20) //前進超過20m forward = !forward; //變為後退狀態 } else //如果往後退 { transform.Translate(0, 0, -Time.deltaTime * speed); //後退 if (transform.position.z <= initialPos.z) //如果退回到初始位置 forward = !forward; //又往前走 } } }

每次初始化時可以記錄一次初始位置,將forward狀態定為True。

三.方塊疊加

本遊戲的核心玩法就是當移動方塊經過頂層底座上方時,玩家需要在適當時機讓方塊儘可能地與完整得落在頂層方塊上,多出去的將會被削掉,而如果移動方塊落在頂層之外,則遊戲直接結束:

void FallingFun() //落下方法 { //z軸偏差大於自身z軸尺寸,說明沒落在頂層上 if (Mathf.Abs(transform.position.z - top.transform.position.z) >= size.z ) { //遊戲結束 } else //否則就視為成功落在頂層底座上 { //生成新頂層和邊角料 //將舊頂層的名字轉移給新頂層 //移動方塊會根據新頂層的位置初始化一次 } }

上面代碼中的遊戲結束方法之後再來做。

四.方塊切割(重點)

1.在遊戲中方塊成功落下時,看上去的表現是被「切割」了,其實我在這裡的實現中採用了一個曲線救國的方法。

我重新創建了兩個方塊。一個疊加在頂層成為新頂層,另一個則成為邊角料被處理,生成這兩個方塊的位置和大小則根據移動方塊落下時與頂層方塊產生的偏移量來確定。

採用這種方法新方塊的生成又分為兩種情況,請看下面兩張圖:

移動方塊z軸坐標大於頂層方塊z軸坐標時

移動方塊z軸坐標小於頂層方塊z軸坐標時

2.我們就拿第一種情況用一張圖來分析兩個新方塊生成時的位置與大小:

第二種情況也可以參考此圖

3.通過畫圖分析,我們的代碼可以這樣來寫:

//生成新頂層和邊角料(方法參數為頂層和移動方塊) public GameObject CreateNewFoundationAndCffcut(GameObject top, GameObject moveCube) { //獲取頂層位置 Vector3 tPos = top.transform.position; //獲取頂層大小 Vector3 tSize = top.transform.localScale; //獲取移動方塊位置 Vector3 mPos = moveCube.transform.position; //創建一個Cube做新頂層 GameObject newTop = GameObject.CreatePrimitive(PrimitiveType.Cube); //載入一個邊角料預製件 GameObject cffcut = Instantiate(Resources.Load("Prefab/Cffcut") as GameObject); //獲取移動方塊與原頂層z軸位置偏差 float z_Offset = Mathf.Abs(mPos.z - tPos.z); //新頂層大小(原頂層-偏差) newTop.transform.localScale = new Vector3(Mathf.Abs(tSize.x - x_Offset), tSize.y, Mathf.Abs(tSize.z - z_Offset)); //新頂層位置和邊角料大小及位置(受位置狀態影響) //邊角料大小(原頂層x值,原頂層厚度,z軸偏差值) cffcut.transform.localScale = new Vector3(tSize.x, tSize.y, z_Offset); if (mPos.z < tPos.z) //當移動方塊z軸值小於原頂層z軸時 { //新頂層位置(原頂層x值, 原頂層y值 + 原頂層厚度, 原頂層z值 - 偏差值/2) newTop.transform.position = new Vector3(tPos.x, tPos.y + tSize.y, tPos.z - z_Offset / 2); //邊角料位置(原頂層x值,原頂層y值 + 原頂層厚度,原頂層z值-(原頂層z值/2+偏差值/2)) cffcut.transform.position = new Vector3(tPos.x, tPos.y + tSize.y, tPos.z - (tSize.z / 2 + z_Offset / 2)); } else//否則,當移動方塊z軸值大於等於原頂層z軸時 { //新底座位置(原頂層x值, 原頂層y值 + 原頂層厚度, 原頂層z值 + 偏差值/2) newTop.transform.position = new Vector3(tPos.x, tPos.y + tSize.y, tPos.z + z_Offset / 2); //邊角料位置(原頂層x值,原頂層y值 + 原頂層厚度,原頂層z值+(原頂層z值/2+偏差值/2)) cffcut.transform.position = new Vector3(tPos.x, tPos.y + tSize.y, tPos.z + (tSize.z / 2 + z_Offset / 2)); } } return newTop; //返回新頂層 }

因為邊角料創建後即刻就會被銷毀(銷毀方法我們在後面來解決),所以我們把新頂層作為返回值。返回後的新頂層可以重新取名為TopFloor,方便移動方塊初始化,這個方法可以在方塊成功落下時調用。

五.邊角料銷毀

上面我們講到邊角料創建後就會被銷毀(真杯具),那麼我們可以專門為它寫一個腳本掛在邊角料預製體上:

// 掛邊角料預製件上public class DeleteCffcut : MonoBehaviour{ void Update() { Color a = GetComponent<MeshRenderer>().material.color; //獲取顏色 a.a -= Time.deltaTime * 0.5f; //a值逐漸減小 //將改變後的顏色賦值給自身(顏色逐漸透明) GetComponent<MeshRenderer>().material.color = a; if (a.a < 0) //當透明度小到一定時候 Destroy(gameObject); //銷毀自身 }}

六.計分

之前我們搭建場景是創建了分數Text,分數的統計很簡單,只要方塊成功疊加一次,分數加一即可:

void AddScore(Color cubeColor) //加分方法 { //根據名字獲取分數對象轉成整數類型,+1後再轉成字元串類型賦予自身 Text score = GameObject.Find("Scroe").GetComponent<Text>(); score.text = (System.Convert.ToInt32(score.text) + 1).ToString(); }

計分方法和新頂層的創建應該是同步的,我們可以把兩個方法放到一起去執行。

七.攝像機跟隨

1.看效果圖,方塊每次疊加攝像機會跟著往上走一層,其實攝像機要做的工作很簡單,時刻關注名叫TopFloor(頂層底座)的物體即可,因為TopFloor隨時在易主,即每次產生新的頂層,新頂層就會將TopFloor的名字賦予自身,感覺攝像機就像一個勢利眼一樣,攝像機我們也為他單獨寫一個腳本:

//掛攝像機上,時刻盯緊頂層public class CameraFollow : MonoBehaviour{ public float height; //攝像機與頂層相對高度 public float followSpeed; //攝像機跟隨頂層速度 void Update() { if (GameObject.Find("TopFloor")) //如果場景中有該物體,始終跟隨 { //獲取頂層 Transform topFloor = GameObject.Find("TopFloor").transform; //攝像機始終看向頂層方向 Quaternion dir = Quaternion.LookRotation(topFloor.position - transform.position); transform.rotation = Quaternion.Lerp(transform.rotation, dir, Time.deltaTime * 5); //攝像機跟隨頂層高度 Vector3 cameraPos = transform.position; //獲取攝像機坐標 //攝像機時刻比頂層高出一定高度(跟著頂層上升) cameraPos.y = topFloor.position.y + height; //攝像機漸變到指定高度 transform.position = Vector3.Lerp(transform.position, cameraPos, Time.deltaTime * followSpeed); } }}

做完上面的工作,遊戲的核心邏輯部分基本就完成了,剩下的工作就看個人喜好了。

八.攝像機旋轉縮放

由於本人覺得攝像機的功能不應該止於此,所以又順便給它豐富一下,讓它可以繞頂層Y軸旋轉,多角度進行遊戲,還增加了鏡頭縮放的功能:

//按住滑鼠右鍵移動進行繞頂層旋轉 if (Input.GetMouseButton(1)) { float h = Input.GetAxis("Mouse X");//獲取滑鼠水平移動 transform.RotateAround(topFloor.position, Vector3.up, h * Time.deltaTime * rotateSpeed); } //使用滑鼠滑條拉近拉遠鏡頭 float slider = Input.GetAxis("Mouse ScrollWheel"); //獲取滑條滾動信息 GetComponent<Camera>().fieldOfView -= slider * Time.deltaTime * zoomSpeed;

把這幾段代碼放到攝像機腳本的Update的if條件下,毫無違和感。

九.結束遊戲

當移動方塊沒有成功落在頂層底座上時,遊戲結束。

遊戲結束的方法其實有很多,比如直接顯示遊戲結束的字幕。我這裡腦洞大開地採取了讓所有底座爆炸的方式。

具體的思路,是創建一個集合來管理所有底座。遊戲開始時,就將第一個底座添加進集合,在遊戲過程中,每創建一個新頂層(新底座),就將新頂層添加進集合中,當遊戲結束時,讓這個集合內的底座從後向前(遊戲場景中從上往下)依次原地爆炸,相關代碼如下:

// 底座管理類,掛Pedestals上public class PedestalManage : MonoBehaviour{ public List<Transform> pedestals = new List<Transform>();//創建一個集合用來管理所有底座 float timer = 0; //創建計時器 void Start() { pedestals.Add(GameObject.Find("TopFloor").transform);//首先將初始頂層底座添加進集合 } public void AllPedestalBoom() //所有底座爆炸 { //開始計時 timer += Time.deltaTime; if (timer > 0.05f) //每0.05秒炸掉一層 if (pedestals.Count > 0) //如果集合內不為空 { Transform top = pedestals[pedestals.Count - 1]; //獲取頂層(集合最後一位) if (pedestals.Count > 1) //如果底座有兩層以上 pedestals[pedestals.Count - 2].name = top.name;//把頂層名字給下一層 //將頂層從集合中移除並銷毀 pedestals.Remove(top); Destroy(top.gameObject); timer = 0; //計時清零 } }}

之前創建初始底座時就有一個叫Pedestals的空物體來管理它們,此腳本就可以掛在Pedestals上,在遊戲中每次創建了新的底座,別忘了把它添加進集合中。

十.顏色漸變

1.每次方塊成功疊加後,移動方塊的顏色會逐漸變化,本人在剛開始構思的時候也有點懵,經過一番推敲,最後決定用下面的方法來實現:

2.先說思路,初始狀態時方塊為白色,是因為初始化時移動方塊的RGB值都為最大值:

初始化時移動方塊的RGB值都為最大值,所以初始狀態為白色

3.隨機抽取一個值遞減,不同的值遞減會讓方塊往不同顏色轉換:

減少R值的結果

減少G值的結果

4.為了實現顏色轉化的隨機性,需要隨機抽取一個值進行遞減,當它減為0時,再在剩下兩個值中隨機挑一個遞減,直到剩下的那個。我的實現方法是,把RGB三個值放進一個數組中,然後打亂數組索引順序:

float[] rgb = { 1, 1, 1 }; //聲明rgb顏色數組(在代碼中RGB值在0~1之間)int[] rgbIndex = { 0, 1, 2 }; //創建顏色數組的索引也放在一個數組中 int[] RandomIndex() //將數組內元素順序隨機打亂 { //創建一個集合1,將索引數組的值給它 List<int> list1 = new List<int>(); for (int i = 0; i < rgbIndex.Length; i++) { list1.Add(rgbIndex[i]); } //再創建一個集合2,把集合1的元素順序隨機打亂賦給集合2 List<int> list2 = new List<int>(); for (int i = list1.Count; i > 0; i--) { int index; index = Random.Range(0, i); list2.Add(list1[index]); list1.Remove(list1[index]); } //將打亂後的索引成員重新賦值給索引數組 return list2.ToArray(); }

此方法需要在腳本開始時就打亂一次顏色索引數組的索引順序,一開始就處於隨機狀態。

5.使用打亂後的索引在顏色數組rgb中找出相應的值,依次進行遞減,當三個值都為0時,顏色為黑色:

6.當判斷三個值都為0時,又依次遞加,遞加也採用同樣的隨機方式,遞加滿了再遞減:

bool reduce = true; //顏色rgb遞減void ColorGradualChange() //顏色漸變 { //逐漸減少r,g,b的值 if (reduce) { if (rgb[rgbIndex[0]] > 0) rgb[rgbIndex[0]] -= 0.1f; else if (rgb[rgbIndex[1]] > 0) rgb[rgbIndex[1]] -= 0.1f; else if (rgb[rgbIndex[2]] > 0) rgb[rgbIndex[2]] -= 0.1f; else //當三個值都小於0時,變為逐漸增加 { reduce = !reduce; //改為逐漸增加 rgbIndex = RandomIndex(); //再次打亂索引順序 } } else //逐漸增加r,g,b的值 { if (rgb[rgbIndex[0]] < 1) rgb[rgbIndex[0]] += 0.1f; else if (rgb[rgbIndex[1]] < 1) rgb[rgbIndex[1]] += 0.1f; else if (rgb[rgbIndex[2]] < 1) rgb[rgbIndex[2]] += 0.1f; else //當三個值都大於1時,變為逐漸減少 { reduce = !reduce; //改為逐漸減少 rgbIndex = RandomIndex(); //再次打亂索引順序 } } }

這個方法也是方塊疊加一次調用一次,所以要放在恰當位置,比如和加分方法放一起也是可以的。

十一.移動方塊飛向鏡頭

看起來很炫酷,實現起來卻很簡單,我們假想攝像機前有一個方塊,顏色為白色,坐標為***,尺寸為***(可以通過後期自己調試決定實際坐標大小),有了這個方塊之後只需要將移動方塊MoveCube的坐標大小及顏色變換成該方塊就行了:

//由於並沒此方塊,所以方法參數都靠自己編(參數有位置,尺寸,旋轉,顏色)void Transformation(Vector3 pos, Vector3 local, Vector3 euler, Color color) //變換 { //移動方塊往該處飛行轉換 transform.position = Vector3.Lerp(transform.position, pos, 0.1f); //漸變為該尺寸 transform.localScale = Vector3.Lerp(transform.localScale, local, 0.1f); //漸變為該旋轉 transform.eulerAngles = Vector3.Lerp(transform.eulerAngles, euler, 0.1f); //漸變為該顏色 GetComponent<MeshRenderer>().material.color = Color.Lerp(GetComponent<MeshRenderer>().material.color, color, 0.1f); //如果接近目的地 if ((transform.position - pos).magnitude < 0.1f) { //確定位置尺寸旋轉和顏色 transform.position = pos; transform.localScale = local; transform.eulerAngles = euler; GetComponent<MeshRenderer>().material.color = color; } }

此方法在遊戲進行時調用,通過狀態機決定往攝像機處轉換或轉換回原處。如果想實現轉換回原處的操作,一定要在飛向攝像機時,記錄好初始信息,才能復原成原狀態,在移動方塊上的文字也隨轉換狀態啟用和禁用。

總結

遊戲的主要邏輯已經寫完,剩下還有一些完善工作,比如特效,特效的生成時機位置方式可以按照個人喜好進行。

方塊的移動狀態,我做了z軸和x軸兩種移動狀態,可通過狀態機實現,不一定要將狀態限制為兩種。

還有就是背景,做天空盒是一種方式,我是用的另一個攝像機,放了一堆粒子特效在面前做成背景,只渲染背景不渲染遊戲物體,主攝像機則相反,兩個攝像機一起工作。

附上完整工程代碼地址:

wushupei/Rainbow-Tower?

github.com圖標

有想進一步系統地學習遊戲開發的,歡迎到levelpp.com/強勢圍觀。

OK,本篇文章就到這裡,接下來我打算寫一系列商業遊戲中會用得較多的一些內容,敬請期待。


推薦閱讀:

Unity快速上手系列之1:拉方塊
Unity 遊戲框架搭建 (十五) 優雅的 ActionKit(QChain)
104_Group 組合
基於矩陣運算的父約束

TAG:Unity桌面環境 | 遊戲開發 | 編程學習 |