请选择 进入手机版 | 继续访问电脑版

查看: 726|回复: 0

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

[复制链接]

1012

主题

1694

帖子

2万

贡献

管理员

Rank: 9Rank: 9Rank: 9

积分
21960
QQ
发表于 2018-4-8 03:49:16 | 显示全部楼层 |阅读模式
相信我们很多人都玩过扫雷游戏,它是一个单人解谜游戏,最早于上世纪60年代发布。游戏的目标是探索雷区,努力不触发地雷。每显示一个无雷区域,游戏会显示一个数字,表明四周的地雷数量。这为游戏增加了一个不错的策略因素。



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

开发准备
本篇教程无需任何特殊的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项目窗口中双击并打开脚本。

[C#] 纯文本查看 复制代码
  using UnityEngine;
  using System.Collections;

  public class Element : MonoBehaviour {
      // 初始化
      void Start () {
      }

      // 每帧调用一次Update
      void Update () {
      }
  }


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

[C#] 纯文本查看 复制代码
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。

[C#] 纯文本查看 复制代码
using UnityEngine;
using System.Collections;

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

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


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

[C#] 纯文本查看 复制代码
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变量了。

[C#] 纯文本查看 复制代码
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函数暂时做下修改,以测试我们的函数。

[C#] 纯文本查看 复制代码
//初始化
void Start () {
    // 随机决定它是否是一颗地雷
    //mine = Random.value < 0.15;

    // 测试
    loadTexture(1);
}


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



现在我们可以把Start函数还原回去了。
[C#] 纯文本查看 复制代码
// 初始化
void Start () {
    // 随机决定它是否是一颗地雷
    mine = Random.value < 0.15;
}


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

[C#] 纯文本查看 复制代码
//是否被覆盖?
public bool isCovered() {
    return GetComponent<SpriteRenderer>().sprite.texture.name == "default";
}


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

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

[C#] 纯文本查看 复制代码
void OnMouseUpAsButton() {
    // ToDo: do stuff..
}

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

[C#] 纯文本查看 复制代码
void OnMouseUpAsButton() {
    // 这是一个地雷
    if (mine) {
        // ToDo: do stuff..
    }

    //这不是一个地雷
    else {
        // ToDo: do stuff..
    }
}


如果这是个地雷,那应当显示所有地雷,然后游戏结束。
[C#] 纯文本查看 复制代码
void OnMouseUpAsButton() {
    //这是一个地雷
    if (mine) {
        // ToDo: 显示所有的地雷
        // ...
        // 游戏结束
        print("you lose");
    }

    //这不是一个地雷
    else {
        // ToDo: do stuff..
    }
}


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



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

[C#] 纯文本查看 复制代码
void OnMouseUpAsButton() {
        // I这是个地雷
        if (mine) {
            // ToDo: 显示所有的地雷
            // ...
            //游戏结束
            print("you lose");
        }

        // 这不是个地雷
        else {
            // 显示邻接地雷数量
            //loadTexture(...);
            //显示所有无雷区域
            // ...
            //判断游戏是否已获胜
            // ...
        }
    }


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

小结
使用Unity 2D实现经典扫雷游戏的上篇,就为大家介绍到这里。在下篇中将采用网格来处理更加复杂的游戏逻辑,从而完整的实现游戏。尽请期待

本帖子中包含更多资源

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

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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