[Unity] 使用Unity 2D实现经典的扫雷游戏(上)

查看:786 |回复:0 | 2019-11-14 17:06:45

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 kanisen 于 2019-11-14 17:08 编辑

非常简单的Unity 2D实现经典的扫雷游戏,仅有85行代码以及一些像素美术素材。


u=886208527,3948948419&fm=26&gp=0.jpg



相信我们很多人都玩过扫雷游戏,它是一个单人解谜游戏,最早于上世纪60年代发布。游戏的目标是探索雷区,努力不触发地雷。每显示一个无雷区域,游戏会显示一个数字,表明四周的地雷数量。这为游戏增加了一个不错的策略因素。

5d1ed038-ab57-402b-894e-c2b9f5034db3_1.jpg


这篇教程使用Unity 2D实现经典的扫雷游戏将非常简单,仅有85行代码以及一些像素美术素材。我们将学习一些有关Unity编程以及如何实现广受欢迎的泛洪填充算法。

说明:泛洪填充算法 https://en.wikipedia.org/wiki/Flood_fill

开发准备

本篇教程无需任何特殊的Unity技巧,除了一些有关游戏对象和变换等内容的基本知识。理解递归的概念将对泛洪填充算法的实现很有帮助。

本篇教程可以使用Unity 5.0.0f4及更高版本来进行实现。

项目设置

启动Unity,选择New Project创建新项目,并将项目命名为minesweeper,选择任意一个保存位置,比如C:\,选择项目类型为2D,点击Create Project创建新项目。



现在我们可以修改摄像机,确保游戏处于屏幕中心。首先选择层级窗口中的Main Camera ,然后将Background Color设置为黑色。我们还需要将Size 与Position 修改设置为下图所示的样子。



基础项目设置就完成了。

默认元素

让我们把默认元素添加到游戏。默认元素是那些我们尚未点击的区块,用于隐藏它下面真实的内容。

首先我们需要一些可用的图片。为了简单起见,可以使用类似 Paint.NET这样的绘图工具,画一个16x16像素的图像。

保存到Assets文件夹后,我们可以在Project项目窗口选择它。



然后我们可以在检视窗口中修改它的导入设置。需要注意,导入设置指定了图像在最终游戏中的大小以及是否要进行压缩。



现在我们可以将图像从Project项目区域拖入到场景。



我们在场景窗口或层级窗口中选中默认元素,然后检查检视窗口。我们要将它的位置设为x=0,y=0。x是水平位置,而y是垂直位置。因为这是个2D游戏,不需要第三个维度,所以我们将z设为0。



我们希望能在用户单击一个元素时获得通知。Unity已经提供了一个函数可用于实现此目的,不过仅对有碰撞器的元素才有效。一个碰撞器可以使我们的对象成为物理世界的一部分。现在我们的默认元素只是游戏世界中的一个图像。一旦为它添加了碰撞器,它就能成为物理世界的一部分,就像一面墙一样。

要添加一个碰撞器,可以在检视窗口中选择:Add Component->Physics 2D->Box Collider 2D。这样,它现在是物理世界的一部分了。



如果我们点击运行,就能看到游戏中的第一个元素。



添加更多元素

我们的2D扫雷游戏如果只有一个元素那就太无聊了。我们可以通过刚才的流程,或者右击层级窗口中的default游戏对象并选择“Duplicate” 进行复制,以添加更多的元素。



我们将复制的元素放x=1,y=0的位置。



现在我们可以不停地复制元素,直到有10(水平)x 13(垂直)个元素。



底部元素的坐标是x=0,y=0。右上角的元素位于x=9,y=12。中间的元素位置坐标应当要四舍五入就像这样x=2,y=3,而非x=2.04,y=3.002。

现在我们的游戏界面是不是看上去已经有点像扫雷游戏的模样了!

关于邻接

让我们花点时间了解下邻接地雷的情况,这将是我们扫雷游戏的重要部分。

点击一个非地雷元素后,用户应当能看到一个指示邻接地雷数量的数字。我们将采取一种称为8向邻接检测的手段。或者换而言之,我们不仅会检测顶/底/左/右,同时还要检测左上/右上/左下/右下的元素。

