当前位置: 首页 > news >正文

【Unity】MiniGame编辑器小游戏(四)数独【Sudoku】

更新日期:2025年6月24日。
项目源码:后续章节发布

索引

  • 数独【Sudoku】
    • 一、游戏最终效果
    • 二、玩法简介
    • 三、正式开始
      • 1.定义游戏窗口类
      • 2.规划游戏窗口、视口区域
      • 3.绘制九宫格棋盘
        • ①.定义方格节点
        • ②.生成数独棋盘
        • ③.绘制数独棋盘
      • 4.生成数独题目
      • 5.在空白方格中填写数字
      • 6.检测游戏是否通关
      • 7.绘制游戏操作说明
      • 8.暂停游戏、退出游戏

数独【Sudoku】

本篇的目标是开发一个数独【Sudoku】小游戏。

一、游戏最终效果

Unity编辑器小游戏:数独

二、玩法简介

数独游戏是一种经典的逻辑推理游戏,其规则简单却富有挑战性。

游戏的目标是在一个9×9的方格中填入数字1到9,使得每一行、每一列以及每一宫内(3×3的小方格),每个数字都只出现一次。游戏开始时,部分格子中已经填入了数字作为提示,玩家需要根据这些提示,推理出其他空格中的数字。

三、正式开始

1.定义游戏窗口类

首先,定义数独的游戏窗口类MiniGame_Sudoku,其继承至MiniGameWindow【小游戏窗口基类】

    /// <summary>/// 数独/// </summary>public class MiniGame_Sudoku : MiniGameWindow{}

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

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

        /// <summary>/// 游戏名称/// </summary>public override string Name => "数独 [Sudoku]";/// <summary>/// 游戏窗体大小/// </summary>public override Vector2 WindowSize => new Vector2(480, 310);/// <summary>/// 游戏视口区域/// </summary>public override Rect ViewportRect => new Rect(5, 25, 280, 280);

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

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

        [MenuItem("MiniGame/数独 [Sudoku]", priority = 5)]private static void Open_MiniGame_Sudoku(){MiniGameWindow.OpenWindow<MiniGame_Sudoku>();}

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

在这里插入图片描述

3.绘制九宫格棋盘

数独游戏的棋盘为9x9个方格组成,每一行、每一列均为9个格子,所以我们先来绘制如下这样的棋盘:

在这里插入图片描述

①.定义方格节点

首先,定义方格节点Node,其代表棋盘中的一个方格:

        /// <summary>/// 节点/// </summary>public class Node{public int Value = 0;public bool IsBlank = false;}
②.生成数独棋盘

生成数独棋盘:

        private int[] _numbers;private Node[,] _sudoku;private Node _current;/// <summary>/// 开始游戏/// </summary>private void StartGame(){//一个缓存数组,用于随机取1-9不重复数字_numbers = new int[9];for (int i = 0; i < _numbers.Length; i++){_numbers[i] = i + 1;}for (int i = 0; i < _numbers.Length; i++){int j = Random.Range(i, _numbers.Length);int temp = _numbers[i];_numbers[i] = _numbers[j];_numbers[j] = temp;}//创建数独棋盘_sudoku = new Node[9, 9];_current = null;for (int row = 0; row < 9; row++){for (int col = 0; col < 9; col++){_sudoku[row, col] = new Node();}}//生成数独棋盘if (GenerateSudoku()){//生成数独题目GenerateSudokuQuestion();}else{_sudoku = null;Debug.LogError("Sudoku: Failed to generate Sudoku board, please try again.");}}

这里的核心即是生成数独棋盘的算法,目前采用了较为暴力的回溯算法

        /// <summary>/// 生成数独棋盘/// </summary>private bool GenerateSudoku(){for (int row = 0; row < 9; row++){for (int col = 0; col < 9; col++){//发现未填入数字的方格if (_sudoku[row, col].Value == 0){//随机取1-9数字填入for (int num = 0; num < _numbers.Length; num++){//先尝试填入数字,判断填入后是否合规if (IsSafe(row, col, _numbers[num])){//如果合规,则确认填入该数字_sudoku[row, col].Value = _numbers[num];//填入该数字后,棋盘产生变化,再次递归进行整个棋盘的空方格填字if (GenerateSudoku()){return true;}else{//GenerateSudoku 返回了false,需要回溯到上一步,则本方格数字清空_sudoku[row, col].Value = 0;}}}//如果填入1-9任何数字均不合规,则证明上一方格填写的数字有误,回溯到上一步return false;}}}return true;}/// <summary>/// 在指定行、列填入指定数字后,是否合规(每一行、每一列、每一宫均为1-9不重复数字)/// </summary>private bool IsSafe(int row, int col, int number){//列中存在重复数字,不合规for (int x = 0; x < 9; x++){if (_sudoku[row, x].Value == number){return false;}}//行中存在重复数字,不合规for (int x = 0; x < 9; x++){if (_sudoku[x, col].Value == number){return false;}}//九宫中存在重复数字,不合规int startRow = row - row % 3;int startCol = col - col % 3;for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){if (_sudoku[i + startRow, j + startCol].Value == number){return false;}}}return true;}

