一文教你Unity零基礎製做2048!

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!前端

摘要:玩遊戲也能學習知識?還記得高中時的化學元素常見金屬活動性屬性表嗎?一塊兒來看看化學元素和遊戲之間發生的碰撞吧~「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」算法

前言

玩遊戲也能學習知識?還記得高中時的化學元素常見金屬活動性屬性表嗎?一塊兒來複習一下:鉀K,鈣Ca,鈉Na,鎂Mg,鋁Al,鋅Zn,鐵Fe, 錫Ni,鉛Sn,氫(H),銅Cu,汞Hg,銀Ag,鉑Pt,金Au。 一股很熟悉的味道有沒有?一塊兒來看看化學元素和遊戲之間發生的碰撞吧~後端

一,遊戲介紹和效果展現

2048 一款益智小遊戲,遊戲的規則十分簡單,簡單易上手的數字小遊戲,閒來無事,本身製做一個,卻怎麼也到沒有成功到2048。數組

老規矩,先看下按照此博文一步步操做完成的效果吧~markdown


二,開發前的準備工做

2.1 建立工程

打開Unity Hub 點擊「新建」,在彈窗中輸入 --> "項目名稱" --> 選擇"項目位置" --> "項目版本控制系統" 選不選都行,不須要在雲端備份的話就不要選(我通常不選) ,選了沒用過的話,會自動幫你安裝PlasticSCM到本地。最後點擊建立便可。dom

2.2 導入素材

將提早準備好的素材(圖片和聲音)導入工程。(文末會提供下載地址)oop

  • 方式一: 在"Project"面板右鍵 --> "Improt Package" --> "Custom Package..." --> 選擇本身下載下來的UnityPackage --> 最後點擊"Import" 導入。

  • 方式二: 直接將下載還的UnityPackage,拖拽到項目中,而後點擊"Import" 導入:

導入項目後工程目錄以下: post


三,遊戲開發進行中

3.1 遊戲場景搭建

  • 設置分辨率:點擊Game視圖分辨率選框,設置爲(1080x1920),沒有的話點擊加號自行添加一個,或者使用一個豎屏的就能夠。學習

  • 建立背景:右鍵UI --> Image 建立後,重命名爲BG,將其錨點設置爲鋪滿,源文件指定爲素材"play_bg_forest_dark":測試

  • 修改Canvas:將自動建立出來的Canvas的Canvas Scaler 屬性設置以下:

  • 建立格子: 在「Canvas」下建立Image,重命名爲「MapBg」,將其寬高設置爲(1720,1720),而後修改其顏色爲淺藍色,透明度相應調低,效果以下:(本身以爲好看就能夠了)

而後在「MapBg」下再次建立一個Image,重命名爲「gezi」,將其寬高設置爲(400,388),調整其顏色爲(22,0,192,30),仍是同樣調整到本身喜歡的顏色便可。而後「Ctrl + D」 複製15個格子出來: 最後在「MapBg」上添加"Grid Layout Group",屬性設置以下圖,將格子鋪滿到地圖背景上:

  • 建立數字池: 右鍵建立一個空物體做爲加載數字的父物體,並命名爲「NumPool」,全部屬性默認便可。

  • 建立數字預製體: 右鍵Image重命名爲Num,寬高調整爲和格子同樣大(400,380),而後將其拽到Resources文件夾下,做爲預製體,而後右鍵刪除場景中Num便可:

  • 遊戲結束面板:建立Image命名爲「UIFinsh」,鋪滿背景。在「UIFinsh」下在建立一個Text和一個Button,做爲遊戲結束顯示文本和從新開始按鈕:

3.2 核心代碼編寫

場景終於搭建完了,下面開始編寫腳本吧,完整代碼在四中給出,這裏只講解實現思路,查看核心邏輯。

  • 在Project下建立Scripts文件夾,而後建立"Manager.cs" 和 "Number.cs" 腳本。

  • 基礎邏輯就是:默認生成兩個數字,接收用戶輸入,管理器觸發移動邏輯,數字移動並校驗合併,移動後校驗是否遊戲結束,若結束處理遊戲結束摞,若未結束在自動生成一個數字:

