「申し訳ありませんが、私は釣りを選びます」-「マインスイーパ」の小さなゲーム開発実際の戦闘、アルゴリズム、ソースコード、Unity3D開発に基づく

「私はナゲッツコミュニティゲームクリエイティビティコンテストの個人コンテストに参加しています。詳細については、ゲームクリエイティビティコンテストをご覧ください。 」

おすすめの読み物

みなさん、こんにちは。私は仏教のエンジニアです☆静かな小さな魔法のドラゴン☆。Unityの開発スキルを随時更新しています。便利だと思ったら、忘れずに3回クリックしてください。

I.はじめに

今日はまた釣りをしています...ああ、いや、仕事の日です。今日は、非常に古典的なWinシステムの組み込みゲーム「Minesweeper」があります。Win10をインストールした後、「Minesweeper」ゲームが見つかりません。非常に不快です。

マインスイーパのゲームプレイは、地雷に触れることなく地雷原を見つけることです。

地雷のないブロックを発見すると、周囲の地雷の数を示す数字が表示されます。

いつものように、手順を詳しく説明し、誰もが理解できるようにコードにコメントを付けます。

2.テキスト

2-1.新しいプロジェクト

(1)プロジェクト開発、新しいプロジェクトから開始して、使用するUnityのバージョンはUnity 2019.4.7f1、テンプレートが選択され2D、プロジェクト名はオプションです。中国語ではありません。

image.png

(2)ディレクトリを作成し、プロジェクトビューで右クリックして[作成]→[フォルダ]を選択し、いくつかの新しいフォルダを作成します。

image.png

(3)ディレクトリは以下のとおりです。

image.png

  • プレハブ:プレハブリソースフォルダー
  • シーン:シーンリソースフォルダー
  • スクリプト:スクリプトリソースフォルダー
  • スプライト:画像リソースフォルダー

2-2.リソースのインポート

次に、必要なリソースをインポートします。

default.png null.png 1.png 2.png 3.png 4.png 5.png 6.png 7.png 8.png mine.png

すべてを右クリックして画像として保存し、プロジェクトビューのスプライトフォルダにインポートします。

image.png

すべての画像を選択した状態で、インスペクタービューで、ユニットあたりのピクセル数を16に設定します。

image.png

16に設定されている理由は、ゲームの世界では16X16の方が適しているためです。

2-3.カメラのプロパティを設定する

階層ビューで、メインカメラオブジェクトを選択し、インスペクタービューでカメラコンポーネントを見つけて、プロパティを設定します。

image.png

注:[フラグのクリア]は[スカイボックス]に設定され、[背景]は図のように設定され、[サイズ]は20に設定されています。

2-4、制作默认方块

(1)将Project视图的Sprites目录中的default对象拖入Hierarchy视图中:

image.png

(2)选中default对象,在Inspector视图中,选择Add Componet→Physics 2D→Box Collider 2D,添加碰撞器组件:

image.png

注意:勾选Is Trigger

(3)选中default对象,拖回到Projcet视图的Prefabs文件夹内,做成一个预制体,我们将在后面的代码中去实例化生成它:

image.png

(4)Hierarchy视图中的default对象就可以删除了。

(5)新建脚本CreateBg.cs,在Projec视图的Scripts目录中,右击选择Create→C# Script:

image.png

双击打开脚本,编辑代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateBg : MonoBehaviour
{
    public GameObject block;//默认方块

    void Start()
    {
        //创建默认方块
        CreateBlock();
    }

    private void CreateBlock()
    {
        //创建方块父物体
        GameObject blockParent = new GameObject("blockParent");
        //创建10行10列的默认方块
        for (int i = 0; i < 10; i++)
        {
            for (int j = 0; j < 10; j++)
            {
                //Instantiate参数为:预制体 位置 旋转 父物体
                Instantiate(block, new Vector2(i, j), Quaternion.identity, blockParent.transform);
            }
        }
    }
}
复制代码

将脚本托给Main Camera对象,然后将预制体拖入Block卡槽中:

image.png

运行脚本:

image.png

是不是有点样子了,这个基本界面就做好了。

2-5、相邻的概念

让我们花一分钟的时间来分析一下相邻的概念,这是《扫雷》游戏中重要的一个部分。

单击一个非地雷的元素后,可以看到指示相邻地雷数量的数字,也就是这个数字的周围有这个数字的雷的数量,一共有9种情况:

image.png

