2048をUnityで作る


チュートリアル作成日

 

2023年11月20日

難易度上級だと思います。

 

ゲームのルール

4×4の盤面に数字の書かれたタイルが並んでいる。上下左右にスワイプするとタイルが移動し、同じ数字のタイルはくっついて合計された数字になる。これを繰り返して2048を目指す。

(インターネットで無料で遊べる2048があるので、一度遊んでみるとゲームを作りやすいと思います)

 

制作環境

 

Unity2022.3.11f1

C#

 

完成予想画像

遊び方

 

プレイヤーは画面をスワイプして、タイルを移動し、より大きな数字を作っていく。

 

 

目次

今回の個人的な挑戦

今回の考え方

下準備

ゲームボードの作成(3番目にややこしい)

タイルの作成

タイルをゲームボードに出す

スワイプの実装(2番目にややこしい)

マージのプログラミング(一番ややこしいところ)

ゲームオーバー判定

効果音を出す

スコア表示など


今回の個人的な挑戦について

1.今回はUIのImageをゲームオブジェクトにしてみることにしました。いつもはSprite画像をHierarchyに持っていき、そこからプレハブを作ってましたが、今回はそれをUIでやってみました。このメリットは画面サイズが異なるスマホでもきれいに表示されるところです。

結論:いつもより手数が多かったけど、なんとかできました。

 

2.Dotweenを使ってみました。アニメーションをコルーチンではなく、Dotweenという無料アセットで作りました。

結論:エラーが出まくってあきらめかけたけど、なんとかできました。

 


今回の考え方

・Unityエディタでゲームボードと各マスを作成しておく。

・マスを配列で管理する。

・プレハブ化されたタイルは、マスの上を移動する。

 

タイルそのものを配列で管理すればよいところ、Canvasはtransformの座標がよくわからなかったので、マスの座標を利用することにした。

タイルには数字と色のデータを持たせる。

マスには、いま自分の上に乗っているタイルの数字と、タイルオブジェクトを認識させる。

 


下準備

今回もAndroid向けに作りますが、WebGLでもiPhoneでもやることはほぼ同じです。

 

プロジェクトの新規作成

 

2D。(コアじゃない方で)

名前、保存場所はご自由に。

 

プラットフォームは何でもOK。PCでもAndroidでも。このチュートリアルではAndroidスマホタテ型ゲームとして作っていきます。

AndroidへSwitch Platform

 

Unity > File > BuildSettings

 

Androidを選択して、Switch Platformをクリック。2分程度で処理が終わる。

 

Gameウィンドウにて、1080x1920 portraitにする。(ここはお好みで)

 

 

Sceneの名前を変更する。

 

Project > Sceneフォルダ > SampleSceneで右クリック。名前をGameに変更。

 

 

2つのフォルダを作る。

 

Project > Assetsにて右クリック Create > Folderを選択。名前をPrefabsにする。

 

同じ要領で、Scriptsのフォルダも作成する。

 

 

Dotweenのダウンロード(無料の方ね!)

 

UnityのアセットストアでDotweenを入手します。

https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676

 

Unityにサインインしている必要があったかな?

 

ダウンロードできたら、Unity > Window > Package Manager > My Assets Tab > DOTween > Import > set up

各段階でけっこう時間かかります。

以下のサイトなどでインストール方法を教えてくれていますから参考にしてください。

https://shibuya24.info/entry/unity-install-dotween

 


ゲームボードの作成

ゲームボードを作る

Unity > Hierarchyで右クリック、UI > Imageを選択。名前をBoard

 

Canvasの設定

HierarchyでCanvasを選択。

Inspector > Canvas > > Render Mode > Screen Space - Camera

 

Render CameraにMain Cameraをアサイン

 

Canvas Scaler > UI Scale Mode > Scale With Screen Size

 

Reference Resolution > X:1080, Y:1920

 

Screen Match Mode > Expand (or Match Width Or Height > Match 0.5)

 

ゲームボードの調整

Inspector > Rect Transform > PosX,PosY = 0

Width:1000, Height:1000 (これはお好み、800くらいでもいいかも。以降、ここが1000として進めます)

 

Image > Color お好み(たとえばF5F8E6)

 

で、ここから少し面倒くさい。

 

Hierarchy > Boardを右クリックして、Create Empty 名前をRowにする。(行列の行という意味)

 

次は、Rowを選択して右クリック、UI > Image名前をSquareにする。

Width:230, Height:230にしてみました。