graph TD
默認生成數字 --> 管理器接收用戶輸入
--> 管理器觸發數字移動邏輯 --> 數字移動並校驗合併
--> 移動後校驗遊戲結束 --結束--> 處理遊戲結束邏輯
移動後校驗遊戲結束 --繼續--> 默認生成數字
  • Manager中接收用戶輸入的方式一種是在移動端的滑動,一種是在PC端的按下剪頭或WASD上下左右移動:

  • Manager收到用戶輸入後的控制遊戲數字移動邏輯:

  • Num類收到Manager的移動,合併邏輯處理:

  • 最後將Manager掛載到「BG」,並對公有變量'PoolManager'和'UIFinsh'賦值,以下圖便可:

  • 將Number腳本掛載到上面製做的預製體「Num」上便可。

此時全部的遊戲邏輯就都完成,運行遊戲就能夠玩耍了~


3.3 遊戲音效處理

音效是這一個遊戲的很重要的部分,一個好的音效可讓用戶獲得更好的反饋,因此再簡單的遊戲也要有背景音和音效給用戶一個好的交互體驗。

  • 背景音樂:在「BG」上,添加組件「Audio Source」,將音頻文件「bg_1」賦值給AudioClip並勾選Loop,便可完成自動循環播放背景音樂:

  • 數字音效:同理在「Num」預製體上添加組件「Audio Source」,將音頻文件「num」賦值給AudioClip,其餘屬性默認便可。


四,遊戲完成源碼分享

4.1 Manager遊戲管理類

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

public class Manager : MonoBehaviour
{
    public static Manager _isnstance;  //單例模式的引用

    public Transform poolManager;   //生成數字的池子
    private GameObject numPrefab;   //數字的預製體
    public Number[,] numbers = new Number[4, 4]; //保存方格中的數組
    //正在移動中的Num
    public List<Number> isMovingNum = new List<Number>();  
    public bool hasMove = false;   //是否有數字發生了移動
 
    public GameObject UIFinsh;  //遊戲結束頁面

    void Awake()
    {
        _isnstance = this;
    }
    
    void Start()
    {
        numPrefab = Resources.Load<GameObject>("Num");
        // 開始遊戲
        ReStartBtn();
        // 遊戲結束面板按鈕監聽,從新開始
        UIFinsh.GetComponentInChildren<Button>().onClick.AddListener(ReStartBtn);
    }

    // 從新開始
    void ReStartBtn()
    {
        isMovingNum.Clear();
        numbers = new Number[4, 4];
        for (int i = poolManager.childCount - 1; i >= 0; i--)
        {
            Destroy(poolManager.GetChild(i).gameObject);
        }
        hasMove = false;
        //遊戲開始生成兩個數字
        CreateNun();
        CreateNun();
        UIFinsh.SetActive(false);
    }

    void CreateNun()
    {
        GameObject go = Instantiate(numPrefab);
        go.transform.parent = poolManager;
        go.transform.localScale = Vector3.one;
    }

    #region 檢測鍵盤和觸摸輸入
    void Update()
    {
        //--------- 移動端檢測邏輯 ---------
        //有觸摸點,且滑動
        if (isMovingNum.Count == 0)
        {
            if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved)
            {
                int dieX = 0;
                int dieY = 0;
                //獲取滑動的距離
                Vector2 touchDelPos = Input.GetTouch(0).deltaPosition;
                if (Mathf.Abs(touchDelPos.x) > Mathf.Abs(touchDelPos.y))
                {
                    //滑動距離
                    if (touchDelPos.x > 10)
                    {
                        dieX = 1;
                    }
                    else
                    if (touchDelPos.x < -10)
                    {
                        dieX = -1;
                    }
                }
                else
                {
                    if (touchDelPos.y > 10)
                    {
                        dieY = 1;
                    }
                    else if (touchDelPos.y < -10)
                    {
                        dieY = -1;
                    }
                }
                MoveNum(dieX, dieY);
            }
        }
        