因此,我们需要做的就是计算每个字段的相邻的地雷数量,然后得出数字,如果没有相邻的地雷,则为空。

2-6、制作数字和地雷

(1)新建一个脚本Element.cs,然后在Project视图的Prefabs文件夹中选中default对象,点击Add Componet→Element添加脚本:

image.png

(2)双击打开Element.cs,编辑代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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];
    }

    // 判断是否被点击
    public bool isCovered()
    {
        //判断当前纹理的名称是不是默认值
        return GetComponent<SpriteRenderer>().sprite.texture.name == "default";
    }

    // 鼠标点击
    void OnMouseUpAsButton()
    {
        // 是雷的话
        if (mine)
        {
            // 揭露所有雷
            // ...

            // 游戏结束
            Debug.Log("Game Over");
        }
        else
        {
            // 显示相邻的数字号
            // loadTexture(...);

            // 揭露没有地雷的地区  
            // ...

            // 判断游戏是否胜利
            // ...
        }
    }

}
复制代码

(3)选中default预制体,将对应的资源拖入Element.cs脚本的属性卡槽中:

image.png

(4)新建一个Grid.cs脚本,将脚本也添加到预制体default身上,Grid脚本将处理更加复杂的游戏逻辑,比如计算某个元素相邻的地雷,或者发现整个区域的无雷位置:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Grid : MonoBehaviour
{
    // 创建一个二维数组网格
    public static int w = 10; // 网格的长
    public static int h = 10; // 网格的高
    public static Element[,] elements = new Element[w, h];

    // 发现所有地雷
    public static void uncoverMines()
    {
        foreach (Element elem in elements)
            if (elem.mine)
                elem.loadTexture(0);
    }


    // 看看这个坐标上是否有地雷
    public static bool mineAt(int x, int y)
    {
        // 控制坐标范围
        if (x >= 0 && y >= 0 && x < w && y < h)
            return elements[x, y].mine;
        return false;
    }

    // 为一个元素计算相邻的地雷 8个方向
    public static int adjacentMines(int x, int y)
    {
        //计数器
        int count = 0;
        if (mineAt(x, y + 1)) ++count; // 上
        if (mineAt(x + 1, y + 1)) ++count; // 右上
        if (mineAt(x + 1, y)) ++count; // 右
        if (mineAt(x + 1, y - 1)) ++count; // 右下
        if (mineAt(x, y - 1)) ++count; // 下
        if (mineAt(x - 1, y - 1)) ++count; // 左下
        if (mineAt(x - 1, y)) ++count; // 做
        if (mineAt(x - 1, y + 1)) ++count; // 左上
        //返回相邻的地雷数量
        return count;
    }
}
复制代码

(5)修改Element.cs脚本代码:

Start函数修改:

void Start()
{
       // 随机决定它是否是地雷
       mine = Random.value < 0.15;

       // 在Grid注册
       int x = (int)transform.position.x;
       int y = (int)transform.position.y;
       Grid.elements[x, y] = this;
}
复制代码

OnMouseUpAsButton函数修改:

    // 鼠标点击
    void OnMouseUpAsButton()
    {
        // 是雷的话
        if (mine)
        {
            // 揭露所有雷
            Grid.uncoverMines();

            // 游戏结束
            Debug.Log("Game Over");
        }
        else
        {
            // 显示相邻的数字号
            int x = (int)transform.position.x;
            int y = (int)transform.position.y;
            loadTexture(Grid.adjacentMines(x, y));

            // 揭露没有地雷的地区  
            // ...

            // 判断游戏是否胜利
            // ...
        }
    }
复制代码

プログラムを実行すると、地雷をクリックすると、他の地雷も発見されることがわかります。要素を見つけたら、隣接する番号を確認できます。

image.png

2-7、フラッディングアルゴリズム

わかりました。ユーザーが隣接する地雷のない要素を見つけると、次のように、隣接する地雷のないエリア全体が自動的に検出されます。

aHR0cHM6Ly9ub29idHV0cy5jb20vY29udGVudC91bml0eS8yZC1taW5lc3dlZXBlci1nYW1lL3VuY292ZXJfbWluZWxlc3NfZWxlbWVudHMuZ2lm.gif

これを実行できるアルゴリズムはたくさんありますが、最も単純なアルゴリズムはフラッディングアルゴリズムです。再帰を理解していれば、フラッディングアルゴリズムもよく理解されています。フラッディングアルゴリズムの機能は次のとおりです。

  • 要素から始める
  • この要素でやりたいことをする
  • 隣接する要素ごとに再帰的に続行します

