最近用Unity做了一个简单的扫雷小游戏,可以实现电脑自动扫雷、人工布雷等功能,效果图如下。

在游戏的任何时间按下T键后,电脑会自动进行游戏,直到游戏结束。按下B键后可以通过鼠标点击埋雷。

项目一共有两个脚本。Manager脚本负责与玩家进行交互,就是普通的扫雷游戏。AI脚本负责自动扫雷功能。下面先对Manager脚本进行说明。

Manager脚本

在编写该脚本之前,需要准备以下Prefab。

贴着不同数字贴图的Cube,对应脚本中的m_bottomCubePrefab

贴有炸弹贴图和旗帜贴图的Cube,对应脚本中的m_mineCube和m_markedCubePrefab

覆盖在数字上的Cube,对应脚本中的m_topCubePrefab

准备好这些Prefab后,先构造一个逻辑上的Cube类。

  public class Cube{public int m_x, m_y;public bool m_ismarked;public bool m_hasMine;public bool m_visited;public int m_num;public Cube(int x, int y){m_x = x; m_y = y;m_ismarked = false;m_hasMine = false;m_visited = false;m_num = 0;}}

然后编写与玩家进行交互的Manager类,其中的难点是当玩家翻开的方格下的数字为零时,周围的方格需要扩散翻开,直到遇到数字为止。如下图。

此处采用深度优先搜索算法实现,算法的大概流程如下。

