unity新手入门项目之扫雷MineSweeper

时间:2022-05-02 20:11:43

---

title:  DocTutorials

tags: 图文教程,demo,MineSweeper

---

 

前言:

这个版本实现游戏基本玩法,version0_?版会对算法和玩法进行进一步的研究和探究。

Demo版制作流程参考,noobtuts.com原文链接。  本文并非对原文的翻译和转载,具体差异请君细察。

 

准备工作:

素材准备。

方法很多种,最偷懒的方法,unity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeper右键另存为,demo版总共没有几张图片。

Q:能否自己做?游戏本就是一个多媒体展现,声音,画面,交互逻辑共同作用的这么一个电子时代综合产物。所以,一个单纯的程序员很难做出一个漂亮大气让人爱不释手的游戏,相反,一个具备扎实美术功底的略懂编程的就很容易出作品,那么如果自己做,都需要准备些什么呢?本篇暂不讨论,可以想象,后续专门写文说这件事,到时会回过头来贴个链接。

 

软件准备。

Unity5.X或者最新2017版均可。

Visual Studio 2017,非必需,完全可以使用自带的MomoDevelop

知识准备。

Unity基本操作。

C#基本语法,面向对象基本概念。

 

开始制作。

新建2D项目。

文档注意事项:

命名规范,并没有强调说明一定要使用非常准确的英文名称,甚至现在unity也早已支持了中文名称,然而自己有准则还是方便以后的工作的。可以参考驼峰命名规则。Ps:自行搜索常见命名规则。

条理清晰。主要是指自己的文档结构,版本更新最好不要直接覆盖原始项目,否则万一出bug,欲哭无泪啊。

环境搭建。

摄像机参数设置。

 unity新手入门项目之扫雷MineSweeper

Ps:摄像机参数详细释义参见unity官方手册manual点击camera组件齿轮旁边的问号按钮2游戏蛮牛中文手册

需要修改:

1,位置和大小。PositionSize的修改是为了地图以(00)生成时,能位于摄像机中间。

Q:本demo是固定数量,如果数量根据难度变化,怎么保证游戏对象(扫雷的地图)仍旧位于摄像机中间?拖动Game窗口大小以更改分辨率,观察场景物体位置变化,如何在设备不同分辨率下都能完整显示游戏对象?

 

导入素材:

设置参数

 unity新手入门项目之扫雷MineSweeper

参数释义: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查找。

unity新手入门项目之扫雷MineSweeper

这里使用经典模型,先贴代码,而且搞程序的,用一堆话去解释起来太麻烦,咱们加几行代码,几个操作来让程序解释。

首先是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方法中每寻找一格就调用一次打印坐标信息。可以看到

unity新手入门项目之扫雷MineSweeperunity新手入门项目之扫雷MineSweeper

demo完成。

 

接下来我们先不完善游戏功能,我们去思考扫雷的这几个基础功能,尤其注意文档中的Q,并多准备几个解决办法,我们在下一次进行对比。

 

 

 

知识清单knowledge list

问题清单question list

解决方法清单solution list

 

仍待完善的功能:

1:生成一定数量的地雷。按照Windows经典游戏的玩法,不同难度生成不同数量的地雷,当然地图大小也不一样。

2:红旗标识功能;

3:界面UI以及其余东西。