更新日期:2025年6月17日。
項目源碼:后續章節發布
索引
- 馬賽克【Mosaic】
- 一、游戲最終效果
- 二、玩法簡介
- 三、正式開始
- 1.定義游戲窗口類
- 2.規劃游戲窗口、視口區域
- 3.地圖方塊陣列
- ①.定義方塊結構體
- ②.生成方塊陣列
- ③.計算九宮格黑色方塊數量
- ④.排除任意九宮格內不存在任何一個顯示文字方塊的情況
- ⑤.設置馬賽克題目
- 4.繪制方塊陣列
- 5.標記方塊
- 6.檢測游戲是否通關
- 7.繪制游戲操作說明
- 8.游戲技巧
- ①.數字0所在九宮格全為白色
- ②.數字9所在九宮格全為黑色
- ③.靠邊6所在九宮格全為黑色
- ④.精準排除法
- ⑤.模糊排除法
- 9.暫停游戲、退出游戲
馬賽克【Mosaic】
本篇的目標是開發一個馬賽克【Mosaic】
小游戲。
一、游戲最終效果
Unity編輯器小游戲:馬賽克
二、玩法簡介
馬賽克
是掃雷游戲的變種,是一款推理游戲,其玩法簡單卻富有挑戰性。
游戲界面由方塊陣列組成,玩家需要推理每個方塊的正確顏色,并標記為該顏色(分為黑色
和白色
),所以游戲通關后的界面看起來像馬賽克
一樣,因而得名。
有些方塊上會顯示一個數字,代表了該方塊所在的9宮格中黑色
方塊的數量(包含該方塊自身),玩家需要通過這些信息來推理逐步找出所有黑色方塊。
三、正式開始
1.定義游戲窗口類
首先,定義馬賽克的游戲窗口類MiniGame_Mosaic
,其繼承至MiniGameWindow【小游戲窗口基類】
:
/// <summary>/// 馬賽克/// </summary>public class MiniGame_Mosaic : MiniGameWindow{}
2.規劃游戲窗口、視口區域
通過覆寫虛屬性
實現規劃游戲視口區域大小:
/// <summary>/// 游戲名稱/// </summary>public override string Name => "馬賽克 [Mosaic]";/// <summary>/// 游戲窗體大小/// </summary>public override Vector2 WindowSize => new Vector2(700, 530);/// <summary>/// 游戲視口區域/// </summary>public override Rect ViewportRect => new Rect(5, 25, 500, 500);
注意:游戲窗體大小必須 > 游戲視口區域。
然后通過代碼打開此游戲窗口:
[MenuItem("MiniGame/馬賽克 [Mosaic]", priority = 3)]private static void Open_MiniGame_Mosaic(){MiniGameWindow.OpenWindow<MiniGame_Mosaic>();}
便可以看到游戲的窗口、視口區域如下(左側深色凹陷區域為視口
區域):
3.地圖方塊陣列
馬賽克游戲的背景也是由一系列方塊組成的,所以我們先來繪制如下這樣的地圖方塊陣列:
①.定義方塊結構體
首先,定義方塊結構體Block
,其代表方塊陣列中的一個方塊:
/// <summary>/// 方塊/// </summary>public struct Block{/// <summary>/// 方塊位置/// </summary>public Rect Position;/// <summary>/// 是否為黑色/// </summary>public bool IsBlack;/// <summary>/// 是否為白色/// </summary>public bool IsWhite;/// <summary>/// 周圍九宮格內黑色方塊數量/// </summary>public int BlackCount;/// <summary>/// 是否顯示數量文字/// </summary>public bool IsShowCount;}
②.生成方塊陣列
我們設計如下四種難度等級
的關卡:
名稱 | 地圖大小 |
---|---|
初級 | 5*5 |
中級 | 10*10 |
高級 | 15*15 |
大師級 | 20*20 |
private readonly string[] LEVELS = new string[] { "初級(5*5)", "中級(10*10)", "高級(15*15)", "大師級(20*20)" };
所以游戲視口的寬度、高度是根據大師級難度(方塊尺寸25 * 方塊寬高20 = 500)
的大小來設置的:
private const int BLOCKSIZE = 25;
根據選擇的不同難度,來生成對應的地圖方塊陣列:
private Block[,] _mosaic;/// <summary>/// 開始游戲/// </summary>private void StartGame(){if (_level == 0){WIDTH = 5;HEIGHT = 5;}else if (_level == 1){WIDTH = 10;HEIGHT = 10;}else if (_level == 2){WIDTH = 15;HEIGHT = 15;}else if (_level == 3){WIDTH = 20;HEIGHT = 20;}GenerateMosaic();}/// <summary>/// 生成馬賽克矩陣/// </summary>private void GenerateMosaic(){//生成馬賽克矩陣_mosaic = new Block[WIDTH, HEIGHT];for (int row = 0; row < WIDTH; row++){for (int col = 0; col < HEIGHT; col++){_mosaic[row, col].Position = new Rect(row * BLOCKSIZE, col * BLOCKSIZE, BLOCKSIZE, BLOCKSIZE);_mosaic[row, col].IsBlack = Utility.IsTriggerProbability(60);_mosaic[row, col].IsWhite = false;_mosaic[row, col].IsShowCount = Utility.IsTriggerProbability((_level == 0 || _level == 1) ? 70 : 60);}}}
在生成方塊陣列的方法中,每一個方塊有60%
概率為黑色:
_mosaic[row, col].IsBlack = Utility.IsTriggerProbability(60);
在初級
和中級
時,每一個方塊有70%
概率顯示九宮格內黑色方塊數量,高級
和大師級
為60%
,相應提升了難度:
_mosaic[row, col].IsShowCount = Utility.IsTriggerProbability((_level == 0 || _level == 1) ? 70 : 60);
③.計算九宮格黑色方塊數量
在生成方塊陣列完成后,下一步就需要計算每一個方塊所屬九宮格中的黑色方塊數量:
/// <summary>/// 生成馬賽克矩陣/// </summary>private void GenerateMosaic(){//......//計算所有方塊所在九宮格的黑色方塊數量for (int row = 0; row < WIDTH; row++){for (int col = 0; col < HEIGHT; col++){_mosaic[row, col].BlackCount = CalculateBlackCount(row, col);}}}/// <summary>/// 計算方塊所在九宮格的黑色方塊數量/// </summary>private int CalculateBlackCount(int x, int y){int count = 0;for (int i = -1; i <= 1; i++){for (int j = -1; j <= 1; j++){int newX = x + i;int newY = y + j;if (newX >= 0 && newX < WIDTH && newY >= 0 && newY < HEIGHT && _mosaic[newX, newY].IsBlack){count++;}}}return count;}
④.排除任意九宮格內不存在任何一個顯示文字方塊的情況
我們必須確保,任意九宮格中,至少有一個
方塊會顯示黑色方塊數量,否則會顯著提升解題難度
,甚至不可解:
/// <summary>/// 生成馬賽克矩陣/// </summary>private void GenerateMosaic(){//......//排除單一九宮格內不存在任何一個顯示文字方塊的情況for (int row = 0; row < WIDTH; row++){for (int col = 0; col < HEIGHT; col++){int count = CalculateShowCount(row, col);if (count <= 0){_mosaic[row, col].IsShowCount = true;}}}}/// <summary>/// 計算方塊所在九宮格的顯示文字的數量/// </summary>private int CalculateShowCount(int x, int y){int count = 0;for (int i = -1; i <= 1; i++){for (int j = -1; j <= 1; j++){int newX = x + i;int newY = y + j;if (newX >= 0 && newX < WIDTH && newY >= 0 && newY < HEIGHT && _mosaic[newX, newY].IsShowCount){count++;}}}return count;}
⑤.設置馬賽克題目
馬賽克游戲也可以看作是一道邏輯解密題
,由于之前我們隨機生成了一些黑色方塊,現在部分方塊上已經標注了其所在九宮格中黑色方塊的數量,現在只需要將所有黑色方塊去掉,使玩家通過邏輯推理來尋找黑色方塊即可:
/// <summary>/// 生成馬賽克矩陣/// </summary>private void GenerateMosaic(){//......//設置馬賽克題目for (int row = 0; row < WIDTH; row++){for (int col = 0; col < HEIGHT; col++){_mosaic[row, col].IsBlack = false;}}}
4.繪制方塊陣列
然后在OnGameViewportGUI
方法中繪制方塊陣列:
//未標記的方塊風格private GUIStyle _noBlockGS;//已標記的方塊風格private GUIStyle _blockGS;protected override void OnGameViewportGUI(){base.OnGameViewportGUI();DrawPanel();}/// <summary>/// 繪制畫布/// </summary>private void DrawPanel(){for (int h = 0; h < HEIGHT; h++){for (int w = 0; w < WIDTH; w++){DrawBlock(w, h);}}}/// <summary>/// 繪制方塊/// </summary>private void DrawBlock(int x, int y){string count = _mosaic[x, y].IsShowCount ? _mosaic[x, y].BlackCount.ToString() : "";if (_mosaic[x, y].IsBlack){GUI.backgroundColor = Color.black;GUI.Box(_mosaic[x, y].Position, count, _blockGS);GUI.backgroundColor = Color.white;}else if (_mosaic[x, y].IsWhite){GUI.Box(_mosaic[x, y].Position, count, _blockGS);}else{GUI.Box(_mosaic[x, y].Position, count, _noBlockGS);}}
此時就能繪制出游戲的地圖方塊陣列了,比如初級(5*5)的:
注意:這里有一個選擇關卡難度的過程省略了,該過程很簡單便不浪費篇幅贅述了,后續在源碼中即可一目了然。
兩種狀態的方塊繪制出來大致是這樣的:
5.標記方塊
在OnGamePlayingEvent
方法中完成標記方塊的邏輯:
protected override void OnGamePlayingEvent(Event e, Vector2 mousePosition){base.OnGamePlayingEvent(e, mousePosition);if (e.type == EventType.MouseDown){if (e.button == 0){for (int h = 0; h < HEIGHT; h++){for (int w = 0; w < WIDTH; w++){if (_mosaic[w, h].Position.Contains(mousePosition)){//鼠標左鍵標記為黑色(再次點擊則取消標記黑色)_mosaic[w, h].IsWhite = false;_mosaic[w, h].IsBlack = !_mosaic[w, h].IsBlack;Repaint();return;}}}}else if (e.button == 1){for (int h = 0; h < HEIGHT; h++){for (int w = 0; w < WIDTH; w++){if (_mosaic[w, h].Position.Contains(mousePosition)){//鼠標右鍵標記為白色(再次點擊則取消標記白色)_mosaic[w, h].IsBlack = false;_mosaic[w, h].IsWhite = !_mosaic[w, h].IsWhite;Repaint();return;}}}}}}
6.檢測游戲是否通關
檢測游戲是否通關的邏輯為:每一個顯示了黑色方塊數量
的方塊,其所在九宮格內必須真實標記相應數量
的黑色方塊,其余標記為白色。
跟掃雷不同的是:每個方塊的
黑、白
屬性并不固定,只要最終滿足每個九宮格的黑色方塊數量即可。
/// <summary>/// 檢測馬賽克題目是否完成(游戲是否通關)/// </summary>private bool CheckMosaicQuestion(){for (int row = 0; row < WIDTH; row++){for (int col = 0; col < HEIGHT; col++){//如果此方塊顯示了黑色方塊數量,則檢測其所在九宮格中是否存在相應數量的方塊if (_mosaic[row, col].IsShowCount){int count = CalculateBlackCount(row, col);//任意九宮格中黑色方塊數量不對,則游戲未通關if (count != _mosaic[row, col].BlackCount){return false;}}}}return true;}
7.繪制游戲操作說明
最后,操作說明等其他UI統一繪制在OnOtherGUI
方法中:
protected override void OnOtherGUI(){base.OnOtherGUI();Rect rect = new Rect(ViewportRect.x + ViewportRect.width + 5, ViewportRect.y + ViewportRect.height - 25, 80, 20);GUI.backgroundColor = Color.green;//玩家可主動點擊Done按鈕,檢測游戲是否通關if (GUI.Button(rect, "Done")){if (CheckMosaicQuestion()){IsGameSuccessed = true;}else{ShowNotification(new GUIContent("You are failed, please try again."));}}rect.x += 85;GUI.backgroundColor = Color.yellow;//也可重新開始(如果當前題目無解,隨機生成會有無解的情況)if (GUI.Button(rect, "Restart")){OnRestart();}GUI.backgroundColor = Color.white;rect.Set(ViewportRect.x + ViewportRect.width + 5, ViewportRect.y + ViewportRect.height - 75, 80, 20);GUI.Button(rect, "Mouse Left");rect.x += 85;rect.width = 100;GUI.Label(rect, "Marked as black");rect.Set(ViewportRect.x + ViewportRect.width + 5, ViewportRect.y + ViewportRect.height - 50, 80, 20);GUI.Button(rect, "Mouse Right");rect.x += 85;rect.width = 100;GUI.Label(rect, "Marked as white");}
這里繪制出來的效果如下:
8.游戲技巧
介紹一些游戲技巧(并不是全部)。
①.數字0所在九宮格全為白色
數字0
代表所在九宮格中一個黑色方塊也沒有:
②.數字9所在九宮格全為黑色
同理,數字9
代表所在九宮格中全為黑色方塊:
③.靠邊6所在九宮格全為黑色
靠邊數字6
代表所在九宮格中有6個黑色方塊,但其九宮格只有6個方塊,所以全為黑色:
④.精準排除法
如下圖方塊數字4
,已知其下方2個方塊
為白色,排除后其九宮格只剩4個方塊,所以4個全為黑色:
⑤.模糊排除法
如下圖方塊數字5
,已知其下方2個方塊
為黑色,則上方4個方塊
中只能有3個黑色方塊。
轉向數字8
,表明其所在九宮格中只有1個白色方塊,則其上方
和左側
方塊均為黑色(那1個白色方塊在與數字5
的交界區域中)。
至此,一個簡單的馬賽克小游戲就完成了,簡單但卻耐玩,試玩效果如下:馬賽克【Mosaic】。
9.暫停游戲、退出游戲
同俄羅斯方塊。