这里使我们会遇到的九种不同情况:



所以我们要计算每个块的邻接地雷数量,然后绘制数字。如果没有邻接地雷,则什么也不绘制。

添加更多图像:数字与地雷

为了要绘制那些数字,我们可以使用Unity的GUI系统或为简单地为每个数字快速绘制一个纹理。然后将它们保存到项目的Assets文件夹中。



我们还需要用到一个地雷的图像。将它们保存到项目的Assets文件夹中。

我们将所有那些图像保存到Project窗口中后,要选择它们,并在检视窗口中为它们应用以下这些导入设置。



开始编码

现在开始编写代码。右击Project窗口,选择Create->C# Script,并命名为Element。



这个脚本目前没有任何作用,让我们选择层级窗口中的所有default元素,然后通过点击检视窗口中的Add Component->Script->Element,将脚本添加到它们上面。



在Project项目窗口中双击并打开脚本。



  1. using UnityEngine;
  2.   using System.Collections;

  3.   public class Element : MonoBehaviour {
  4.       // 初始化
  5.       void Start () {
  6.       }

  7.       // 每帧调用一次Update
  8.       void Update () {
  9.       }
  10.   }
点击此处复制文本


我们可以移除Update函数,因为不需要它。然后我们添加一个变量,表明当前元素是否是一个地雷。


  1. using UnityEngine;
  2. using System.Collections;

  3. public class Element : MonoBehaviour {
  4.     // 这是一颗地雷吗?
  5.     public bool mine;

  6.     // 初始化
  7.     void Start () {
  8.     }
  9. }
点击此处复制文本


变量mine是公共的,这样其它元素才能看到它。Start函数会在游戏开始时被调用。

通过在Start函数中使用Random.value,现在我们可以随机决定这个元素是否是一颗地雷。Random.value总会返回一个介于0和1之间的新随机数。如果我们希望元素有15%的概率是一颗地雷,所以我们将使用Random.value<0.15。


  1. using UnityEngine;
  2. using System.Collections;

  3. public class Element : MonoBehaviour {

  4.     // 这是一颗地雷吗?
  5.     public bool mine;

  6.     // 初始化
  7.     void Start () {
  8.         // 随机决定它是否是一颗地雷
  9.         mine = Random.value < 0.15;
  10.     }
  11. }
点击此处复制文本


现在让我们创建一个小小的辅助函数。我们希望能随时从默认纹理切换为空纹理、某个数字纹理或地雷纹理。

首先我们要定义一些纹理变量。


  1. using UnityEngine;
  2. using System.Collections;

  3. public class Element : MonoBehaviour {

  4.     // 这是一颗地雷吗?
  5.     public bool mine;

  6.     // 不同纹理
  7.     public Sprite[] emptyTextures;
  8.     public Sprite mineTexture;

  9.     // 初始化
  10.     void Start () {
  11.         //  随机决定它是否是一颗地雷
  12.         mine = Random.value < 0.15;
  13.     }
  14. }
点击此处复制文本


Sprite是纹理的另一种说法。Sprite[]则是一个数组,也就是不止一个Sprite。

现在我们可以在检视窗口中看到一些新的栏位。



可以将纹理拖动其中。让我们把Project项目窗口中的纹理一个个拖入到这些栏位。



现在我们可以通过loadTexture函数使用我们的Sprite变量了。


  1. using UnityEngine;
  2. using System.Collections;

  3. public class Element : MonoBehaviour {

  4.     // 这是一颗地雷吗?
  5.     public bool mine;

  6.     // 不同纹理
  7.     public Sprite[] emptyTextures;
  8.     public Sprite mineTexture;

  9.     // 初始化
  10.     void Start () {
  11.         // 随机决定它是否是一颗地雷
  12.         mine = Random.value < 0.15;
  13.     }

  14.     //加载另一个纹理
  15.     public void loadTexture(int adjacentCount) {
  16.         if (mine)
  17.             GetComponent<SpriteRenderer>().sprite = mineTexture;
  18.         else
  19.             GetComponent<SpriteRenderer>().sprite = emptyTextures[adjacentCount];
  20.     }
  21. }
点击此处复制文本


