教程 | 使用Unity 2D實現經典的掃雷遊戲(上)

源 Unity官方開發者社區

相信我們很多人都玩過掃雷遊戲,它是一個單人解謎遊戲,最早於上世紀60年代發布。遊戲的目標是探索雷區,努力不觸發地雷。每顯示一個無雷區域,遊戲會顯示一個數字,表明四周的地雷數量。這為遊戲增加了一個不錯的策略因素。

這篇教程使用Unity 2D實現經典的掃雷遊戲將非常簡單,僅有85行代碼以及一些像素美術素材。我們將學習一些有關Unity編程以及如何實現廣受歡迎的泛洪填充演算法。

說明:泛洪填充演算法 en.wikipedia.org/wiki/F

開發準備

本篇教程無需任何特殊的Unity技巧,除了一些有關遊戲對象和變換等內容的基本知識。理解遞歸的概念將對泛洪填充演算法的實現很有幫助。

本篇教程可以使用Unity 5.0.0f4及更高版本來進行實現。


項目設置

啟動Unity,選擇New Project創建新項目,並將項目命名為minesweeper,選擇任意一個保存位置,比如C:,選擇項目類型為2D,點擊Create Project創建新項目。

現在我們可以修改攝像機,確保遊戲處於屏幕中心。首先選擇層級窗口中的Main Camera ,然後將Background Color設置為黑色。我們還需要將Size 與Position 修改設置為下圖所示的樣子。

基礎項目設置就完成了。


默認元素

讓我們把默認元素添加到遊戲。默認元素是那些我們尚未點擊的區塊,用於隱藏它下面真實的內容。

首先我們需要一些可用的圖片。為了簡單起見,可以使用類似 Paint.NET這樣的繪圖工具,畫一個16x16像素的圖像。

保存到Assets文件夾後,我們可以在Project項目窗口選擇它。

然後我們可以在檢視窗口中修改它的導入設置。需要注意,導入設置指定了圖像在最終遊戲中的大小以及是否要進行壓縮。

現在我們可以將圖像從Project項目區域拖入到場景。

我們在場景窗口或層級窗口中選中默認元素,然後檢查檢視窗口。我們要將它的位置設為x=0,y=0。x是水平位置,而y是垂直位置。因為這是個2D遊戲,不需要第三個維度,所以我們將z設為0。

我們希望能在用戶單擊一個元素時獲得通知。Unity已經提供了一個函數可用於實現此目的,不過僅對有碰撞器的元素才有效。一個碰撞器可以使我們的對象成為物理世界的一部分。現在我們的默認元素只是遊戲世界中的一個圖像。一旦為它添加了碰撞器,它就能成為物理世界的一部分,就像一面牆一樣。

要添加一個碰撞器,可以在檢視窗口中選擇:Add Component->Physics 2D->Box Collider 2D。這樣,它現在是物理世界的一部分了。

如果我們點擊運行,就能看到遊戲中的第一個元素。


添加更多元素

我們的2D掃雷遊戲如果只有一個元素那就太無聊了。我們可以通過剛才的流程,或者右擊層級窗口中的default遊戲對象並選擇「Duplicate」 進行複製,以添加更多的元素。

我們將複製的元素放x=1,y=0的位置。

現在我們可以不停地複製元素,直到有10(水平)x 13(垂直)個元素。

底部元素的坐標是x=0,y=0。右上角的元素位於x=9,y=12。中間的元素位置坐標應當要四捨五入就像這樣x=2,y=3,而非x=2.04,y=3.002。

現在我們的遊戲界面是不是看上去已經有點像掃雷遊戲的模樣了!


關於鄰接

讓我們花點時間了解下鄰接地雷的情況,這將是我們掃雷遊戲的重要部分。

點擊一個非地雷元素後,用戶應當能看到一個指示鄰接地雷數量的數字。我們將採取一種稱為8向鄰接檢測的手段。或者換而言之,我們不僅會檢測頂/底/左/右,同時還要檢測左上/右上/左下/右下的元素。

這裡使我們會遇到的九種不同情況:

所以我們要計算每個塊的鄰接地雷數量,然後繪製數字。如果沒有鄰接地雷,則什麼也不繪製。


添加更多圖像:數字與地雷

為了要繪製那些數字,我們可以使用Unity的GUI系統或為簡單地為每個數字快速繪製一個紋理。然後將它們保存到項目的Assets文件夾中。

我們還需要用到一個地雷的圖像。

將它們保存到項目的Assets文件夾中。

我們將所有那些圖像保存到Project窗口中後,要選擇它們,並在檢視窗口中為它們應用以下這些導入設置。


開始編碼

現在開始編寫代碼。右擊Project窗口,選擇Create->C# Script,並命名為Element。

這個腳本目前沒有任何作用,讓我們選擇層級窗口中的所有default元素,然後通過點擊檢視窗口中的Add Component->Script->Element,將腳本添加到它們上面。

在Project項目窗口中雙擊並打開腳本。