虽然较为暴力,但此算法也是解决数独问题比较常用的算法。

③.绘制数独棋盘

然后在OnGameViewportGUI方法中绘制数独棋盘:

        protected override void OnGameViewportGUI(){base.OnGameViewportGUI();DrawPanel();}/// <summary>/// 绘制画布/// </summary>private void DrawPanel(){for (int h = 0; h < 9; h++){for (int w = 0; w < 9; w++){DrawNode(w, h);}}}/// <summary>/// 绘制节点/// </summary>private void DrawNode(int x, int y){//每一个方格根据所在位置进行细微偏移,使得九个宫之间存在细微的间隔_nodeRect.Set(x * BLOCKSIZE + x / 3 * 5, y * BLOCKSIZE + y / 3 * 5, BLOCKSIZE, BLOCKSIZE);//如果为空白需填充的节点,则绘制一个按钮,点击可选中并输入数字if (_sudoku[x, y].IsBlank){GUI.color = _current == _sudoku[x, y] ? Color.cyan : Color.white;GUI.contentColor = Color.yellow;string str = _sudoku[x, y].Value == 0 ? "" : _sudoku[x, y].Value.ToString();if (GUI.Button(_nodeRect, str, _blankGS)){if (_current == _sudoku[x, y]){_current = null;}else{_current = _sudoku[x, y];}}GUI.color = Color.white;GUI.contentColor = Color.white;}//如果为已显示数字的节点,则直接绘制数字else{GUI.Box(_nodeRect, _sudoku[x, y].Value.ToString(), _noBlankGS);}}

此时就能绘制出数独棋盘了:

在这里插入图片描述

4.生成数独题目

在生成数独棋盘时,我们已然生成了一个完整合规的数独九宫格,此时只需要随机将其中部分方块的数字抹除(让玩家来填入),便生成了一道数独的题目。

由此,我们依然设计几种难度等级的关卡(抹除的方格数量越多,意味着需要玩家填写的方格越多,则难度越高):

名称随机抹除的方格数量
初级2-4
中级4-6
高级6-8
        private readonly string[] LEVELS = new string[] { "初级", "中级", "高级" };

在生成数独题目时,根据游戏难度进行随机抹除方格:

        /// <summary>/// 生成数独题目/// </summary>private void GenerateSudokuQuestion(){Vector2Int blankNumber = new Vector2Int();if (_level == 0){blankNumber.x = 2;blankNumber.y = 4;}else if (_level == 1){blankNumber.x = 4;blankNumber.y = 6;}else if (_level == 2){blankNumber.x = 6;blankNumber.y = 8;}//以每个小的九宫为单位进行抹除List<Node> nodes = new List<Node>();for (int row = 0; row < 3; row++){for (int col = 0; col < 3; col++){//提取该九宫中的所有方格nodes.Clear();nodes.Add(_sudoku[row * 3, col * 3]);nodes.Add(_sudoku[row * 3, col * 3 + 1]);nodes.Add(_sudoku[row * 3, col * 3 + 2]);nodes.Add(_sudoku[row * 3 + 1, col * 3]);nodes.Add(_sudoku[row * 3 + 1, col * 3 + 1]);nodes.Add(_sudoku[row * 3 + 1, col * 3 + 2]);nodes.Add(_sudoku[row * 3 + 2, col * 3]);nodes.Add(_sudoku[row * 3 + 2, col * 3 + 1]);nodes.Add(_sudoku[row * 3 + 2, col * 3 + 2]);//生成随机抹除数int number = Random.Range(blankNumber.x, blankNumber.y);for (int n = 0; n < number; n++){//随机取出一个方格进行抹除Node node = RandomDequeueNode(nodes);node.Value = 0;node.IsBlank = true;}}}}/// <summary>/// 随机取出一个节点/// </summary>private Node RandomDequeueNode(List<Node> nodes){int index = Random.Range(0, nodes.Count);Node node = nodes[index];nodes.RemoveAt(index);return node;}

生成数独题目完成后(部分方块的数字被抹除),现在的游戏棋盘界面就是如下这样的:

在这里插入图片描述

注意:这里有一个选择关卡难度的过程省略了,该过程很简单便不浪费篇幅赘述了,后续在源码中即可一目了然。

5.在空白方格中填写数字