Image > Colorを灰色っぽい色に。たとえばB1A8B4。

 

そしたら、Rowを複製して4つにする。Squareは16個になる。

 

そうしたら、Rowを選んで、Inspector > Add Compnent > Layout > Horizontal Layout Group

Inspector > Horizontal Layout Group > Spacing:16 お好みで。

 

するときれいに横に並ぶと思います。

 

今度は、Boardに、Vertical Layout GroupをAdd Component

Padding > Left:16, Top:16にしてみました。

綺麗に並ぶようにいじくってみてください。

 


タイルの作成

次はタイルを作ります。

先ほど作ったSquareのどれかの上で右クリック、UI > Image 名前をTileに。

サイズをX:230,Y:230にして、Squareの上にぴったり乗るように。

色は後でスクリプトで変えていきますが、とりあえずピンク色っぽいものに。

 

そしたら、そのTileで右クリック、UI > Text - TextMeshPro。

Textを2とかにして、タイルの真ん中に来るようにポジションを調整。

Inspector > TextMeshPro > Font Style 太字、Auto Sizeにチェック、Min 18, Max 70とか。

Alignmentは左右上下とも真ん中に。

 

Inspectorの下の方にShaderというのがあるので、

Face > Dilateをいじると文字がかわいらしくなったりするのでいじくってみる。 

 

スクリプトを作ります。

Project > Scriptsで右クリック、Create > C# Script。名前をTileに。

 

Tileスクリプトを、Tileオブジェクトにアサイン。

ここまでできたら、HierarchyのTileを、Project > Prefabsへドラッグしてプレハブ化。

 

 

スクリプトを開いてVisual Studioへ。

 

using TMPro;// 忘れずに!!

using UnityEngine;

using UnityEngine.UI;

 

// 冒頭にこんな呪文を書く。各タイルの色と数字のデータクラス。

[System.Serializable]

public class TileData

{

    public Color color;

    public int value;

}

 

public class Tile : MonoBehaviour

{

    public TileData[] tileData; // 色と数字のデータの配列。上で作ったTileDataのこと。Unityインスペクターで設定。

    public Image imageColor; // タイルの色

    public int value; //このタイルの数字。上のTileDataとは別物だけど、リンクする。

    public TMP_Text valueText; // タイルの数字を表示する。

 

 

 

    // タイルの値を設定するメソッド。GameManagerから呼び出される関数。

    public void SetValue(int newValue)

    {

        // 与えられた数字に対応するデータを取得

        TileData data = GetTileData(newValue);

 

        // データが存在すれば、色や数字を設定

        if (data != null)

        {

            imageColor.color = data.color;

            value = data.value;

            valueText.text = value.ToString();

        }

    }

 

    // 数字に対応するデータを取得するメソッド

    TileData GetTileData(int targetValue)

    {

        foreach (TileData data in tileData)

        {

            if (data.value == targetValue)

            {

                return data;

            }

        }

 

        return null; // 対応するデータが見つからない場合

    }

 

}

 

 

 

保存してUnityへ。

 

タイルの色と数字を設定していきます。

Project > Prefabs > Tile

Inspector > Tile(Script)

> Image Color > Tile自身をアサイン

> Value Text > Text-tmp(自身のTMPをアサイン)

 

で、タイルデータを作っていきます。

Tile Dataのプラスマークを押して、Elementを作っていきます。

Valueは、2,4,8,16,32,64,128,256,512,1024,2048,4096くらいまで作っておけばよいと思います。

色はお好みです。

がんばって色を付けてください。

 

最後に、TileのRect Transform > Scale > X:0.05,Y:0.05にしておきます。小さくします。登場するときにアニメーションで大きくします。


タイルをゲームボードに出す

いま作ってきたタイルをゲームボードに表示していきます。

その前に、マスのスクリプトを作ります。

 

Project > Scriptsで右クリック、Create > C# Script 名前をSquareScriptに。

これを16個すべてのSquareにアサイン。

 

SquareScriptsを開きます。

 

using UnityEngine;

 

public class SquareScript : MonoBehaviour

{

    public GameObject TileOnMe; // このマス目の上にあるタイルを入れる。

    public int tileValue; // 上に乗っているタイルの数字を入れる。

    

 

}

 

 

Unityに戻ります。

 

Unity > Hierarchy で右クリック、Create Empty、名前をGameManagerに。

Project > Scripts で右クリック、Create > C# Script、名前をGameManagerに。アイコンがかっこよくなります。