        //--------- PC端檢測邏輯 ---------
        if (isMovingNum.Count == 0)
        {
            int dieX = 0;
            int dieY = 0;
            if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow))
            {
                dieX = -1;
            }
            else
            if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow))
            {
                dieX = 1;
            }
            else
            if (Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow))
            {
                dieY = 1;
            }
            else
            if (Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow))
            {
                dieY = -1;
            }
            MoveNum(dieX, dieY);
        }

        if (hasMove && isMovingNum.Count == 0)   //生成新的數字
        {
            CreateNun();
            hasMove = false;

            for (int i = 0; i < 4; i++)
            {
                for (int j = 0; j < 4; j++)
                {
                    if (numbers[i, j] != null)
                    {
                        numbers[i, j].OneMove = false;
                    }
                }
            }
        }
    }
    #endregion

    #region 遊戲邏輯
    /// <summary>
    /// 數字移動方法
    /// </summary>
    /// <param name="directionX"></param>
    /// <param name="directionY"></param>
    public void MoveNum(int directionX, int directionY)
    {
        if (directionX == 1)  //向右移動 
        {
            //首先將空格填滿 最右側列不需作判斷
            for (int j = 0; j < 4; j++)
            {
                for (int i = 2; i >= 0; i--)
                {
                    if (numbers[i, j] != null)  //格子中有物體(數字),,調用移動方法
                    {
                        numbers[i, j].Move(directionX, directionY);
                    }
                }
            }
        }
        else

        //===========向左移動==================
        if (directionX == -1)
        {
            for (int j = 0; j < 4; j++)
            {
                for (int i = 1; i < 4; i++)
                {   //最左側的一列 [0,0] [0,1] [0,2] [0,3]
                    if (numbers[i, j] != null)
                    {
                        numbers[i, j].Move(directionX, directionY);
                    }
                }
            }
        }
        else

        //===========向上移動==================
        if (directionY == 1)
        {
            for (int i = 0; i < 4; i++)
            {
                for (int j = 2; j >= 0; j--)
                {
                    if (numbers[i, j] != null)
                    {
                        numbers[i, j].Move(directionX, directionY);
                    }
                }
            }
        }
        else

        //===========向下移動==================
        if (directionY == -1)
        {
            for (int i = 3; i >= 0; i--)
            {
                for (int j = 0; j < 4; j++)
                {
                    if (numbers[i, j] != null)  //有物體(數字)就移動
                    {
                        numbers[i, j].Move(directionX, directionY);
                    }
                }
            }
        }
    }


    /// <summary>
    /// 判斷是不是空格的方法
    /// </summary>
    /// <param name="x">數組索引X</param>
    /// <param name="y">數組索引Y</param>
    /// <returns></returns>
    public bool isEmpty(int x, int y)
    {
        if (x < 0 || x > 3 || y < 0 || y > 3)
        {
            return false;
        }
        else if (numbers[x, y] != null)
        {
            return false;
        }
        return true;
    }

    /// <summary>
    /// 判斷遊戲是否結束
    /// </summary>
    /// <returns>返回true則遊戲結束</returns>
    public bool isDead()
    {
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                if (numbers[i, j] == null)
                {
                    return false;
                }
            }
        }

        for (int j = 0; j < 4; j++)
        {
            for (int i = 0; i < 3; i++)
            {
                if (numbers[i, j].value == numbers[i + 1, j].value)
                {
                    return false;
                }
            }
        }

        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                if (numbers[i, j].value == numbers[i, j + 1].value)
                {
                    return false;
                }
            }
        }
        return true;
    }

    #endregion
    
    /// <summary>
    /// 遊戲結束
    /// </summary>
    /// <param name="isSuccess">false:輸,true:贏</param>
    public void ShowUIFinsh(bool isSuccess)
    {
        UIFinsh.SetActive(true);
        if (isSuccess)
        {
            UIFinsh.GetComponentInChildren<Text>().text = "遊戲成功";
        }
        else
        {
            UIFinsh.GetComponentInChildren<Text>().text = "遊戲失敗";
        }
    }
}
複製代碼