OnGamePlayingEvent方法中完成填写数字的逻辑:

        protected override void OnGamePlayingEvent(Event e, Vector2 mousePosition){base.OnGamePlayingEvent(e, mousePosition);//空白的方格会绘制为按钮,点击按钮即可选中该方格if (_current == null)return;//选中方格后,可输入指定的数字,Delete或退格键清空输入if (e.type == EventType.KeyDown){switch (e.keyCode){case KeyCode.Alpha1:case KeyCode.Keypad1:_current.Value = 1;Repaint();break;case KeyCode.Alpha2:case KeyCode.Keypad2:_current.Value = 2;Repaint();break;case KeyCode.Alpha3:case KeyCode.Keypad3:_current.Value = 3;Repaint();break;case KeyCode.Alpha4:case KeyCode.Keypad4:_current.Value = 4;Repaint();break;case KeyCode.Alpha5:case KeyCode.Keypad5:_current.Value = 5;Repaint();break;case KeyCode.Alpha6:case KeyCode.Keypad6:_current.Value = 6;Repaint();break;case KeyCode.Alpha7:case KeyCode.Keypad7:_current.Value = 7;Repaint();break;case KeyCode.Alpha8:case KeyCode.Keypad8:_current.Value = 8;Repaint();break;case KeyCode.Alpha9:case KeyCode.Keypad9:_current.Value = 9;Repaint();break;case KeyCode.Backspace:case KeyCode.Delete:_current.Value = 0;Repaint();break;}}}

在这里插入图片描述

6.检测游戏是否通关

也即是检测数独题目是否已解:要求每一行每一列每一宫中的九个方格中填入1-9不重复数字。

        /// <summary>/// 检测数独题目是否完成/// </summary>private bool CheckSudokuQuestion(){//检测是否有空白for (int row = 0; row < 9; row++){for (int col = 0; col < 9; col++){if (_sudoku[row, col].Value == 0){return false;}}}HashSet<int> nodes = new HashSet<int>();//检测每一列是否合规for (int row = 0; row < 9; row++){nodes.Clear();for (int col = 0; col < 9; col++){nodes.Add(_sudoku[row, col].Value);}if (!IsNonRepetitive(nodes))return false;}//检测每一行是否合规for (int col = 0; col < 9; col++){nodes.Clear();for (int row = 0; row < 9; row++){nodes.Add(_sudoku[row, col].Value);}if (!IsNonRepetitive(nodes))return false;}//检测每一宫是否合规for (int row = 0; row < 3; row++){for (int col = 0; col < 3; col++){nodes.Clear();nodes.Add(_sudoku[row * 3, col * 3].Value);nodes.Add(_sudoku[row * 3, col * 3 + 1].Value);nodes.Add(_sudoku[row * 3, col * 3 + 2].Value);nodes.Add(_sudoku[row * 3 + 1, col * 3].Value);nodes.Add(_sudoku[row * 3 + 1, col * 3 + 1].Value);nodes.Add(_sudoku[row * 3 + 1, col * 3 + 2].Value);nodes.Add(_sudoku[row * 3 + 2, col * 3].Value);nodes.Add(_sudoku[row * 3 + 2, col * 3 + 1].Value);nodes.Add(_sudoku[row * 3 + 2, col * 3 + 2].Value);if (!IsNonRepetitive(nodes))return false;}}return true;}/// <summary>/// 是否为不重复的且包含1-9数字的集合/// </summary>private bool IsNonRepetitive(HashSet<int> nodes){for (int num = 1; num <= 9; num++){if (!nodes.Contains(num))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;if (GUI.Button(rect, "Done")){//点击Done按钮,检测游戏是否通关if (CheckSudokuQuestion()){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;}

这里绘制出来的效果如下:

在这里插入图片描述

至此,一个简单的数独小游戏就完成了,试玩效果如下:数独【Sudoku】。

8.暂停游戏、退出游戏

同俄罗斯方块。

相关文章:

  • 移动端手机网站制作网络口碑营销案例
  • 有本地服务器怎么做网站公司seo是什么级别
  • 找设计案例的网站北京网站优化公司
  • 延安网站制作重庆百度竞价开户
  • 国外单页制作网站模板百度搜索什么关键词排名
  • 德州做名片的网站谷歌商店下载官方
  • 从设备自动化到智能管控:MES如何赋能牛奶饮料行业高效生产?
  • “边缘化”的机顶盒,被华为云CloudDevice拉回了客厅C位
  • 0 基础读懂可视化建模
  • 技术伦理之争:OpenAI陷抄袭风波,法院强制下架宣传视频
  • 前端react使用 UmiJS 构建框架 在每次打包时候记录打包时间并在指定页面显示
  • iOS runtime随笔-消息转发机制
  • ZooKeeper集群安装
  • lib61850 代码结构与系统架构深度分析
  • 第八节 CSS工程化-CSS模块化实践
  • ASP.NET Core 中 Kestrel 的应用及在前后端分离项目中的角色
  • order、sort、distribute和cluster by(Spark/Hive)
  • 监控易运维可视化大屏:迅速精准定位关键信息
  • 基于单片机的语音控制设计(论文)
  • Vue3+el-table-v2虚拟表格大数据量多选功能详细教程
  • 安全报告:LLM 模型在无显性攻击提示下的越狱行为分析
  • 通义灵码2.5智能体模式实战———集成高德MCP 10分钟生成周边服务地图应用
  • Vue.js 列表过滤实现详解(watch和computed实现)
  • AI对话导出工具 (AI Chat Exporter)——支持 ChatGPT, Grok 和 Gemini 平台
  • 【bug】searchxng搜索报错Searx API returned an error
  • 【软考高级系统架构论文】论软件系统架构评估