GameManagerスクリプトをGameManagerオブジェクトにアサイン。

 

スクリプトを開いて書いていきます。今回はこのGameManagerが長くなります。

 

 

//usingはデフォルトに加えて、以下を追記。

using DG.Tweening;// Dotweenです。アニメーションします。

 

 

public class GameManager : MonoBehaviour

{

 

    // マス目オブジェクトを認識しておく。タイルの移動でマス目の座標を使う。

    [SerializeField] private GameObject[] squares; //マス目オブジェクト配列。Unityで16個アサインする。

    private GameObject[,] squareArray = new GameObject[4, 4]; // マス目を二次元配列として認識する。[ , ]ですので注意。

   // 生成するタイル関係

   public GameObject tilePrefab;

 

   // Start is called before the first frame update

   void Start()

   {

       SetSquareArray();

       SpawnTile(); //2回。

       SpawnTile();

       

   }

 

   void SetSquareArray() // 4*4のマス配列を作る。 

   {

       // unityでは上の行からアサインしていく予定。0行目が一番上のマス目たち。

       int count = 0;

       for (int i = 0; i < 4; i++) // i行目

    { 

           for (int j = 0; j < 4; j++) // j列目

           {

               squareArray[i,j] = squares[count];

               squareArray[i, j].GetComponent<SquareScript>().tileValue = 0; //とりあえず0にしてしまう。

               count++;

           }

       }

      

   }

 

 

   void SpawnTile()

   {

 

           //空っぽのマスの座標を教えてもらう。

           var emptyCell = FindEmptySquare(); //

 

           // そこにタイルを生成する。

           GameObject newTile = Instantiate(tilePrefab, emptyCell.transform); //squareの位置に生成。

           newTile.transform.SetParent(emptyCell.transform, false);//Squareの子オブジェクトにする。

           RectTransform newTileRect = newTile.GetComponent<RectTransform>();//これでサイズや位置をいじくっていく。

 

           // 新しいタイルのサイズを設定(tilePrefabのサイズをそのままコピー)

           newTileRect.sizeDelta = tilePrefab.GetComponent<RectTransform>().sizeDelta;

           // 新しいタイルを空っぽのマスの上に配置する

           newTileRect.position = emptyCell.GetComponent<RectTransform>().position;

           

           // タイルの数字をランダム生成。

   int newValue = Random.Range(1, 3) * 2;// 2 or 4

           // その数値でもって、タイルの色や数字を指定。これは見た目の設定。

           //newTile.GetComponent<Tile>().SetValue(newValue);

 

           //Dotween----------Dotweenについて知りたい方は各自調べてね。

           newTileRect.DOScale(Vector3.one, 0.1f).SetLink(newTile);

 

           // そのタイルをマス目に認識させる。これは中身の管理。

           emptyCell.GetComponent<SquareScript>().tileValue = newTile.GetComponent<Tile>().value;

           emptyCell.GetComponent<SquareScript>().TileOnMe = newTile;

   

   }

   

GameObject FindEmptySquare() // GameObjectをreturnします。voidじゃないよ。

{

    List<GameObject> emptySquares = new List<GameObject> ();

      

    foreach (var item in squareArray) // マス配列の中から、valueが0のもの。つまり空っぽのものを探す。

    {

       if (item.GetComponent<SquareScript>().tileValue == 0)

       {

             //空っぽのマスのリストを作る。

             emptySquares.Add(item);

       }

    }

 

    if (emptySquares.Count > 0)

    {

        // tileValue=0であるitemの中からランダムでひとつ選びたい。

        int randomIndex = Random.Range(0, emptySquares.Count);

        GameObject selectedSquare = emptySquares[randomIndex];

        //

        return selectedSquare;

 

    }

 

    return null; 

}

 

 

 

Unityに戻ります。

Hierarchy > GameManager 

Inspector > GameManager(Script)に、16個のマスをアサインしていきます。

 

※今回は、上の行から順番につけてください。つまりsquares[0]は左上のマスです。squares[1]はその右隣り。

 

Tile Prefabにはタイルのプレハブをアサイン。

 

これでテストプレイすると、タイルが2個配置されると思います。

色と数字もうまくセットされると思います。どうでしょうか?

何度かテストプレイして、位置やタイルがランダムになっていることを確認してください。

 

 


スワイプの実装

 

次はタイルを動かしていきます。

 

GameManagerスクリプトに書いていきます。

 

