---
title: DocTutorials
tags: 图文教程,demo版,MineSweeper
---
前言:
这个版本实现游戏基本玩法,version0_?版会对算法和玩法进行进一步的研究和探究。
Demo版制作流程参考,noobtuts.com,原文链接。 本文并非对原文的翻译和转载,具体差异请君细察。
准备工作:
素材准备。
方法很多种,最偷懒的方法,右键另存为,demo版总共没有几张图片。
Q:能否自己做?游戏本就是一个多媒体展现,声音,画面,交互逻辑共同作用的这么一个电子时代综合产物。所以,一个单纯的程序员很难做出一个漂亮大气让人爱不释手的游戏,相反,一个具备扎实美术功底的略懂编程的就很容易出作品,那么如果自己做,都需要准备些什么呢?本篇暂不讨论,可以想象,后续专门写文说这件事,到时会回过头来贴个链接。
软件准备。
Unity5.X或者最新2017版均可。
Visual Studio 2017,非必需,完全可以使用自带的MomoDevelop。
知识准备。
Unity基本操作。
C#基本语法,面向对象基本概念。
开始制作。
新建2D项目。
文档注意事项:
命名规范,并没有强调说明一定要使用非常准确的英文名称,甚至现在unity也早已支持了中文名称,然而自己有准则还是方便以后的工作的。可以参考驼峰命名规则。Ps:自行搜索常见命名规则。
条理清晰。主要是指自己的文档结构,版本更新最好不要直接覆盖原始项目,否则万一出bug,欲哭无泪啊。
环境搭建。
摄像机参数设置。
Ps:摄像机参数详细释义参见unity官方手册manual,点击camera组件齿轮旁边的问号按钮。2游戏蛮牛中文手册。
需要修改:
1,位置和大小。Position和Size的修改是为了地图以(0,0)生成时,能位于摄像机中间。
Q:本demo是固定数量,如果数量根据难度变化,怎么保证游戏对象(扫雷的地图)仍旧位于摄像机中间?拖动Game窗口大小以更改分辨率,观察场景物体位置变化,如何在设备不同分辨率下都能完整显示游戏对象?
导入素材:
设置参数
参数释义:manual手册详解,游戏蛮牛中文手册
需要更改的地方,
Pixels per unit,尝试一下更改会有什么变化,这里我们使用16。
答:是指多少像素在unity世界中占据1个单位,比如设置为1,则意味着1个像素占用一个单位。
搭建游戏场景
创建游戏基本元素:
default元素拖至场景中,设置位置,添加collider组件,因为我们要用到OnMouseUpAsButton方法。
知识点:Collider组件详解,
创建地图:
方法一:一个一个创建,请务必尝试一次,因为第一练习操作,增加熟练程度。第二,在重复多次的操作中才能发现自己的问题
方法二:for循环,定义地图大小,使用for循环创建。贴个代码如下,没什么可说的,简单的两层for循环。
1 public GameObject basicItem;
2 // Use this for initialization
3 void Start () {
4 for (int i = 0; i < 9; i++) {
5 for (int j = 0; j < 9; j++) {
6 Vector3 pos = new Vector3 (i, j, 0);
7 GameObject go = Instantiate (basicItem, pos, Quaternion.identity);
8 go.transform.parent = transform;
9 }
10 }
11 }
Q:如果是特定地图怎么办?比如10*20,又或者像推箱子那样只在固定位置生成,每关又不一样?
A:你能想到多少种方法?咱们下次来对比运行效率。比如做成场景,使用时加载。或者将地图做成数据文件,使用时读取。
创建地雷等游戏元素
为我们的prefab添加脚本命名为Element,因为我们需要知道每一个位置是否有地雷。
Q:能否不让每个元素都进行计算和判断,专门一个脚本负责所有计算,是否有雷也存放在一个数组或者list里面,地图元素只用来监测用户输入并执行游戏效果即可。当然可以,下次我们直接做来比较效果。
第一,添加公共变量判断是否有雷
第二,获取之后的操作素材。使用Sprite获取。
代码如下:
//if this is a mine
public bool mine;
public Sprite[] emptyTextures;
public Sprite mineTexture;
// Use this for initialization
void Start () {
mine = Random.value < 0.02f;//random随机生成0到1的float类型数,后边数值设置几率
}
添加游戏功能:
第一:游戏结束功能。
踩到地雷后,所有地雷出现,游戏结束。
第二:计算周围的雷数
创建一个新的class命名为Grid,并在生成地图元素时创建一个实例,去存储,使用循环获取。
在vs中直接右键添加class,或者在unity中右键新的C#脚本,打开后删除继承自MonoBehaviour,
知识点:面向对象之封装继承等。
第三:周围空雷一次性打开所有相邻格子。
解释一下,就是说周围相邻的都是具有相同数量而且是0的方格,在算法中有专门的一类填充算法,比如我们这次要用到的flood fill,应用的场景有很多,比如ps的魔法棒或者油漆桶。
Knowledge:填充方法。这里使用flood fill,更详细的释义可以参考*,或者自行百度看博客,最好Google查找。
这里使用经典模型,先贴代码,而且搞程序的,用一堆话去解释起来太麻烦,咱们加几行代码,几个操作来让程序解释。
首先是Element脚本
public class Element : MonoBehaviour {
//if this is a mine
public bool mine;
public Sprite[] emptyTextures;
public Sprite mineTexture;
// Use this for initialization
void Start () {
mine = Random.value < 0.1;
int x = (int)transform.position.x;
int y = (int)transform.position.y;
Grid.elements [x, y] = this;
}
public void loadTextures(int adjacentCount)
{
if (mine) {
GetComponent<SpriteRenderer> ().sprite = mineTexture;
} else
GetComponent<SpriteRenderer> ().sprite = emptyTextures [adjacentCount];
}
public void GiveNews(int x,int y){
Debug.Log (x.ToString() + "," + y.ToString());
}
public bool isCovered()
{
return GetComponent<SpriteRenderer>().sprite.texture.name=="default";
}
void OnMouseUpAsButton() {
//it's a mine
if (mine) {
//todo:
Grid.uncoverMines();
}
else {
//todo:
int x = (int)transform.position.x;
int y = (int)transform.position.y;
loadTextures (Grid.adjacentMines (x, y));
Grid.FFuncover (x, y, new bool[Grid.width, Grid.hight]);
if (Grid.isFinished()) {
print ("You win");
}
}
}
}
然后是Grid类class,
1 public class Grid{
2 public static int width = 9;
3 public static int hight = 9;
4 public static Element[,] elements = new Element[width, hight];
5
6 public static void uncoverMines()
7 {
8 foreach (var item in elements) {
9 if (item.mine) {
10 item.loadTextures (0);
11 }
12 }
13 }
14
15 public static bool mineAt(int x,int y)
16 {
17 if (x>=0&&y>=0&&x<width&&y<hight)
18 return elements [x, y].mine;
19 return false;
20 }
21
22 public static int adjacentMines(int x,int y)
23 {
24 int count = 0;
25 for (int i = -1; i < 2; i++) {
26 for (int j = -1; j < 2; j++) {
27 if (mineAt (x + i, y + j))
28 ++count;
29 }
30 }
31 return count;
32 }
33
34 // Flood Fill empty elements
35 public static void FFuncover(int x, int y, bool[,] visited) {
36 // Coordinates in Range?
37 if (x >= 0 && y >= 0 && x < width && y < hight) {
38 // visited already?
39 if (visited[x, y])
40 return;
41
42 // uncover element
43 elements[x, y].loadTextures(adjacentMines(x, y));
44 elements [x, y].GiveNews (x, y);
45
46 // close to a mine? then no more work needed here
47 if (adjacentMines(x, y) > 0)
48 return;
49
50 // set visited flag
51 visited[x, y] = true;
52
53 // recursion
54 FFuncover(x-1, y, visited);
55 FFuncover(x+1, y, visited);
56 FFuncover(x, y-1, visited);
57 FFuncover(x, y+1, visited);
58 }
59 }
60
61 public static bool isFinished() {
62 // Try to find a covered element that is no mine
63 foreach (Element elem in elements)
64 if (elem.isCovered() && !elem.mine)
65 return false;
66 // There are none => all are mines => game won.
67 return true;
68 }
69 }
flood fill究竟是怎么运算和工作呢,我们在Element脚本中添加一个方法,在flood fill方法中每寻找一格就调用一次打印坐标信息。可以看到
demo完成。
接下来我们先不完善游戏功能,我们去思考扫雷的这几个基础功能,尤其注意文档中的Q,并多准备几个解决办法,我们在下一次进行对比。
知识清单knowledge list
问题清单question list
解决方法清单solution list
仍待完善的功能:
1:生成一定数量的地雷。按照Windows经典游戏的玩法,不同难度生成不同数量的地雷,当然地图大小也不一样。
2:红旗标识功能;
3:界面UI以及其余东西。