【Unity】MiniGame编辑器小游戏(三)马赛克【Mosaic】

更新日期:2025年6月17日。

项目源码:后续章节发布

索引

马赛克【Mosaic】

本篇的目标是开发一个马赛克【Mosaic】小游戏。

一、游戏最终效果

Unity编辑器小游戏:马赛克

二、玩法简介

马赛克扫雷游戏的变种,是一款推理游戏,其玩法简单却富有挑战性。

游戏界面由方块阵列组成,玩家需要推理每个方块的正确颜色,并标记为该颜色(分为黑色白色),所以游戏通关后的界面看起来像马赛克一样,因而得名。

有些方块上会显示一个数字,代表了该方块所在的9宫格中黑色方块的数量(包含该方块自身),玩家需要通过这些信息来推理逐步找出所有黑色方块。

三、正式开始

1.定义游戏窗口类

首先,定义马赛克的游戏窗口类MiniGame_Mosaic,其继承至MiniGameWindow【小游戏窗口基类】

csharp 复制代码
    /// <summary>
    /// 马赛克
    /// </summary>
    public class MiniGame_Mosaic : MiniGameWindow
    {
    
    }

2.规划游戏窗口、视口区域

通过覆写虚属性实现规划游戏视口区域大小:

csharp 复制代码
        /// <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);

注意:游戏窗体大小必须 > 游戏视口区域。

然后通过代码打开此游戏窗口:

csharp 复制代码
        [MenuItem("MiniGame/马赛克 [Mosaic]", priority = 3)]
        private static void Open_MiniGame_Mosaic()
        {
            MiniGameWindow.OpenWindow<MiniGame_Mosaic>();
        }

便可以看到游戏的窗口、视口区域如下(左侧深色凹陷区域为视口区域):

3.地图方块阵列

马赛克游戏的背景也是由一系列方块组成的,所以我们先来绘制如下这样的地图方块阵列:

①.定义方块结构体

首先,定义方块结构体Block,其代表方块阵列中的一个方块:

csharp 复制代码
        /// <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
csharp 复制代码
        private readonly string[] LEVELS = new string[] { "初级(5*5)", "中级(10*10)", "高级(15*15)", "大师级(20*20)" };

所以游戏视口的宽度、高度是根据大师级难度(方块尺寸25 * 方块宽高20 = 500)的大小来设置的:

csharp 复制代码
        private const int BLOCKSIZE = 25;

根据选择的不同难度,来生成对应的地图方块阵列:

csharp 复制代码
        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%概率为黑色:

csharp 复制代码
_mosaic[row, col].IsBlack = Utility.IsTriggerProbability(60);

初级中级时,每一个方块有70%概率显示九宫格内黑色方块数量,高级大师级60%,相应提升了难度:

csharp 复制代码
_mosaic[row, col].IsShowCount = Utility.IsTriggerProbability((_level == 0 || _level == 1) ? 70 : 60);
③.计算九宫格黑色方块数量

在生成方块阵列完成后,下一步就需要计算每一个方块所属九宫格中的黑色方块数量:

csharp 复制代码
        /// <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;
        }
④.排除任意九宫格内不存在任何一个显示文字方块的情况

我们必须确保,任意九宫格中,至少有一个方块会显示黑色方块数量,否则会显著提升解题难度,甚至不可解:

csharp 复制代码
        /// <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;
        }
⑤.设置马赛克题目

马赛克游戏也可以看作是一道逻辑解密题,由于之前我们随机生成了一些黑色方块,现在部分方块上已经标注了其所在九宫格中黑色方块的数量,现在只需要将所有黑色方块去掉,使玩家通过逻辑推理来寻找黑色方块即可:

csharp 复制代码
        /// <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方法中绘制方块阵列:

csharp 复制代码
		//未标记的方块风格
        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方法中完成标记方块的逻辑:

csharp 复制代码
        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.检测游戏是否通关

检测游戏是否通关的逻辑为:每一个显示了黑色方块数量的方块,其所在九宫格内必须真实标记相应数量的黑色方块,其余标记为白色。

跟扫雷不同的是:每个方块的黑、白属性并不固定,只要最终满足每个九宫格的黑色方块数量即可。

csharp 复制代码
        /// <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方法中:

csharp 复制代码
        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.暂停游戏、退出游戏

俄罗斯方块

相关推荐
还债大湿兄4 小时前
游戏技能编辑器开发完全指南系统架构设计之技能编辑器整体架构
游戏
come112344 小时前
VS Code 项目中的 .vscode 目录详解
ide·vscode·编辑器
像素之间5 小时前
设置vscode使用eslint
ide·vscode·编辑器
还债大湿兄5 小时前
游戏架构中的第三方SDK集成艺术:构建安全高效的接入体系
游戏
阿幸软件杂货间8 小时前
VSCode1.101.1Win多语言语言编辑器便携版安装教程
vscode·编辑器
龚子亦8 小时前
【数字人开发】Unity+百度智能云平台实现长短文本个性化语音生成功能
百度·unity·游戏引擎
benben0449 小时前
Unity3D仿星露谷物语开发67之创建新的NPC
开发语言·游戏·ui·c#·游戏引擎
RPGMZ11 小时前
RPGMZ游戏引擎之如何设计每小时开启一次的副本
javascript·游戏·游戏引擎·rpgmz
旧物有情13 小时前
Unity2D 街机风太空射击游戏 学习记录 #12环射道具的引入
学习·游戏