using UnityEngine; using System.Collections; public class Element : MonoBehaviour { // 初始化 void Start () { } // 每幀調用一次Update void Update () { } }

我們可以移除Update函數,因為不需要它。然後我們添加一個變數,表明當前元素是否是一個地雷。

using UnityEngine;using System.Collections;public class Element : MonoBehaviour { // 這是一顆地雷嗎? public bool mine; // 初始化 void Start () { }}

變數mine是公共的,這樣其它元素才能看到它。Start函數會在遊戲開始時被調用。

通過在Start函數中使用Random.value,現在我們可以隨機決定這個元素是否是一顆地雷。Random.value總會返回一個介於0和1之間的新隨機數。如果我們希望元素有15%的概率是一顆地雷,所以我們將使用Random.value<0.15。

using UnityEngine;using System.Collections;public class Element : MonoBehaviour { // 這是一顆地雷嗎? public bool mine; // 初始化 void Start () { // 隨機決定它是否是一顆地雷 mine = Random.value < 0.15; }}

現在讓我們創建一個小小的輔助函數。我們希望能隨時從默認紋理切換為空紋理、某個數字紋理或地雷紋理。

首先我們要定義一些紋理變數。

using UnityEngine;using System.Collections;public class Element : MonoBehaviour { // 這是一顆地雷嗎? public bool mine; // 不同紋理 public Sprite[] emptyTextures; public Sprite mineTexture; // 初始化 void Start () { // 隨機決定它是否是一顆地雷 mine = Random.value < 0.15; }}

Sprite是紋理的另一種說法。Sprite[]則是一個數組,也就是不止一個Sprite。

現在我們可以在檢視窗口中看到一些新的欄位。

可以將紋理拖動其中。讓我們把Project項目窗口中的紋理一個個拖入到這些欄位。

現在我們可以通過loadTexture函數使用我們的Sprite變數了。

using UnityEngine;using System.Collections;public class Element : MonoBehaviour { // 這是一顆地雷嗎? public bool mine; // 不同紋理 public Sprite[] emptyTextures; public Sprite mineTexture; // 初始化 void Start () { // 隨機決定它是否是一顆地雷 mine = Random.value < 0.15; } //載入另一個紋理 public void loadTexture(int adjacentCount) { if (mine) GetComponent<SpriteRenderer>().sprite = mineTexture; else GetComponent<SpriteRenderer>().sprite = emptyTextures[adjacentCount]; }}

這個函數會首先檢測元素是不是地雷。如果是,就載入地雷紋理。如果不是,就載入emptyTextures(數字)中的一個,具體根據adjacentCount而定。GetComponent<SpriteRenderer>().sprite 就是我們更改當前紋理的方式。

可以對Start函數暫時做下修改,以測試我們的函數。

//初始化void Start () { // 隨機決定它是否是一顆地雷 //mine = Random.value < 0.15; // 測試 loadTexture(1);}

如果按下運行,我們可以看到每個元素都載入了數字1的紋理,如下圖所示,則代表正確。

現在我們可以把Start函數還原回去了。

// 初始化void Start () { // 隨機決定它是否是一顆地雷 mine = Random.value < 0.15;}

隨後我們將需要知道某個元素是否仍被覆蓋,例如:尚未點擊。所以讓我們添加一個小函數,簡單地將當前紋理名與默認名做下比較。

//是否被覆蓋?public bool isCovered() { return GetComponent<SpriteRenderer>().sprite.texture.name == "default";}

只要默認紋理還在就說明元素還未被顯示。反之,如果我們載入了一個不同的紋理,比如地雷或某個數字,就說明它已被顯示。

我們還要給Element腳本添加一個函數,用於檢測滑鼠的點擊。每個元素都已經附加了一個Collider2D組件,這意味著只要點擊某個元素,Unity就會調用函數OnMouseUpAsButton。當然,這首先要在腳本中有這麼一個函數:

void OnMouseUpAsButton() { // ToDo: do stuff..}

當點擊一個元素時,可能會發生二種情況:要麼是地雷,要麼不是。

void OnMouseUpAsButton() { // 這是一個地雷 if (mine) { // ToDo: do stuff.. } //這不是一個地雷 else { // ToDo: do stuff.. }}

如果這是個地雷,那應當顯示所有地雷,然後遊戲結束。

void OnMouseUpAsButton() { //這是一個地雷 if (mine) { // ToDo: 顯示所有的地雷 // ... // 遊戲結束 print("you lose"); } //這不是一個地雷 else { // ToDo: do stuff.. }}

如果不是地雷,那可能會發生好幾種情況。首先,我們要根據鄰接地雷數量載入正確數字的空紋理。如果點擊的元素沒有任何鄰接地雷,那我們應該顯示整個沒有地雷的元素區域,如下圖所示。

我們還應該判斷是否所有除地雷外的元素都已被顯示,這種情況下遊戲就已獲勝。這是第一個版本的代碼。

void OnMouseUpAsButton() { // I這是個地雷 if (mine) { // ToDo: 顯示所有的地雷 // ... //遊戲結束 print("you lose"); } // 這不是個地雷 else { // 顯示鄰接地雷數量 //loadTexture(...); //顯示所有無雷區域 // ... //判斷遊戲是否已獲勝 // ... } }

我們所有的ToDo功能都有一個共同點:它們除了需要元素本身的信息之外,都需要訪問其它元素。所以讓我們再創建一個腳本,用來處理所有元素。

上篇完

教程 | 使用Unity 2D實現經典的掃雷遊戲(下)


推薦閱讀:

TAG:編程 | Unity遊戲引擎 | 掃雷遊戲Minesweeper |