这个操作对应Manager类中的Expose()方法。下面是Manager类。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;   //Manager类,与玩家进行交互public class Manager : MonoBehaviour{float m_timer = 0;bool m_hand = false;//是否正在手动埋雷bool m_AIRuning = false;bool m_fail = false;bool m_win = false;public GameObject m_words;//提示语对象public GameObject m_markedCubePrefab;//被标记(插旗)的方块预制体public GameObject m_mineCube;//炸弹方块预制体public GameObject m_topCubePrefab;//表面的方块预制体public GameObject[] m_bottomCubePrefab = new GameObject[9];//下面的方块预制体,共九个int m_rowMax = 13;//列数int m_colMax = 10;//行数int m_mineNums = 12;//炸弹的数量float m_topZ = -1.5f;//表明方块的Z坐标float m_bottomZ = -0.5f;//下面方块的Z坐标AI m_ai;//AI对象Tuple m_curAction;//现在AI正在执行的动作Cube[,] m_board;GameObject[,] m_topCubes;GameObject[,] m_bottomCubes;List<Cube> m_visitedCubes;//已经被翻开的方块集合List<Cube> m_markedCubes;//已经被标记的方块集合void Start(){m_board = new Cube[m_rowMax, m_colMax];m_topCubes = new GameObject[m_rowMax, m_colMax];m_bottomCubes = new GameObject[m_rowMax, m_colMax];for (int j = 0; j < m_rowMax; j++)for (int i = 0; i < m_colMax; i++)m_board[j, i] = new Cube(j, i);m_visitedCubes = new List<Cube>();m_markedCubes = new List<Cube>();m_ai = new AI(m_rowMax, m_colMax);InitMine();MakeMap();}void Update(){m_timer += Time.deltaTime;if (m_fail || m_win){return;}if (JudgeWin()){m_win = true;return;}RunOneStep();}//检测某个位置是否合法bool CheckValid(int x, int y){return (!(x < 0 || x >= m_rowMax || y < 0 || y >= m_colMax));}//初始化炸弹位置void InitMine(){int[] row = new int[m_mineNums];for (int i = 0; i < m_mineNums; i++)row[i] = UnityEngine.Random.Range(0, m_rowMax - 1);int[] col = new int[m_mineNums];for (int i = 0; i < m_mineNums; i++)col[i] = UnityEngine.Random.Range(0, m_colMax - 1);for (int i = 0; i < m_mineNums; i++){int x = row[i], y = col[i];if (!m_board[x, y].m_hasMine){m_board[x, y].m_hasMine = true;if (CheckValid(x - 1, y)) m_board[x - 1, y].m_num++;if (CheckValid(x + 1, y)) m_board[x + 1, y].m_num++;if (CheckValid(x, y - 1)) m_board[x, y - 1].m_num++;if (CheckValid(x, y + 1))m_board[x, y + 1].m_num++;if (CheckValid(x - 1, y - 1)) m_board[x - 1, y - 1].m_num++;if (CheckValid(x + 1, y + 1)) m_board[x + 1, y + 1].m_num++;if (CheckValid(x + 1, y - 1))m_board[x + 1, y - 1].m_num++;if (CheckValid(x - 1, y + 1)) m_board[x - 1, y + 1].m_num++;}}}//运行一步void RunOneStep(){if (Input.GetKeyDown("t")) m_AIRuning = true;if (Input.GetKeyDown("b")) m_hand = !m_hand;if (m_hand)//手动埋雷{if (Input.GetMouseButtonDown(0)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){Vector3 PointPos = hit.point;//得到碰撞点的坐标if (PointPos.x > -0.5 && PointPos.x < m_rowMax - 0.5 && PointPos.y > -0.5 && PointPos.y < m_colMax - 0.5){int x = PointPos.x > 0 ? (int)(PointPos.x + 0.5) : (int)(PointPos.x - 0.5);int y = PointPos.y > 0 ? (int)(PointPos.y + 0.5) : (int)(PointPos.y - 0.5);//x,y为玩家点击的方格坐标if (!m_board[x, y].m_hasMine)//这个位置没有雷{m_board[x, y].m_hasMine = true;Destroy(m_bottomCubes[x, y]);GameObject temp = Instantiate(m_mineCube);temp.transform.position = new Vector3(x, y, m_bottomZ);m_bottomCubes[x, y] = temp;if (CheckValid(x - 1, y)) m_board[x - 1, y].m_num++;if (CheckValid(x + 1, y)) m_board[x + 1, y].m_num++;if (CheckValid(x, y - 1)) m_board[x, y - 1].m_num++;if (CheckValid(x, y + 1)) m_board[x, y + 1].m_num++;if (CheckValid(x - 1, y - 1)) m_board[x - 1, y - 1].m_num++;if (CheckValid(x + 1, y + 1)) m_board[x + 1, y + 1].m_num++;if (CheckValid(x + 1, y - 1)) m_board[x + 1, y - 1].m_num++;if (CheckValid(x - 1, y + 1)) m_board[x - 1, y + 1].m_num++;for (int j = 0; j < m_rowMax; j++)for (int i = 0; i < m_colMax; i++){if (!m_board[j, i].m_hasMine){int k = m_board[j, i].m_num;Destroy(m_bottomCubes[j, i]);GameObject temp2 = Instantiate(m_bottomCubePrefab[k]);temp2.transform.position = new Vector3(j, i, m_bottomZ);m_bottomCubes[j, i] = temp2;}}}return;}}}}if (!m_AIRuning)//玩家进行游戏{if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){Vector3 PointPos = hit.point;//得到碰撞点的坐标if (PointPos.x > -0.5 && PointPos.x < m_rowMax - 0.5 && PointPos.y > -0.5 && PointPos.y < m_colMax - 0.5){int x = PointPos.x > 0 ? (int)(PointPos.x + 0.5) : (int)(PointPos.x - 0.5);int y = PointPos.y > 0 ? (int)(PointPos.y + 0.5) : (int)(PointPos.y - 0.5);if (Input.GetMouseButtonDown(0))//左键,翻开格子Expose(x, y);if (Input.GetMouseButtonDown(1))//右键,标记(插旗)Mark(x, y);}}}}else if (m_timer > 0.1f)//AI进行游戏{m_curAction = m_ai.ExecuteAction(m_visitedCubes, m_markedCubes);if (m_curAction.m_action == Action.Mark){Mark(m_curAction.m_x, m_curAction.m_y);}if (m_curAction.m_action == Action.Expose){Expose(m_curAction.m_x, m_curAction.m_y);}m_timer = 0;}}//生成地图void MakeMap(){for (int j = 0; j < m_rowMax; j++)for (int i = 0; i < m_colMax; i++){if (!m_board[j, i].m_visited && m_topCubes[j, i] == null){GameObject temp = Instantiate(m_topCubePrefab);temp.transform.position = new Vector3(j, i, m_topZ);m_topCubes[j, i] = temp;}if (m_bottomCubes[j, i] == null){if (!m_board[j, i].m_hasMine){int k = m_board[j, i].m_num;GameObject temp = Instantiate(m_bottomCubePrefab[k]);temp.transform.position = new Vector3(j, i, m_bottomZ);m_bottomCubes[j, i] = temp;}else{GameObject temp = Instantiate(m_mineCube);temp.transform.position = new Vector3(j, i, m_bottomZ);m_bottomCubes[j, i] = temp;}}}}//翻开某个方格void Expose(int x, int y){if (m_board[x, y].m_ismarked) return;if (m_board[x, y].m_visited == false){m_board[x, y].m_visited = true;m_board[x, y].m_ismarked = false;m_topCubes[x, y].SetActive(false);m_visitedCubes.Add(m_board[x, y]);if (m_board[x, y].m_hasMine){m_fail = true;m_words.GetComponent<Text>().text = "YOU FAIL!";return;}if (m_board[x, y].m_num == 0)//被翻开的方格数字为0,搜索周围八个方格{if (CheckValid(x - 1, y))Expose(x - 1, y);if (CheckValid(x + 1, y))Expose(x + 1, y);if (CheckValid(x, y - 1))Expose(x, y - 1);if (CheckValid(x, y + 1))Expose(x, y + 1);if (CheckValid(x - 1, y - 1))Expose(x - 1, y - 1);if (CheckValid(x + 1, y + 1))Expose(x + 1, y + 1);if (CheckValid(x + 1, y - 1))Expose(x + 1, y - 1);if (CheckValid(x - 1, y + 1))Expose(x - 1, y + 1);}}}//标记某个方格void Mark(int x, int y){if (!m_board[x, y].m_visited){if (!m_board[x, y].m_ismarked){m_board[x, y].m_ismarked = true;m_markedCubes.Add(m_board[x, y]);Destroy(m_topCubes[x, y]);m_topCubes[x, y] = Instantiate(m_markedCubePrefab);m_topCubes[x, y].transform.position = new Vector3(x, y, m_topZ);}else{m_board[x, y].m_ismarked = false;m_markedCubes.Remove(m_board[x, y]);Destroy(m_topCubes[x, y]);m_topCubes[x, y] = Instantiate(m_topCubePrefab);m_topCubes[x, y].transform.position = new Vector3(x, y, m_topZ);}}}//判断是否胜利bool JudgeWin(){foreach (var one in m_board){//胜利状态下,所有方块都应该处于两种状态:1.不是雷并被点开 2.是雷但被标记if (!(!one.m_hasMine && one.m_visited || one.m_hasMine && one.m_ismarked)){return false;}}m_words.GetComponent<Text>().text = "YOU WIN!";return true;}}