4.2 Number數字處理類

using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UI;
using Vector3 = UnityEngine.Vector3;

public class Number : MonoBehaviour
{
    //在二維數組中的位置X,Y
    public int posX; 
    public int posY;

    private int offsetX = -620;      //顯示偏移,Y,,,
    private int offsetY = -620;
    private int space = 420;         // 間距

    private bool isMoving = false;   //動畫是否播放過的計數
    public int value;                //產生數字是幾
    private bool toDestroy;          //判斷數字是否銷燬 
    public bool OneMove = false;     //標識數字是否合併過一次
 
    // Use this for initialization
    void Start()
    {
        // 80%成2的機率,更改自己的Sprite名字,以更換圖片
        value = Random.value > 0.2f ? 2 : 4;
        this.GetComponent<Image>().sprite = LoadSprite();
        do
        {
            posX = Random.Range(0, 4);
            posY = Random.Range(0, 4);
        } while (Manager._isnstance.numbers[posX, posY] != null);

        transform.localPosition = GetLocalPos();
        // 存放數字自己到數組中,表示此位置有數字不能生成新的數字
        Manager._isnstance.numbers[posX, posY] = this;
        if (Manager._isnstance.isDead())
        {
            // 遊戲失敗
            Manager._isnstance.ShowUIFinsh(false);
        }
    }

    // Update is called once per frame
    void Update()
    {
        //播放一次動畫
        if (!isMoving)
        {
            if (transform.localPosition != GetLocalPos())
            {
                isMoving = true;
                StartCoroutine(MoveAni());
            }
        }
    }

    // 移動動畫
    IEnumerator MoveAni()
    {
        Debug.Log("移動動畫...");
        float t = 0;
        for (int i = 0; i < 10; i++)
        {
            transform.localPosition = Vector3.Lerp(transform.localPosition, GetLocalPos(), t);
            t += 0.1f;
            yield return new WaitForEndOfFrame();
        }
        // 移動結束的回調
        MoveOver();
    }


    #region 遊戲核心移動算法
    /// <summary>
    /// 核心,移動方法(有空格,有物體是否同樣)
    /// </summary>
    public void Move(int directionX, int directionY)
    {
        //Debug.Log("測試");
        
        //==========向右移動================== 
        if (directionX == 1) 
        {
            int index = 1;  // 空格標誌
            while (Manager._isnstance.isEmpty(posX+index,posY))
            {
                index++;
            }
            // 有空格的移動
            if (index>1)
            {
                
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {   // 保證不會重複添加物體(數字)到列表,
                    Manager._isnstance.isMovingNum.Add(this);
                }
                //移動一次,就生成兩個數字的標誌符
                Manager._isnstance.hasMove = true;
                //向空格位置移動
                Manager._isnstance.numbers[posX, posY] = null;
                posX = posX + index - 1;
                Manager._isnstance.numbers[posX, posY] = this;
            }
            //有相同數字的移動
            if (posX < 3 && value == Manager._isnstance.numbers[posX+1,posY].value &&
                !Manager._isnstance.numbers[posX+1,posY].OneMove)
            {
                // 只合並一次的標誌
                Manager._isnstance.numbers[posX + 1, posY].OneMove = true;
                // 移動的標誌,(生成新的物體(數字))
                Manager._isnstance.hasMove = true;
                // 動畫播放的限定(有數字在列表中就不會重複播放第二次動畫) 
                // 不會重複添加物體(數字)到列表,
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }
                // 碰到同樣的數字,講位置設爲空 並銷燬自己標識(true),
                // 再將其位置上的值變爲2倍,(更換成新的數字)
                toDestroy = true;
                Manager._isnstance.numbers[posX, posY] = null;
                Manager._isnstance.numbers[posX + 1, posY].value *= 2;
                posX += 1;
            }
        }else