   //スワイプ関係

  private Vector2 touchStartPos;

  private float swipeThreshold = 80f; // スワイプを検出するための閾値。これより指の動きが小さければ無反応。ご自由に調整してください。

  private bool canPlay = true;

  private bool tapped = false;

 

 

   void Update()

   {

       

       if (Input.GetMouseButtonDown(0) && canPlay) 

       {

           touchStartPos = Input.mousePosition;

           tapped = true;

       }

       else if (Input.GetMouseButton(0) && canPlay && tapped)

       {

           Vector2 touchEndPos = Input.mousePosition;

 

           float swipeX = touchEndPos.x - touchStartPos.x;

           float swipeY = touchEndPos.y - touchStartPos.y;

          

               // スワイプの方向を判定。まずは横方向の動きが大きい場合。つまり左右どちらか。

               if (Mathf.Abs(swipeX) > Mathf.Abs(swipeY)) //Mathf.Absは絶対値を計算します。

               {

                   // 右方向のスワイプ

                   if (swipeX > swipeThreshold) //閾値を超えている場合はスワイプとみなす。

                   {

                       tapped = false;

                       RightSwipe();

                       

                   }

                   else if (swipeX < -swipeThreshold) //左。

                   {

                       tapped = false;

                       LeftSwipe();

                       

                   }

              

               }

               else // 縦方向

               {

                   // 上方向のスワイプ

                   if (swipeY > swipeThreshold)

                   {

                       tapped = false;

                       UpSwipe();

                       

                   }

                   else if (swipeY < -swipeThreshold) //下。

                   {

                       tapped = false;

                       DownSwipe();

                      

                   }

               }//縦方向おわり

               

 

       }

       else if (Input.GetMouseButtonUp(0) && tapped)

       {

           tapped = false;

           

       }

   }

 

 

とりあえず右方向へのスワイプ(→)の関数を書きます。

 

    // Right Swipe

    void RightSwipe()

    {

        bool canMove = false;

        

        //squareArrayを右列から見ていき、タイルが入っているマスがあれば、それを右に移動する。これを端まで繰り返す。

        for (int row = 0; row < 4; row++) // 上の行から

        {

            for (int column = 3; column >= 0; column--) //一番右の3列目から見て、左の0列まで

            {

    int myNumber;

 

                if (squareArray[row, column].GetComponent<SquareScript>().tileValue != 0) // 何かタイルがあれば。

                {

                    GameObject temp = squareArray[row, column].GetComponent<SquareScript>().TileOnMe;

                    myNumber =  squareArray[row, column].GetComponent<SquareScript>().tileValue;

                    int x = column;

 

                    while (x < 3 && squareArray[row, x + 1].GetComponent<SquareScript>().tileValue == 0) //自分より右が空である限り。一番右は見なくてよい。

                    {

                        canMove = true;

                        x++;

                    }

                  

                    if (!canMove) //移動できない場合は以降の処理はスキップ。

                    {

                        continue;

                    }

                    // いったん置き換えて。

                    GameObject targetSquare = squareArray[row, x];

                    //もと居たマスから移動先のマスへバリューを代入。

                    targetSquare.GetComponent<SquareScript>().tileValue = myNumber;

 

                    //もと居たマスを空っぽに。

                    EmptyMySquare(squareArray[row, column]);

 

                    //Animation

                    TileMoveAnimation(temp, targetSquare);

 

                   // 移動先のマスに、タイルを認識させる。

                    targetSquare.GetComponent<SquareScript>().TileOnMe = temp;

 

                }

            }

        }

        if (canMove)// タイルが移動した場合は、新しいタイルを生成。

        {

            canPlay = false;

        

            Invoke("SpawnTile", 0.2f); //タイルの移動に0.1fかけるので、それが終わった後で新しいタイルを生成。

 

        }

  

    }

 

 // 元居たマスを空っぽにする関数

  void EmptyMySquare(GameObject mySquare)

  {

      //元のマスのバリューを0に。

      mySquare.GetComponent<SquareScript>().tileValue = 0;

      //元いたマスのタイルをnullに。

      mySquare.GetComponent<SquareScript>().TileOnMe = null;

 

  }

 

  //タイルの移動。

  void TileMoveAnimation(GameObject temp, GameObject targetSquare)