以上两个类就是Manager脚本的全部内容。

AI脚本

对于Agent来说,所获得的信息与玩家是相同的(即在游戏界面上显示出的信息),所谓Agent就是通过逻辑来模仿玩家进行游戏时的策略。Agent与客户端的关系如下图。

AI的完整脚本如下。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace MineSweeper
{//动作public enum Action{Expose,Mark}//每一次Agent给客户端发送的信息为一个三元组public class Tuple{public Action m_action;public int m_x;public int m_y;public Tuple(Action action, int x, int y){m_action = action;m_x = x;m_y = y;}}public class AI{Cube[,] m_agentBoard;//Agent已获知的地图信息int m_rowMax;//地图列数int m_colMax;//地图行数Queue<Tuple> m_actionsQueue;//动作队列public AI(int row, int col){m_rowMax = row;m_colMax = col;m_agentBoard = new Cube[m_rowMax, m_colMax];m_actionsQueue = new Queue<Tuple>();for (int j = 0; j < m_rowMax; j++)for (int i = 0; i < m_colMax; i++)m_agentBoard[j, i] = new Cube(j, i);}//检测某一位置是否合法bool CheckValid(int x, int y){return (!(x < 0 || x >= m_rowMax || y < 0 || y >= m_colMax));}//根据从客户端获得的信息决定下一步动作public Tuple ExecuteAction(List<Cube> visitedcube, List<Cube> markedcube){if (m_actionsQueue.Count > 0){return m_actionsQueue.Dequeue();}foreach (var one in m_agentBoard){one.m_ismarked = false;}foreach (var one in visitedcube)//同步输入的消息{m_agentBoard[one.m_x, one.m_y].m_visited = true;m_agentBoard[one.m_x, one.m_y].m_num = one.m_num;}foreach (var one in markedcube)//同步输入的消息{m_agentBoard[one.m_x, one.m_y].m_ismarked = true;}//对每个点进行遍历for (int x = 0; x < m_rowMax; x++)for (int y = 0; y < m_colMax; y++){if (NoVisitedRound(x, y) == m_agentBoard[x, y].m_num)//可以对该点周围的点进行标记{List<Cube> needmarkcubes = new List<Cube>();if (JudgeNoMarkedAndNoVisited(x - 1, y))needmarkcubes.Add(m_agentBoard[x - 1, y]);if (JudgeNoMarkedAndNoVisited(x + 1, y))needmarkcubes.Add(m_agentBoard[x + 1, y]);if (JudgeNoMarkedAndNoVisited(x, y - 1))needmarkcubes.Add(m_agentBoard[x, y - 1]);if (JudgeNoMarkedAndNoVisited(x, y + 1))needmarkcubes.Add(m_agentBoard[x, y + 1]);if (JudgeNoMarkedAndNoVisited(x - 1, y - 1))needmarkcubes.Add(m_agentBoard[x - 1, y - 1]);if (JudgeNoMarkedAndNoVisited(x + 1, y + 1))needmarkcubes.Add(m_agentBoard[x + 1, y + 1]);if (JudgeNoMarkedAndNoVisited(x + 1, y - 1))needmarkcubes.Add(m_agentBoard[x + 1, y - 1]);if (JudgeNoMarkedAndNoVisited(x - 1, y + 1))needmarkcubes.Add(m_agentBoard[x - 1, y + 1]);foreach (var one in needmarkcubes){m_agentBoard[one.m_x, one.m_y].m_ismarked = true;m_actionsQueue.Enqueue(new Tuple(Action.Mark, one.m_x, one.m_y));}}if (MarkedRound(x, y) == m_agentBoard[x, y].m_num)//可以对该点周围的未被标记的点进行点击{List<Cube> needclickcubes = new List<Cube>();if (JudgeNoMarkedAndNoVisited(x - 1, y))needclickcubes.Add(m_agentBoard[x - 1, y]);if (JudgeNoMarkedAndNoVisited(x + 1, y))needclickcubes.Add(m_agentBoard[x + 1, y]);if (JudgeNoMarkedAndNoVisited(x, y - 1))needclickcubes.Add(m_agentBoard[x, y - 1]);if (JudgeNoMarkedAndNoVisited(x, y + 1))needclickcubes.Add(m_agentBoard[x, y + 1]);if (JudgeNoMarkedAndNoVisited(x - 1, y - 1))needclickcubes.Add(m_agentBoard[x - 1, y - 1]);if (JudgeNoMarkedAndNoVisited(x + 1, y + 1))needclickcubes.Add(m_agentBoard[x + 1, y + 1]);if (JudgeNoMarkedAndNoVisited(x + 1, y - 1))needclickcubes.Add(m_agentBoard[x + 1, y - 1]);if (JudgeNoMarkedAndNoVisited(x - 1, y + 1))needclickcubes.Add(m_agentBoard[x - 1, y + 1]);foreach (var one in needclickcubes){if (!one.m_ismarked)m_actionsQueue.Enqueue(new Tuple(Action.Expose, one.m_x, one.m_y));}}}if (m_actionsQueue.Count > 0)return m_actionsQueue.Dequeue();//没有找到可标记或可点击的点,随机点击一个未被点击的点else return TupleRandomExpose();}int NoVisitedRound(int x, int y){//若该点已经被翻开并数字不为零if (m_agentBoard[x, y].m_visited && m_agentBoard[x, y].m_num != 0){int count = 0;if (CheckValid(x - 1, y) && !m_agentBoard[x - 1, y].m_visited)count++;if (CheckValid(x + 1, y) && !m_agentBoard[x + 1, y].m_visited)count++;if (CheckValid(x, y - 1) && !m_agentBoard[x, y - 1].m_visited)count++;if (CheckValid(x, y + 1) && !m_agentBoard[x, y + 1].m_visited)count++;if (CheckValid(x - 1, y - 1) && !m_agentBoard[x - 1, y - 1].m_visited)count++;if (CheckValid(x + 1, y + 1) && !m_agentBoard[x + 1, y + 1].m_visited)count++;if (CheckValid(x + 1, y - 1) && !m_agentBoard[x + 1, y - 1].m_visited)count++;if (CheckValid(x - 1, y + 1) && !m_agentBoard[x - 1, y + 1].m_visited)count++;return count;}else return -1;}int MarkedRound(int x, int y){//若该点已经被翻开并数字不为零if (m_agentBoard[x, y].m_visited && m_agentBoard[x, y].m_num != 0){int count = 0;if (CheckValid(x - 1, y) && m_agentBoard[x - 1, y].m_ismarked)count++;if (CheckValid(x + 1, y) && m_agentBoard[x + 1, y].m_ismarked)count++;if (CheckValid(x, y - 1) && m_agentBoard[x, y - 1].m_ismarked)count++;if (CheckValid(x, y + 1) && m_agentBoard[x, y + 1].m_ismarked)count++;if (CheckValid(x - 1, y - 1) && m_agentBoard[x - 1, y - 1].m_ismarked)count++;if (CheckValid(x + 1, y + 1) && m_agentBoard[x + 1, y + 1].m_ismarked)count++;if (CheckValid(x + 1, y - 1) && m_agentBoard[x + 1, y - 1].m_ismarked)count++;if (CheckValid(x - 1, y + 1) && m_agentBoard[x - 1, y + 1].m_ismarked)count++;return count;}else return -1;}Tuple TupleRandomExpose(){int rx = 0, ry = 0;do{rx = UnityEngine.Random.Range(0, m_rowMax);ry = UnityEngine.Random.Range(0, m_colMax);}while (m_agentBoard[rx, ry].m_visited && !m_agentBoard[rx, ry].m_ismarked);return (new Tuple(Action.Expose, rx, ry));}bool JudgeNoMarkedAndNoVisited(int x, int y){return (CheckValid(x, y) && !m_agentBoard[x, y].m_ismarked && !m_agentBoard[x, y].m_visited);}}
}