        //===========向左移動==================
        if (directionX == -1)
        {
            int index = 1;
            while (Manager._isnstance.isEmpty(posX - index, posY))
            {
                index++;
            }
            //有空格的移動
            if (index > 1)
            {
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                Manager._isnstance.numbers[posX, posY] = null;
                posX = posX - index + 1;
                Manager._isnstance.numbers[posX, posY] = this;
            }

            //碰到相同數字的移動
            if (posX > 0 && value == Manager._isnstance.numbers[posX - 1, posY].value && 
                !Manager._isnstance.numbers[posX - 1, posY].OneMove)
            {
                Manager._isnstance.numbers[posX - 1, posY].OneMove = true;
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                toDestroy = true;
                Manager._isnstance.numbers[posX, posY] = null;
                Manager._isnstance.numbers[posX - 1, posY].value *= 2;
                posX -= 1;
            }

        }else

        //===========向上移動==================
        if (directionY == 1)
        {
            int index = 1;   //空格標誌
            while (Manager._isnstance.isEmpty(posX , posY + index))
            {
                index++;
            }
            //有空格的移動
            if (index > 1)
            {
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                Manager._isnstance.numbers[posX, posY] = null;
                posY = posY + index - 1;
                Manager._isnstance.numbers[posX, posY] = this;
            }
            //有相同位置的移動
            if (posY < 3 && value == Manager._isnstance.numbers[posX , posY + 1].value && !Manager._isnstance.numbers[posX, posY + 1].OneMove)
            {
                Manager._isnstance.numbers[posX , posY + 1].OneMove = true;
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                toDestroy = true;
                Manager._isnstance.numbers[posX, posY] = null;
                Manager._isnstance.numbers[posX , posY + 1].value *= 2;
                posY += 1;
            }

        }else

        //===========向下移動==================
        if (directionY == -1)
        {
            int index = 1;  //空格標誌位
            while (Manager._isnstance.isEmpty(posX, posY - index))
            {
                index++;
            }
            //有空格的移動
            if (index > 1)
            {
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                Manager._isnstance.numbers[posX, posY] = null;
                posY = posY - index + 1;
                Manager._isnstance.numbers[posX, posY] = this;
            }
            //有相同數字的移動
            if (posY > 0 && value == Manager._isnstance.numbers[posX, posY - 1].value && !Manager._isnstance.numbers[posX, posY - 1].OneMove)
            {
                Manager._isnstance.numbers[posX, posY -1].OneMove = true;
                Manager._isnstance.hasMove = true;
                if (!Manager._isnstance.isMovingNum.Contains(this))
                {
                    Manager._isnstance.isMovingNum.Add(this);
                }

                toDestroy = true;
                Manager._isnstance.numbers[posX, posY] = null;
                Manager._isnstance.numbers[posX, posY - 1].value *= 2;
                posY -= 1;
                }
        }
    }

#endregion

    /// <summary>
    /// 動畫結束,標誌改成false
    /// </summary>
    public void MoveOver()
    {
        isMoving = false;
        //若碰到了相同的數字 銷燬本身,和改變另外一個圖片(數字)
        if (toDestroy)   
        {
            Destroy(this.gameObject);
            value = Manager._isnstance.numbers[posX, posY].value;
            Manager._isnstance.numbers[posX, posY].GetComponent<Image>().sprite = LoadSprite();
            //遊戲成功
            if (value == 4096)
            {
                Manager._isnstance.ShowUIFinsh(true);
            }
        }
        Manager._isnstance.isMovingNum.Remove(this);
    }

    Vector3 GetLocalPos()
    {
       return new Vector3(offsetX + posX * space, offsetY + posY * space, 0);
    }

    /// <summary>
    /// 根據數字加載對應圖片
    /// </summary>
    /// <returns></returns>
    Sprite LoadSprite()
    {
        return Resources.Load<Sprite>(value.ToString());
    }
}
複製代碼

結語

本文重新建項目開始一步一步帶你完成全部步驟,還在等什麼呢?三連支持一下吧~

須要素材或者源碼的小夥伴能夠評論區留言哦~

相關文章
相關標籤/搜索