次に、フラッディングアルゴリズムをGridクラスに追加します。

    // 泛洪算法填充空元素
    public static void FFuncover(int x, int y, bool[,] visited)
    {
        if (x >= 0 && y >= 0 && x < w && y < h)
        {
            // 判断是否遍历过
            if (visited[x, y])
                return;

            // 设置遍历标识
            visited[x, y] = true;

            // 递归
            FFuncover(x - 1, y, visited);
            FFuncover(x + 1, y, visited);
            FFuncover(x, y - 1, visited);
            FFuncover(x, y + 1, visited);
        }
    }
复制代码

注:フラッディングアルゴリズムは、各要素に到達するまで、要素の周囲の要素を再帰的に訪問します。

次に、フラッディングアルゴリズムを変更します。これにより、訪問する要素が地雷であるかどうかが確認され、そうである場合は続行されません。

    // 泛洪算法填充空元素
    public static void FFuncover(int x, int y, bool[,] visited)
    {
        if (x >= 0 && y >= 0 && x < w && y < h)
        {
            // 判断是否遍历过
            if (visited[x, y])
                return;

            // 发现元素
            elements[x, y].loadTexture(adjacentMines(x, y));

            // 发现地雷
            if (adjacentMines(x, y) > 0)
                return;

            // 设置遍历标识
            visited[x, y] = true;

            // 递归
            FFuncover(x - 1, y, visited);
            FFuncover(x + 1, y, visited);
            FFuncover(x, y - 1, visited);
            FFuncover(x, y + 1, visited);
        }
    }
复制代码

Element.csスクリプトに戻り、OnMouseUpAsButton関数を変更して、ユーザーが要素の1つをクリックしたときに、このアルゴリズムを使用してすべての空の要素を検索します。

    // 鼠标点击
    void OnMouseUpAsButton()
    {
        // 是雷的话
        if (mine)
        {
            // 揭露所有雷
            Grid.uncoverMines();

            // 游戏结束
            Debug.Log("Game Over");
        }
        else
        {
            // 显示相邻的数字号
            int x = (int)transform.position.x;
            int y = (int)transform.position.y;
            loadTexture(Grid.adjacentMines(x, y));

            // 揭露没有地雷的地区  
            Grid.FFuncover(x, y, new bool[Grid.w, Grid.h]);

            // 判断游戏是否胜利
            // ...
        }
    }
复制代码

プログラムを実行します。空の要素が見つかると、トラバースして、周囲に地雷のない空の要素があるかどうかを確認します。

aH.gif

2-8.すべての地雷が見つかったかどうかを確認します

次に、プレイヤーがすべての地雷を見つけたかどうかを判断する必要があります。そうすれば、ゲームは終了します。

次に、Gridクラスのコードを変更し、関数isFinishedを追加します。

    // 是否找到所有地雷

    public static bool isFinished()
    {
        // 遍历数组 找到没有被地雷覆盖的元素
        foreach (Element elem in elements)
            if (elem.isCovered() && !elem.mine)
                return false;
        // 没有找到 => 全是地雷 => 游戏胜利.
        return true;
    }
复制代码

Element.csのコードを変更します。

    // 鼠标点击
    void OnMouseUpAsButton()
    {
        // 是雷的话
        if (mine)
        {
            // 揭露所有雷
            Grid.uncoverMines();

            // 游戏结束
            Debug.Log("Game Over");
        }
        else
        {
            // 显示相邻的数字号
            int x = (int)transform.position.x;
            int y = (int)transform.position.y;
            loadTexture(Grid.adjacentMines(x, y));

            // 揭露没有地雷的地区  
            Grid.FFuncover(x, y, new bool[Grid.w, Grid.h]);

            // 判断游戏是否胜利
            if (Grid.isFinished())
                Debug.Log("Game Win");
        }
    }
复制代码

プログラムを実行すると、楽しくゲームをプレイできます。

3.まとめ

「マインスイーパ」ゲームの一般的なフレームワークが開発されました。もちろん、ゲームをより面白くするためにいくつかの要素を追加することもできます。

  • 地雷をマーカーでマークする
  • 簡単、中程度、難しいなど、より多くの難易度に分けられます
  • より美しいUIに切り替えます
  • 画面に勝って負けて再起動する
  • 効果音を追加する

おすすめ

転載: juejin.im/post/7083735364233330725