谢谢观看:)

unity应用实例——扫雷游戏(自动扫雷、人工布雷)相关推荐

  1. 原生JS 扫雷游戏 自动插旗子 自定义雷区大小 雷数可调

    能随机背景颜色自动插旗子自定义雷区大小和难度的扫雷游戏 随机变换雷区颜色,以及其它CSS样式,动画效果全是CSS.点击网页上的元素触发游戏事件打开雷区.如果对于一个方格,其周围未打开的方格恰好全都有雷 ...

  2. c++扫雷以及自动扫雷ai性能测试

    扫雷部分: matrix记录地雷和周边信息,用0表示什么都没有,5表示地雷,1,2,3,4,表示周围地雷的数量     matrix_probe记录这个地方是否被探索过,0表示没有被探索过,1表示探索 ...

  3. python自动扫雷_Python自动扫雷实现方法

    Python自动扫雷实现方法 来源:中文源码网    浏览: 次    日期:2018年9月2日 [下载文档:  Python自动扫雷实现方法.txt ] (友情提示:右键点上行txt文档名-> ...

  4. python扫雷_自动扫雷 Python语言

    本文主要向大家介绍了自动扫雷 Python语言,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 自动扫雷一般分为两种,一种是读取内存数据,而另一种是通过分析图片获得数据,并通过模拟 ...

  5. 基于html扫雷游戏毕业论文,扫雷游戏的设计与开发毕业设计毕业论文正稿

    扫雷游戏的设计与开发毕业设计毕业论文正稿 (18页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.9 积分 word格式扫雷游戏的设计与开发论文作者 ...

  6. 自己用python写的扫雷游戏 pygame 扫雷

    代码有点多 函数式编程 纯原创手打,详细注释,不容易啊,搞了两天 UI有点low,目前还咩有自学到UI美化方面的内容  只会画形状填颜色 写到后面,头大了,有很多坐标索引方面的变量都直接用数字算出来代 ...

  7. 计算机里的扫雷游戏,电脑扫雷游戏怎么玩

    扫雷是微软出的一款游戏,可是你会玩吗?其实很简单的,相信你学会了就一定可以体会其中的乐趣.下面是学习啦小编整理的电脑扫雷的玩法,供您参考. 电脑扫雷的玩法 首先在开始菜单里找到游戏,然后打开扫雷.点击 ...

  8. python扫雷游戏,Python扫雷游戏

    #coding: utf-8 __note__ = """ * 扫雷小游戏 * 需要python3.x以上 * 需要安装PyQt5 * pip install PyQt5 ...

  9. matlab 自动扫雷,MATLAB自动扫雷(2)——排雷插旗

    如果周围8个方块和当中数字判断周围有空白方块 % 点击白块 function click_blank() global map global blocks_x global blocks_y glob ...

  10. python扫雷的代码及原理_基于Python实现的扫雷游戏实例代码

    摘要:这篇Python开发技术栏目下的"基于Python实现的扫雷游戏实例代码",介绍的技术点是"Python实现.Python.实例代码.扫雷游戏.扫雷.游戏" ...

最新文章

  1. 即时编译和提前编译_即时编译说明
  2. jmeter时间格式化
  3. DCMTK:将显示曲线导出到文本文件
  4. Device eth0 does not seem to be present,delaying initialization的解决办法
  5. Oracle分页查询格式(八)
  6. Java常用算法二:分治法
  7. Oracle 数据块损坏与恢复具体解释
  8. 关于ios7的适配问题
  9. c语言编写过几天是星期几,计算任何一天是星期几的C语言源代码.
  10. cvThreshold() 阈值化
  11. 欧姆龙plc OMRON SYSMAX CP1H-E 使用 CXONE_V4.60 连接和编程
  12. 世界各国网络域名后缀
  13. html开发一个月多少钱,html5前端开发工资一般是多少
  14. 各代iphone尺寸_Iphone 历代 参数
  15. 中柏平板u盘启动_中柏平板电脑u盘启动设置方法
  16. 【Docker 那些事儿】如何安全地停止、删除容器
  17. PHP修改后缀名绕过
  18. opencvsharp图像处理_腐蚀与膨胀,击中击不中变换(3)
  19. 对多项式求积分和微分
  20. Android 中英文切换的实现。

热门文章

  1. 驻极体MIC并联与串联
  2. JAVA 经典面试题:ES如何做到亿级数据查询毫秒级返回?
  3. [求职简历]我的个人简历
  4. 糖尿病遗传风险检测挑战赛-Coggle 30 Days of ML
  5. java进销存管理系统_java swing开发进销存管理系统
  6. idea中输入中文变成繁体字
  7. 【无障碍】自动朗读的弹窗和浮层实现
  8. FTP操作命令(windows系统)
  9. proteus仿真 C51与ADC0809数模转换:万用表(电压、电流、电阻)数码管显示
  10. 对能源消耗的担忧笼罩着苹果爱尔兰数据中心