  {

      

      RectTransform tempRect = temp.GetComponent<RectTransform>();

      tempRect.SetParent(targetSquare.transform); // Squareの子要素に設定

   

      // Dotween 

      Vector2 endPos = targetSquare.GetComponent<RectTransform>().position;

      

      tempRect.DOAnchorPos(endPos, 0.1f).SetEase(Ease.Linear).SetLink(temp);

      // dotween end

      

  }

 

これでとりあえず右スワイプするとタイルが移動するようになったと思います。

 

左スワイプの場合。右スワイプと違うところを抜粋

 

//squareArrayを左列から見ていき、タイルが入っているマスがあれば、それを左に移動する。これを端まで繰り返す。

for (int row = 0; row < 4; row++) // 上の行から

{

    for (int column = 0; column < 4; column++) //0列目から3列目まで

    {

        int myNumber;

        if (squareArray[row,column].GetComponent<SquareScript>().tileValue != 0) // 何かタイルがあれば。

        {

            GameObject temp = squareArray[row, column].GetComponent<SquareScript>().TileOnMe;

            myNumber =  squareArray[row, column].GetComponent<SquareScript>().tileValue;

            int x = column;

            while ( x > 0 && squareArray[row, x -1].GetComponent<SquareScript>().tileValue == 0) //自分より左が空である限り。左隣はx-1

            {

                canMove = true;

 

                x--; // マイナス。

            }

 

 

 

上スワイプ

 //squareArrayを上行から見ていき、タイルが入っているマスがあれば、それを上に移動する。これを端まで繰り返す。

 for (int column = 0; column < 4; column++) // 左の列から

 {

     for (int row = 0; row < 4; row++) //上から下へ順番に。(ちなみに上の行が0)

     {

          int myNumber;

         if (squareArray[row, column].GetComponent<SquareScript>().tileValue != 0) // 何かタイルがあれば。

         {

             GameObject temp = squareArray[row, column].GetComponent<SquareScript>().TileOnMe;

             myNumber =  squareArray[row, column].GetComponent<SquareScript>().tileValue;

             int y = row;

 

             //自分より上のマスが空である限り

             while (y > 0 && squareArray[y -1, column].GetComponent<SquareScript>().tileValue == 0) 

             {

                 canMove = true;

                

                 y--;

             }

 

下スワイプ

 //squareArrayを下行から見ていき、タイルが入っているマスがあれば、それを下に移動する。これを端まで繰り返す。

 for (int column = 0; column < 4; column++) // 左の列から

 {

     for (int row = 3; row >= 0; row--) //下から上へ順番に。(ちなみに上の行が0)

     {

 

         int myNumber;

         if (squareArray[row, column].GetComponent<SquareScript>().tileValue != 0) // 何かタイルがあれば。

         {

             // いったんタイルを保持して。

             GameObject temp = squareArray[row, column].GetComponent<SquareScript>().TileOnMe;

             myNumber =  squareArray[row, column].GetComponent<SquareScript>().tileValue;

             int y = row;

 

             //自分より下のマスが空である限り。y+1が自分より下の行。

             while (y < 3 && squareArray[y + 1, column].GetComponent<SquareScript>().tileValue == 0)

             {

                 canMove = true;

                 y++;

             }

 

 

以上で、タイルが動き、かつ移動後に新しいタイルが生成されるところまでできました。

 

x + 1, x - 1, y + 1, y -1, ++, --,などと頭がこんがらがるところです。

 


マージのプログラミング

さて、いよいよマージ(合体)です。

今書いたRightSwipe()などに追記していきますが、まず考え方を説明すると、

 

たとえば、2,2,2,4と並んでいて右スワイプしました、と。

そうすると、0,2,4,4になります。

 

たとえば、4,2,0,2と並んでいて右スワイプしました、と。

そうすると、0,0,4,4になります。

 

後者のパターンの場合で、まず2と2がマージして4になったところで、あとからきた4とマージして8になりません。(なるようにしてもいいけれど)

あとからきたタイルは連続コンボでマージできません。

 

これをどうするかというと、まずマス目オブジェクトに「いまのスワイプでマージしましたよ」というフラグを立てて、そのフラグが立っているマス目のタイルとはマージしないようにすればよいわけです。

 

というわけで、SquareScriptを開きます。

 

 public bool mergedThisSwipe = false; // 今回のスワイプでマージされたときtrue

 

//フラグをオフにする関数。

 public void ResetMergeState() // publicで。

 {

     mergedThisSwipe = false;

 }

 

 

 

 

さて、今度はGameManagerスクリプトです。

 

追記・変更したところはテキストの色を変えました。コメントに※をつけました。

 

    // Right Swipe

    void RightSwipe()

    {

        bool canMove = false;

        bool Merging = false; //※マージしたかどうかのフラグ

        

        //squareArrayを右列から見ていき、タイルが入っているマスがあれば、それを右に移動する。これを端まで繰り返す。

        for (int row = 0; row < 4; row++) // 上の行から

        {

            for (int column = 3; column >= 0; column--) //3列目から見て、0まで

            {

                int myNumber; //※移動するタイルの数字

                //GameObject nextTile = null;

 

                if (squareArray[row, column].GetComponent<SquareScript>().tileValue != 0) // 何かタイルがあれば。

                {

                    GameObject temp = squareArray[row, column].GetComponent<SquareScript>().TileOnMe;

                    myNumber = squareArray[row, column].GetComponent<SquareScript>().tileValue; //※数字を把握して

                    int x = column;

                    while (x < 3 && squareArray[row, x + 1].GetComponent<SquareScript>().tileValue == 0) //自分より右が空である限り。一番右は見なくてよい。

                    {

                        canMove = true;

                        x++;

                    }

                    //※ マージできるかどうか。隣が同じ数字、かつ今回のスワイプでマージされたタイルではない。

                    if (x<3 && squareArray[row, x+1].GetComponent<SquareScript>().tileValue ==myNumber

                        && !squareArray[row, x + 1].GetComponent<SquareScript>().mergedThisSwipe)

                    {

                        Merging = true; //※

                        canMove = true; //※

                        x++; //※

                    }

                    else if (!canMove) //※ 隣が空白マスでもなく、マージもできない。

                    {

                        continue;

                    }

                    // いったん置き換えて。

                    GameObject targetSquare = squareArray[row, x];

                    //もと居たマスから移動先のマスへバリューを代入。

                    targetSquare.GetComponent<SquareScript>().tileValue = myNumber;

 

                    //もと居たマスを空っぽに。

                    EmptyMySquare(squareArray[row, column]);

 

                    //Animation

                    TileMoveAnimation(temp, targetSquare, Merging); //※

                

                    if (Merging) //※アニメーションやってる間にこっちが動きますので内部的に必要なことを記述。

                    {

                        targetSquare.GetComponent<SquareScript>().mergedThisSwipe = true;

                        targetSquare.GetComponent<SquareScript>().tileValue = myNumber * 2;

                        Merging = false;

                    }

                    else //※

                    {

                       

                        targetSquare.GetComponent<SquareScript>().TileOnMe = temp;

                    }

 

                }

            }

        }

        if (canMove)// タイルが移動した場合は、新しいタイルを生成。

        {

            canPlay = false;

        

            Invoke("SpawnTile", 0.2f);

 

        }

        // マージ中フラグをリセット //※

        foreach (var square in squareArray)

        {

            square.GetComponent<SquareScript>().ResetMergeState();

        }

 

    }

 

 

 アニメーションの関数も変わります。

 

  void TileMoveAnimation(GameObject temp, GameObject targetSquare, bool Merging )//※bool追加

   {

       

       RectTransform tempRect = temp.GetComponent<RectTransform>();

       tempRect.SetParent(targetSquare.transform); // Squareの子要素に設定

       

       //

       GameObject nextTile = targetSquare.GetComponent<SquareScript>().TileOnMe;//※移動先にあったタイル

 

       // Dotween from here

       Vector2 endPos = targetSquare.GetComponent<RectTransform>().position;

 

       bool MergeAnime = false; //※マージする場合用のフラグ

       if (Merging)

       {

           MergeAnime = true;

       }

       // ※ 書き方がややこしいのでよく見てください。OnComplete(() => {}); となってます。

       tempRect.DOAnchorPos(endPos, 0.1f).SetEase(Ease.Linear).SetLink(temp).OnComplete(() =>

       {

           if (MergeAnime) // ※外見に関することを記述。

           {

               //

               int newValue = temp.GetComponent<Tile>().value;

               temp.GetComponent<Tile>().SetValue(newValue * 2);//タイルデータを更新。

             

               //隣にいたタイルはDestroy。

               Destroy(nextTile);

               //Destroyして空っぽになっているところにtempを入れる。

               targetSquare.GetComponent<SquareScript>().TileOnMe = temp;

               MergeAnime = false;

 

           }

           

       });

       // dotween end

       

   }

 

 

 

DOTweenのOnCompleteというのは、アニメーションが終わってから以降の処理をやります、という命令です。

アニメーションをちまちまやってる間にも、続々と関数が動いて計算は進んでいるんですね。 一つ目のマージのアニメをやってる間にも、隣のタイルの移動の計算が進んでいるので、計算に必要な情報は先に与えて、外見に関することをアニメの関数に書きます。

あとは、左、上、下スワイプです。

右スワイプと違うところを抜粋して載せます。+1、-1などに注意。

 

//左

//マージできるかどうか。

if (x > 0 && squareArray[row, x - 1].GetComponent<SquareScript>().tileValue == squareArray[row, column].GetComponent<SquareScript>().tileValue

     && !squareArray[row, x - 1].GetComponent<SquareScript>().mergedThisSwipe)//隣が同じ数字ならマージします。

{

    Merging = true;//マージするので、新しいタイルにします。

    canMove = true;

    x--;

}

else if (!canMove)

{

    continue;

}

 

 

//上

//マージできるかどうか。

if (y > 0 && squareArray[y - 1, column].GetComponent<SquareScript>().tileValue == squareArray[row, column].GetComponent<SquareScript>().tileValue

    && !squareArray[y - 1, column].GetComponent<SquareScript>().mergedThisSwipe)//上が同じ数字ならマージします。

{

    Merging = true;//マージするので、新しいタイルにします。

    canMove = true;

    y--;

}

else if (!canMove)

{

    continue;

}

 

//下

 //マージできるかどうか。

 if (y < 3 && squareArray[y + 1, column].GetComponent<SquareScript>().tileValue == squareArray[row, column].GetComponent<SquareScript>().tileValue

     && !squareArray[y + 1, column].GetComponent<SquareScript>().mergedThisSwipe)//隣が同じ数字ならマージします。

 {

     Merging = true;//マージするので、新しいタイルにします。

     canMove = true;

                 

     y++;

 }

 else if (!canMove)

 {

     continue;

 }

 

 

 

これで、ほぼゲームは完成です。テストプレイしてみてください。

 


ゲームオーバー判定

ゲームオーバーの判定をしていきます。

スワイプしてタイルが移動して、残りの1マスに新たなタイルが生成された。その時点で、同じ数字が隣り合うことが無く、まったく動かせない状態、これでゲームオーバーです。

 

ゲームオーバー関数を作って、タイルをスポーンするSpawnTile()関数で呼び出します。

 

GameManagerスクリプトを開きます。

 

    // GameOverCheck

    bool GameOverCheck() //boolを返します。

    {

        //1.空いているマスがあるか否か

        foreach (var item in squareArray)

        {

            if (item.GetComponent<SquareScript>().tileValue == 0)

            {

                return false;//1つでも空きマスがあれば続行可能

            }

        }

        //2.左右の隣同士を確認。同じ数字があればまだ続行

        for (int row = 0; row < 4; row++)

        {

            for (int column = 0; column < 3; column++) //左側から見ていくから、最後の列は不要。

            {

                int currentValue = squareArray[row, column].GetComponent<SquareScript>().tileValue;

                int nextValue = squareArray[row, column + 1].GetComponent<SquareScript>().tileValue;

 

                // 隣り合うタイルが同じ数字なら、ゲーム続行可能

                if (currentValue == nextValue)

                {

                    return false;

                }

            }

        }

        //3.上下の確認。

        for (int row = 0; row < 3; row++) //上の行から見ていくので、最下段のチェックは不要。

        {

            for (int column = 0; column < 4; column++)

            {

                int currentValue = squareArray[row, column].GetComponent<SquareScript>().tileValue;

                int nextValue = squareArray[row + 1, column].GetComponent<SquareScript>().tileValue;

 

                // 隣り合うタイルが同じ数字なら、ゲーム続行可能

                if (currentValue == nextValue)

                {

                    return false;

                }

            }

        }

        return true; //どれでもなければゲームオーバーです。

    }

 

これをSpawnTile()に入れます。

 

// SpawnTileの一番最後に入れます。それ以外は省略。

 

// GameOverCheck

if (GameOverCheck()) // true or falseが返ってくる。

{

    Debug.Log("gameover");

    canPlay = false;

   

}

else// ゲームオーバーでないならば。

{

    canPlay = true;

    

}

 

ゲームオーバーになったところでなにかメッセージが出るとか、そういう実装をするとよいと思います。きっとみなさんなら、できるでしょう。 

 


効果音を出す

このサイトのチュートリアルシリーズではじめて音を出したいと思います。

タイルがマージしたときに音を出します。

効果音を入手してください。

フリー素材とかたくさんありますし、もちろん自作した音でOKです。ファイル形式はMP3かwavか。そのあたりが使えます。

注意点は、その音声データの頭に空白があると音が出るのが遅くなるので、空白がない音を選ぶこと、あるいは波形をカットできるソフトで空白をカットしておくことです。

 

それではGameManagerスクリプトを開きます。

音を出すのはマージしたときなので、マージのアニメーションの関数TileMoveAnimationのif(MergeAnime)のところに追記します。

 

 

//音楽関係

[SerializeField] private AudioSource SEaudio; //Unityでアサインします。

[SerializeField] private AudioClip audioClip;

 

//TileMoveAnimation()の中の関連する箇所を抜粋。

 

tempRect.DOAnchorPos(endPos, 0.1f).SetEase(Ease.Linear).SetLink(temp).OnComplete(() =>

{

    if (MergeAnime)

    {

        //

        int newValue = temp.GetComponent<Tile>().value;

        temp.GetComponent<Tile>().SetValue(newValue * 2);//タイルデータを更新。

        // ※ Sound

        SEaudio.PlayOneShot(audioClip); // ※ 

     // 以下、省略。

 

 

 

保存したらUnityに戻り、

Hierarchy > GameManagerを選択。 

Inspectorにて、効果音データをAdd Component

(あるいはAdd Component > Audio > Audio Source)

 

GameManager(Script)のSEaudioに、自分につけたAudioSourceコンポーネントをドラッグしてアサイン

audioClipには効果音データをアサイン。

Inspectorにて音量とかを設定できます。

Play On Awakeのチェックと、Loopのチェックを外します。

 


スコア表示など

スコアを計算して表示します。

スコアは、マージしてできた数字を加算していきます。

 

Hierarchy > Canvas にて右クリック、UI > Text-TextMeshPro、名前はScoreText。

 

テキストの位置をご自身の都合の良いところにしてください。

Sceneビューにして、マウスで大まかなところまで動かしてから、GameビューにしてInspectorの数字をいじると早いような気がします。

 

文字サイズ、色、などなどお好みで。

 

※タイルにつけたTextMeshProとshaderが共通しているので、同じフォントを使う場合は同じような文字スタイルになります。

 

 

GameManagerスクリプトを開きます。

 

    [SerializeField] private TMP_Text ScoreText; // Unityでアサイン。

 

   //スコア関係

   private int score;

 

void Start()

{

  //省略

    

    score = 0;

    DisplayScore(score);

}

 

    void DisplayScore(int score)

    {

        ScoreText.text = score.ToString();

    }

 

スコアの計算は、マージアニメーションのところか、スワイプのところに書けばいいと思います。

アニメーションのところに書けばひとつで足りるので、TileMoveAnimationの中に書きます。

さっきの効果音のスクリプトの下に追記します。

 

//上は省略

 

//Sound

SEaudio.PlayOneShot(Pon);

//スコア関係

score += newValue * 2; // ※

DisplayScore(score); // ※

 

//下も省略

 

 

Unityに戻って、ScoreTextオブジェクトをアサイン。

 

ニューゲームボタンを作る。

 

ゲームオーバーになったらやり直すためのボタンを作ります。

 

Hierarchy > Canvas > UI > Button 名前はNewGameBtn

 

配置、テキストなどご自由に。好きな画像にすることもできます。

 

GameManagerスクリプトを開きます。

 

using UnityEngine.SceneManagement; // これを追加

 

 

 public void NewGame() //publicな関数。

 {

     SceneManager.LoadScene("Game"); // 名前はSceneの名前です。間違いのないように。

 }

 

Unityに戻って、

Hierarchy > NewGameBtn 

Inspector > Button > On Clickに、GameManagerオブジェクトをアタッチして、NewGame関数をアサイン。

 

 

これで完成です!

 

実機ビルドして遊んでみてください。

スワイプの閾値とか、タイルの移動スピードとか、あるいはDOTweenのアニメを変えてみたりなどなど遊んでみてください。

 

 

 それでは!

Feel free to Share and Comment!

 

 

 

 

Profile

string name = "Renoboy";

string message = "《Unity,C#》に詳しくない私による、詳しくない人のためのチュートリアルです。詳しくないので、難しいことは決して教えません。";

 

Appale-Takemuroid