查看原文
其他

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

Unity Unity官方平台 2018-11-15

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



这篇教程使用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项目窗口中双击并打开脚本。


  using UnityEngine;

  using System.Collections;


  public class Element : MonoBehaviour {

      // 初始化

      void Start () {

      }


      // 每帧调用一次Update

      void Update () {

      }

  }


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


using UnityEngine;

using System.Collections;


public class Element : MonoBehaviour {

    // 这是一颗地雷吗?

    public bool mine;


    // 初始化

    void Start () {

    }

}


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


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


using UnityEngine;

using System.Collections;


public class Element : MonoBehaviour {


    // 这是一颗地雷吗?

    public bool mine;


    // 初始化

    void Start () {

        // 随机决定它是否是一颗地雷

        mine = Random.value < 0.15;

    }

}


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


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


using UnityEngine;

using System.Collections;


public class Element : MonoBehaviour {


    // 这是一颗地雷吗?

    public bool mine;


    // 不同纹理

    public Sprite[] emptyTextures;

    public Sprite mineTexture;


    // 初始化

    void Start () {

        //  随机决定它是否是一颗地雷

        mine = Random.value < 0.15;

    }

}


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


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


 

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


 


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


using UnityEngine;

using System.Collections;


public class Element : MonoBehaviour {


    // 这是一颗地雷吗?

    public bool mine;


    // 不同纹理

    public Sprite[] emptyTextures;

    public Sprite mineTexture;


    // 初始化

    void Start () {

        // 随机决定它是否是一颗地雷

        mine = Random.value < 0.15;

    }


    //加载另一个纹理

    public void loadTexture(int adjacentCount) {

        if (mine)

            GetComponent<SpriteRenderer>().sprite = mineTexture;

        else

            GetComponent<SpriteRenderer>().sprite = emptyTextures[adjacentCount];

    }

}


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

 

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

//初始化

void Start () {

    // 随机决定它是否是一颗地雷

    //mine = Random.value < 0.15;

    // 测试

    loadTexture(1);

}


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



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

// 初始化

void Start () {

    // 随机决定它是否是一颗地雷

    mine = Random.value < 0.15;

}


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

//是否被覆盖?

public bool isCovered() {

    return GetComponent<SpriteRenderer>().sprite.texture.name == "default";

}

 

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

 

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

void OnMouseUpAsButton() {

    // ToDo: do stuff..

}

  

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

void OnMouseUpAsButton() {

    // 这是一个地雷

    if (mine) {

        // ToDo: do stuff..

    }

    //这不是一个地雷

    else {

        // ToDo: do stuff..

    }

}


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

void OnMouseUpAsButton() {

    //这是一个地雷

    if (mine) {

        // ToDo: 显示所有的地雷

        // ...

        // 游戏结束

        print("you lose");

    }

    //这不是一个地雷

    else {

        // ToDo: do stuff..

    }

}


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


 

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

void OnMouseUpAsButton() {

        // I这是个地雷

        if (mine) {

            // ToDo: 显示所有的地雷

            // ...

            //游戏结束

            print("you lose");

        }

        // 这不是个地雷

        else {

            // 显示邻接地雷数量

            //loadTexture(...);

            //显示所有无雷区域

            // ...

            //判断游戏是否已获胜

            // ...

        }

    }


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

 

小结

使用Unity 2D实现经典扫雷游戏的上篇,就为大家介绍到这里。在下篇中将采用网格来处理更加复杂的游戏逻辑,从而完整的实现游戏。尽请期待!更多精彩Unity教程尽在Unity中文官方论坛(Unitychina.cn)!


推荐阅读


官方活动

直播预告 | 使用Shader Graph着色器视图快速创建炫酷特效

直播时间:4月11日 20:00-21:00

活动网址:https://connect.unity.com/events/unitychina-shadergraph


赢取15万美元奖金|环球影业经典IP游戏开发大奖赛

活动信息:截至至4月20日 16:00

活动网址:https://connect.unity.com/challenges/universal


Unite Beijing 2018 及 Training Day

活动信息:5月11-13日 北京国家会议中心

售票官网: http://unite2018.csdn.net/  或者直接扫描下图二维码进行购票!



点击“阅读原文”访问Unity中文官方论坛!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存