这个函数会首先检测元素是不是地雷。如果是,就加载地雷纹理。如果不是,就加载emptyTextures(数字)中的一个,具体根据adjacentCount而定。GetComponent<SpriteRenderer>().sprite 就是我们更改当前纹理的方式。

可以对Start函数暂时做下修改,以测试我们的函数。


  1. //初始化
  2. void Start () {
  3.     // 随机决定它是否是一颗地雷
  4.     //mine = Random.value < 0.15;
  5.     // 测试
  6.     loadTexture(1);
  7. }
点击此处复制文本


如果按下运行,我们可以看到每个元素都载入了数字1的纹理,如下图所示,则代表正确。



现在我们可以把Start函数还原回去了。


  1. // 初始化
  2. void Start () {
  3.     // 随机决定它是否是一颗地雷
  4.     mine = Random.value < 0.15;
  5. }
点击此处复制文本


随后我们将需要知道某个元素是否仍被覆盖,例如:尚未点击。所以让我们添加一个小函数,简单地将当前纹理名与默认名做下比较。

  1. //是否被覆盖?
  2. public bool isCovered() {
  3.     return GetComponent<SpriteRenderer>().sprite.texture.name == "default";
  4. }
点击此处复制文本


只要默认纹理还在就说明元素还未被显示。反之,如果我们加载了一个不同的纹理,比如地雷或某个数字,就说明它已被显示。

我们还要给Element脚本添加一个函数,用于检测鼠标的点击。每个元素都已经附加了一个Collider2D组件,这意味着只要点击某个元素,Unity就会调用函数OnMouseUpAsButton。当然,这首先要在脚本中有这么一个函数:

  1. void OnMouseUpAsButton() {
  2.     // ToDo: do stuff..
  3. }
点击此处复制文本


当点击一个元素时,可能会发生二种情况:要么是地雷,要么不是。

  1. void OnMouseUpAsButton() {
  2.     // 这是一个地雷
  3.     if (mine) {
  4.         // ToDo: do stuff..
  5.     }
  6.     //这不是一个地雷
  7.     else {
  8.         // ToDo: do stuff..
  9.     }
  10. }
点击此处复制文本


如果这是个地雷,那应当显示所有地雷,然后游戏结束。

  1. void OnMouseUpAsButton() {
  2.     //这是一个地雷
  3.     if (mine) {
  4.         // ToDo: 显示所有的地雷
  5.         // ...
  6.         // 游戏结束
  7.         print("you lose");
  8.     }
  9.     //这不是一个地雷
  10.     else {
  11.         // ToDo: do stuff..
  12.     }
  13. }
点击此处复制文本


如果不是地雷,那可能会发生好几种情况。首先,我们要根据邻接地雷数量载入正确数字的空纹理。如果点击的元素没有任何邻接地雷,那我们应该显示整个没有地雷的元素区域,如下图所示。



我们还应该判断是否所有除地雷外的元素都已被显示,这种情况下游戏就已获胜。这是第一个版本的代码。

  1. void OnMouseUpAsButton() {
  2.         // I这是个地雷
  3.         if (mine) {
  4.             // ToDo: 显示所有的地雷
  5.             // ...
  6.             //游戏结束
  7.             print("you lose");
  8.         }
  9.         // 这不是个地雷
  10.         else {
  11.             // 显示邻接地雷数量
  12.             //loadTexture(...);
  13.             //显示所有无雷区域
  14.             // ...
  15.             //判断游戏是否已获胜
  16.             // ...
  17.         }
  18.     }
点击此处复制文本


我们所有的ToDo功能都有一个共同点:它们除了需要元素本身的信息之外,都需要访问其它元素。所以让我们再创建一个脚本,用来处理所有元素。

小结

使用Unity 2D实现经典扫雷游戏的上篇,就为大家介绍到这里。





评分

参与人数 1元素币 +10 活跃度 +12 展开 理由
源支始 + 10 + 12

查看全部评分

2019-11-14 17:06:45  
 赞 赞 0

使用道具 登录

0个回答,把该问题分享到群,邀请大神一起回答。

CG 游戏行业专业问题

手机游戏引擎手游引擎Unity3D技术
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表