【Unity】MiniGame编辑器小游戏(三)马赛克【Mosaic】
更新日期: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.暂停游戏、退出游戏
同俄罗斯方块。