unity3d俄羅斯方塊源碼教程+源碼和程序下載

小時候,你們都應玩過或據說過《俄羅斯方塊》,它是紅白機,掌機等一些電子設備中最多見的一款遊戲。而隨着時代的發展,信息的進步,遊戲畫面從簡單的黑白方塊到彩色方塊,遊戲的玩法機制從最簡單的消方塊到如今的多人pk等,無一不是在體現它的火爆。在這裏,經過這篇文章向你們分享一下本身在製做俄羅斯方塊的經驗和心得,以及文章最後的源碼和pc程序。windows

首先,看標題都知道這篇文章中所用到的遊戲引擎是:unity3d,版本不限,但最好是5.4.3以上的,緣由是由於做者本身沒有用過5.4.3如下的版本。數組

準備工具都有:unity3d + Visual Studio 2015app

素材準備有(密碼:m6gz):字體方正粗圓_GBK,以及一些圖集框架

項目分析:dom

當一切準備就緒後,就能夠開始建立咱們的俄羅斯方塊工程(2d)的。編輯器

 

1、遊戲框架的搭建和方塊預設物的製做ide

1.雙擊unity快捷方式,打開unity界面點擊New新建工程,Template類型選擇2d,工程名 Tetris,點擊建立後稍等片刻進入編輯器界面工具

2.在Assets文件夾下建立幾個經常使用文件夾用來歸類:佈局

建立文件夾方式,在Project面板下右鍵測試

Scenes(場景文件夾),Resources(資源文件夾),Script(腳本文件夾),Texture(圖集文件夾),Prefabs(預設物文件夾)

從圖中能夠看到將預設物文件夾放到了資源文件夾下,這樣作的好處是能夠在代碼中直接經過unity3d提供的Resources類進行訪問。固然其餘文件或文件夾也能夠放進去,可是瞭解過Unity3d的你,應該知道Resource下的文件在打包的時候會佔用很大資源,具體的提自行百度。

3.導入圖片等資源到對應的文件夾中(也能夠自行建立);保存當前場景,場景名MainGame;切割圖片資源生成咱們想要的圖片集。

4.在層級面板中右鍵建立UI面板Canvas,有關UI的對象都放置到UI面板中。設置相機背景顏色設置爲純白色,Canvas屬性UIScaleMode設置爲屏幕分辨率

界面搭建過程當中在Canvas下可建立空物體用來作對應界面的父對象,也能夠直接建立Panel。這裏採用了建立空物體

4-1.開始界面的搭建:

建立空物體F2修更名字爲StartPanel,設置StartPanel大小,點擊stretch,按住Alt鍵,以後點擊右下角,StartPanel的大小和位置就會被拉伸到Canvas尺寸

在StartPanel下建立UI→Text,將其錨點固定到上方中心位置也放到上方,文本信息設置爲:俄羅斯方塊

建立一個空物體重命名:ButtonGroup,用來整合開始界面中的按鈕,將空物體放置到界面下方,在ButtonGroup下建立3個UI→Button,而後刪除Button下的Text子物體,添加子物體Image,並分別重命名:btn_Start,btn_Set,btn_Rank。在ButtonGroup上添加布局組件能夠將子物體進行一些佈局調整

(作好一個按鈕後能夠將其拖拽成預設物,方便下次使用)

在預設物文件夾下建立三個文件夾用來分類預設物:Panel,Square,Other,將作好的StartPanel拖放到Panel文件夾下,btn_Start移動到Other文件夾下。

4-2. 遊戲運行界面搭建:建立四個Text其中兩個用來文本提示,另外兩個用來顯示分數。將以前作好的按鈕預設物拽上來,修更名字用來作暫停按鈕,最後將作好的界面放到Panel文件夾中。

4-3.設置界面排行界面暫停界面以及遊戲結束界面,可分別搭建成以下圖所示的樣子。

4-4:界面預設物打包下載:https://pan.baidu.com/s/1XP8LkIVQEZfWYreHnKrYdg 提取碼:ad1o 

5.製做方塊預設物:玩過俄羅斯方塊的都知道,俄羅斯方塊下落的方塊類型有7種:

這7種方塊類型,我提供了下載包,可直接下載使用:https://pan.baidu.com/s/1d-40cuiD_ioH69Pc_Hv9ng 提取碼:8tj7 

6.製做方塊背景地圖Map:Map最直接最簡單的做用是爲了顯示方塊可下落的位置,爲此咱們在作Map時,第一塊物體的位置很重要,在這裏,咱們將其位置重置爲0,而後Ctrl+D複製建立,而後選中複製出來的按住鍵盤Ctrl鍵進行向右拖拽。最後作到一行有10個方塊,以後建立一個空物體將其座標重置,將這一行方塊總體放倒空物體後重命名空物體:Row,重複Ctrl+D複製建立而且按住鍵盤Ctrl鍵進行向上拖拽,作到場景中有12個Row後建立一個空物體將其座標重置重命名爲Map,將12行Row放到Map下面,最終生成10*12一塊Map,將作好的Map拖拽到Other文件夾下生成預設物。提取碼:q0o8

注意:在製做Map時,能夠將第一塊方塊拖拽成預設物,這樣子能夠方便修改Map中全部方塊的屬性。

至此,俄羅斯方塊界面框架和預設物算是完成了。

2、遊戲代碼邏輯編寫

在unity中提供兩種常見的代碼方式:C#和JavaScript,在這裏採用C#去編寫。遊戲代碼的編寫風格以及框架有不少種,能夠根據本身的習慣去書寫本身滿意的代碼風格,但爲了他人在閱讀瀏覽本身代碼是有可能會形成的一些問題,咱們應該規範本身的代碼風格和框架。在本教程中採用的是簡單的MVC框架。

在Script文件夾下建立三個文件夾:Ctrl,View,Data,分別用來存放控制腳本,視圖腳本和數據腳本。

在View文件夾下分別建立對應UIPanel的C#腳本:StartView,RunView,SetView,RankView,PasueView,OverView。

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

public class StartView : MonoBehaviour {

    public Button Btn_Start { get; set; }
    public Button Btn_ReStart { get; set; }
    public Button Btn_Set { get; set; }
    public Button Btn_Rank { get; set; }
    public Text Txt_Title { get; set; }

    // Use this for initialization
    void Awake() {
        Btn_Start = transform.Find("ButtonGroup/btn_Start").GetComponent<Button>();
        Btn_Set = transform.Find("ButtonGroup/btn_Set").GetComponent<Button>();
        Btn_Rank = transform.Find("ButtonGroup/btn_Rank").GetComponent<Button>();
        Txt_Title = transform.Find("txt_Title").GetComponent<Text>();
    }
    
    // Update is called once per frame
    void Update () {        
    }
}
StartView腳本
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class RunView : MonoBehaviour {

    public Button Btn_Pause { get; set; }
    public Text Txt_Curr { get; set; }
    public Text Txt_Max { get; set; }
    // Use this for initialization
    void Awake() {
        Btn_Pause = transform.Find("TopGroup/btn_Pasue").GetComponent<Button>();
        Txt_Curr = transform.Find("TopGroup/txt_Current/Text").GetComponent<Text>();
        Txt_Max = transform.Find("TopGroup/txt_Max/Text").GetComponent<Text>();
        
    }

    public void SetScoreText(int curr,int max) {
        Txt_Curr.text = curr.ToString();
        Txt_Max.text = max.ToString();
    }
    public void SetScoreText(string curr, string max)
    {
        Txt_Curr.text = curr;
        Txt_Max.text = max;
    }
}
RunView腳本
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class SetView : MonoBehaviour {
    public Button Btn_Forum { get; set; }
    public Button Btn_WebSite { get; set; }
    public Button Btn_Collect { get; set; }
    public Button Btn_Sound { get; set; }
    public Button Btn_Return { get; set; }

    public GameObject mask { get; set; }
    // Use this for initialization
    void Awake() {
        Btn_Forum = transform.Find("Img_Bg/Btn_Forum").GetComponent<Button>();
        Btn_WebSite = transform.Find("Img_Bg/Btn_WebSite").GetComponent<Button>();
        Btn_Collect = transform.Find("Img_Bg/Btn_Collect").GetComponent<Button>();
        Btn_Sound = transform.Find("Img_Bg/Btn_Sound").GetComponent<Button>();
        mask = Btn_Sound.transform.Find("Mask").gameObject;
        Btn_Return = GetComponent<Button>();
    }
    public void SetSoundMask(bool isActive)
    {
        mask.SetActive(isActive);
    }
    public void SetSoundMask() {
        mask.SetActive(!mask.activeSelf);
        GameManager.instance.Sound = mask.activeSelf;
        AudioManager.instance.SetAudioSource(!mask.activeSelf);
    }
}
SetView腳本
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class RankView : MonoBehaviour {
    public Button Btn_Clear { get; set; }
    public Button Btn_Return { get; set; }

    public Text Txt_Curr { get; set; }
    public Text Txt_Max { get; set; }
    public Text Txt_Count { get; set; }

    // Use this for initialization
    void Awake() {
        Btn_Clear = transform.Find("Img_Bg/Btn_Clear").GetComponent<Button>();
        Txt_Curr = transform.Find("Img_Bg/Txt_Name").GetComponent<Text>();
        Txt_Max = transform.Find("Img_Bg/Txt_Max/Text").GetComponent<Text>();
        Txt_Count = transform.Find("Img_Bg/Txt_Count/Text").GetComponent<Text>();
        Btn_Return = GetComponent<Button>();
    }
    void Start()
    {
        SetScoreText();
    }
    public void TextClearData() {
        Txt_Max.text = 0.ToString();
        Txt_Count.text = 0.ToString();
        GameManager.instance.Score = 0;
        GameManager.instance.highScore = 0;
        GameManager.instance.numbersGame = 0;
    }

    public void SetScoreText() {
        Txt_Max.text = GameManager.instance.highScore.ToString();
        Txt_Count.text = GameManager.instance.numbersGame.ToString();
    }
    
}
RankView腳本
using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class PasueView : MonoBehaviour {

    public Button Btn_Home { get; set; }
    public Button Btn_Start { get; set; }
    public Text Txt_Curr { get; set; }
    // Use this for initialization
    void Awake()
    {
        Btn_Home = transform.Find("Img_Bg/Btn_Home").GetComponent<Button>();
        Btn_Start = transform.Find("Img_Bg/Btn_Start").GetComponent<Button>();
        Txt_Curr = transform.Find("Img_Bg/Txt_Count").GetComponent<Text>();
    }

}
PasueView腳本
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class OverView : MonoBehaviour {
    public Button Btn_Home { get; set; }
    public Button Btn_ReStart { get; set; }
    public Text Txt_Curr { get; set; }
    // Use this for initialization
    void Awake () {
        Btn_Home = transform.Find("Img_Bg/Btn_Home").GetComponent<Button>();
        Btn_ReStart = transform.Find("Img_Bg/Btn_ReStart").GetComponent<Button>();
        Txt_Curr = transform.Find("Img_Bg/Txt_Count").GetComponent<Text>();
    }

    public void SetScoreText(int score) {
        Txt_Curr.text = score.ToString();
    }
}
OverView腳本

在Ctrl文件夾下分別建立對應UIPanel的C#腳本:StartCtrl,RunCtrl,SetCtrl,RankCtrl,PasueCtrl,OverCtrl。

using UnityEngine;
using System.Collections;

public class StartCtrl : MonoBehaviour {
    public StartView view { get; set; }
    Camera mainCamera { get; set; }
    void Awake()
    {
        mainCamera = Camera.main;
    }

    void Start () {
        view = gameObject.AddComponent<StartView>();
        //開始按鈕事件
        view.Btn_Start.onClick.AddListener(delegate() {
            Destroy(gameObject);
            GameManager.instance.CreatePanel(PanelType.RunPanel);
            AudioManager.instance.PlayCursor();
        });
        //設置按鈕事件
        view.Btn_Set.onClick.AddListener(delegate () {            
            GameManager.instance.CreatePanel(PanelType.SetPanel);
            AudioManager.instance.PlayCursor();
        });
        //排行榜按鈕事件
        view.Btn_Rank.onClick.AddListener(delegate () {
            GameManager.instance.CreatePanel(PanelType.RankPanel);
            AudioManager.instance.PlayCursor();
        });
        StartCoroutine(doZoomOut());
    }
    //縮小
    IEnumerator doZoomOut()
    {
        bool isdo = false;
        yield return null;
        while (!isdo)
        {
            if (mainCamera.orthographicSize > 12.5f)
            {
                isdo = true;
            }
            mainCamera.orthographicSize += 0.1f;
            yield return new WaitForSeconds(0.02f);
        }
    }
}
StartCtrl腳本
using UnityEngine;
using System.Collections;

public class RunCtrl : MonoBehaviour {
    public bool isPause { get; set; }

    public RunView view { get; set; }
    Camera mainCamera { get; set; }

    private void Awake()
    {
        view = gameObject.AddComponent<RunView>();
        GameManager.instance.isOver = false;
        isPause = false;
        mainCamera = GameObject.Find("Main Camera").GetComponent<Camera>();
    }
    // Use this for initialization
    void Start () {
        view.SetScoreText(0, GameManager.instance.highScore);

        StartCoroutine(doZoomIn());


        view.Btn_Pause.onClick.AddListener(delegate () {
            isPause = true;
            GameManager.instance.currentShape.isPause = true;
            GameManager.instance.CreatePanel(PanelType.PasuePanel);
            AudioManager.instance.PlayCursor();
        });
        
    }

    /// <summary>
    /// 放大
    /// </summary>
    /// <returns></returns>
    IEnumerator doZoomIn()
    {
        bool isdo = false;
        yield return null;
        while (!isdo)
        {
            if (Camera.main.orthographicSize < 10f)
            {
                isdo = true;
            }
            Camera.main.orthographicSize -= 0.1f;
            yield return new WaitForSeconds(0.02f);
        }
    }

    void Update()
    {
        if (isPause) return;
        while (GameManager.instance.currentShape == null)
        {
            SquareType st = (SquareType)Random.Range(0, 7);
            //測試生成
            GameManager.instance.CreateSquare(st);
        }
    }

}
RunCtrl腳本
using UnityEngine;
using System.Collections;

public class SetCtrl : MonoBehaviour {
    public SetView view { get; set; }
    // Use this for initialization
    void Start () {    
        view = gameObject.AddComponent<SetView>();

        view.SetSoundMask(GameManager.instance.Sound);

        //聲音按鈕事件
        view.Btn_Sound.onClick.AddListener(delegate () {
            view.SetSoundMask();
            DataModel.SaveData();
            AudioManager.instance.PlayControl();
        });

        view.Btn_Collect.onClick.AddListener(delegate () {
        });
        view.Btn_Forum.onClick.AddListener(delegate () {
        });
        view.Btn_WebSite.onClick.AddListener(delegate () {
        });


        //返回按鈕事件
        view.Btn_Return.onClick.AddListener(delegate () {
            Destroy(gameObject);
        });
    }

    // Update is called once per frame
    void Update () {
    
    }
}
SetCtrl腳本
using UnityEngine;
using System.Collections;

public class RankCtrl : MonoBehaviour {
    public RankView view { get; set; }
    // Use this for initialization
    void Start () {
        view = gameObject.AddComponent<RankView>();

        view.Btn_Clear.onClick.AddListener(delegate () {
            view.TextClearData();
            DataModel.SaveData();
        });

        //返回按鈕事件
        view.Btn_Return.onClick.AddListener(delegate () {
            Destroy(gameObject);
        });
    }

    // Update is called once per frame
    void Update () {
    
    }
}
RankCtrl腳本
using UnityEngine;
using System.Collections;

public class PasueCtrl : MonoBehaviour {
    public PasueView view { get; set; }

    // Use this for initialization
    void Start () {
        view = gameObject.AddComponent<PasueView>();
        view.Txt_Curr.text = GameManager.instance.Score.ToString();
        //繼續遊戲按鈕事件
        view.Btn_Start.onClick.AddListener(delegate () {
            GameManager.instance.isOver = false;
            GameManager.instance.ctrl_run.isPause = false;
            GameManager.instance.currentShape.isPause = false;
            Destroy(gameObject);
            AudioManager.instance.PlayCursor();
        });

        view.Btn_Home.onClick.AddListener(delegate () {
            DestroyAll();
            GameManager.instance.isOver = true;
            AudioManager.instance.PlayCursor();
            GameManager.instance.CreatePanel(PanelType.StartPanel);
        });
    }
    void DestroyAll() {
        for (int i = 1; i < GameManager.instance.transform.childCount; i++)
        {
            Destroy(GameManager.instance.transform.GetChild(i).gameObject);
        }
        for (int i = 0; i < GameManager.instance.CreatePoint.childCount; i++)
        {
            Destroy(GameManager.instance.CreatePoint.GetChild(i).gameObject);
        }
    }
}
PasueCtrl腳本
using UnityEngine;
using System.Collections;

public class OverCtrl : MonoBehaviour {
    public OverView view { get; set; }
    // Use this for initialization
    void Start () {
        view = gameObject.AddComponent<OverView>();

        view.SetScoreText(GameManager.instance.Score);
        
        AudioManager.instance.PlayGameOver();


        //從新開始
        view.Btn_ReStart.onClick.AddListener(delegate() {
            ClearSquare();
            GameManager.instance.isOver = false;
            //取消暫停
            GameManager.instance.ctrl_run.isPause = false;
        });

        //返回主頁
        view.Btn_Home.onClick.AddListener(delegate() {
            ClearSquare();
            GameManager.instance.isOver = true;
            Destroy(GameManager.instance.ctrl_run.gameObject);
            GameManager.instance.CreatePanel(PanelType.StartPanel);
        });
    }

    // Update is called once per frame
    void ClearSquare () {
        //清除已生成的方塊
        for (int i = 0; i < GameManager.instance.CreatePoint.childCount; i++)
        {
            Destroy(GameManager.instance.CreatePoint.GetChild(i).gameObject);
        }
        //清除當前分數
        GameManager.instance.Score = 0;
        GameManager.instance.ctrl_run.view.Txt_Curr.text = 0.ToString();
        //銷燬自身
        Destroy(gameObject);
    }
}
OverCtrl腳本

在Data文件夾下分別建立方塊和遊戲涉及到的數據腳本:DataModel,CtrlInput,SquareControl。

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



/// <summary>
/// 面板類型(PanelType)
/// </summary>
public enum PanelType
{
    StartPanel, RunPanel, SetPanel, RankPanel, PasuePanel, GameOverPanel
}

/// <summary>
/// 方塊類型
/// </summary>
public enum SquareType
{
    Square_1, Square_2, Square_3, Square_4, Square_5, Square_6, Square_7
}

/// <summary>
/// 方向鍵控制
/// </summary>
public enum DirectionType
{
    None, Left, Right, Down, Up
}


public class DataModel  {
    /// <summary>
    /// 獲取數據儲存文件完整路徑
    /// </summary>
    public static readonly string dataPath = Application.persistentDataPath + @"\data.bin";
    /// <summary>
    /// 保存數據
    /// </summary>
    public static void SaveData()
    {
        string content = GameManager.instance.highScore + "\n" + GameManager.instance.numbersGame + "\n" + GameManager.instance.Sound;
        //寫出文件
        File.WriteAllText(dataPath, content);
    }
    /// <summary>
    /// 讀取數據
    /// </summary>
    public static void LoadData()
    {
        //判斷文件是否存在
        if (File.Exists(dataPath))
        {
            string content = File.ReadAllText(dataPath);
            GameManager.instance.highScore = int.Parse(content.Split('\n')[0]);
            GameManager.instance.numbersGame = int.Parse(content.Split('\n')[1]);
            GameManager.instance.Sound = content.Split('\n')[2] == "true" ? true : false;
        }
        else
        {
            GameManager.instance.highScore = 0;
            GameManager.instance.numbersGame = 0;
            GameManager.instance.Sound = false;
        }

    }
}
DataModel腳本
using UnityEngine;
using System.Collections;
using System;

//委託定義好了

public delegate void InputEventHandler(DirectionType res = DirectionType.None);

/// <summary>
/// 觸點滑動操做類
/// </summary>
/// 一、可用於移動端觸點移動
/// 二、可用於PC端鼠標移動
public class CtrlInput  {
    
    /// <summary>
    /// 委託方法事件
    /// </summary>
    public InputEventHandler handler;
    
    /// <summary>
    /// 觸摸起點
    /// </summary>
    Vector3 m_ptStart = Vector3.zero;

    /// <summary>
    /// 開始時間
    /// </summary>
    float m_TimeStart;

    /// <summary>
    /// 有效滑動長度
    /// </summary>
    float m_Len = 30;

    /// <summary>
    /// 操做標誌:用於控制操做識別完成後,不鬆手再滑動,操做無效。
    /// </summary>
    bool m_Flag = false;


    /// <summary>
    /// 起始位置
    /// </summary>
    /// <param name="pt"></param>
    public void Start(Vector3 pt)
    {
        //記錄原點位置
        m_ptStart = pt;
        m_TimeStart = Time.fixedTime;

        m_Flag = true;
    }

    /// <summary>
    /// 滑動後位置
    /// </summary>
    /// <param name="pt"></param>
    public void Check(Vector3 pt)
    {
        //操做無效
        if (!m_Flag) return;
        //滑動限時3秒
        if (Time.fixedTime - m_TimeStart > 3) return;
        //獲取兩點間的距離
        Vector3 v = pt - m_ptStart;
        float len = v.magnitude;
        //長度不夠
        if (len < m_Len) return;
        //Mathf.Rad2Deg 弧度到度轉換常數(只讀)。
        //Mathf.Atan2 反正切 以弧度爲單位計算並返回 y/x 的反正切值。
        //返回值表示相對直角三角形對角的角,其中 x 是臨邊邊長,而 y 是對邊邊長。 
        //返回值是在x軸和一個二維向量開始於0個結束在(x, y)處之間的角。 
        float degree = Mathf.Rad2Deg * Mathf.Atan2(v.x, v.y);
        if (-45 >= degree && degree >= -135)
        {   //
            m_Flag = false;
            handler(DirectionType.Left);
        }
        else if (45 <= degree && degree <= 135)
        {   //
            m_Flag = false;
            handler(DirectionType.Right);

        }
        else if (-45 <= degree && degree <= 45)
        {   //

            m_Flag = false;
            handler(DirectionType.Up);

        }
        else if (135 <= degree || degree <= 135)
        {   //
            m_Flag = false;
            handler(DirectionType.Down);
        }
    }

    #region 示例
    /*使用方法1:實例化 CtrlInput 類,
     * 實例化對象名:如 ctrlInput
     * 用新實例化出來的對象,調用方法
     * ctrlInput.TouchControl();
     * ctrlInput.MouseControl();
     * 
     * 使用方法2:實例化 CtrlInput 類,
     * 將方法拷貝或剪切到 控制類中
     * 修改示例中的 Start(touch.position),Check(touch.position) 方法調用爲
     * ctrlInput.Start(touch.position);
     * ctrlInput.Check(touch.position);
     */
    #region 一、移動端觸點移動示例
    /// <summary>
    /// 觸點輸入控制
    /// </summary>
    public void TouchControl()
    {
        //若是觸點數量爲0返回
        if (Input.touchCount == 0) return;

        Touch touch = Input.GetTouch(0);
        if (touch.phase == TouchPhase.Began)
        {
            Start(touch.position);
        }
        else if (touch.phase == TouchPhase.Moved)
        {
            Check(touch.position);
        }
    }
    #endregion

    #region 二、PC端鼠標輸入移動示例
    
    bool m_MouseMove = false;
    /// <summary>
    /// 鼠標輸入控制
    /// </summary>
    void MouseControl()
    {
        //鼠標按下時記錄當前位置
        if (Input.GetMouseButtonDown(0))
        {
            m_MouseMove = true;
            Start(Input.mousePosition);
        }
        else if (Input.GetMouseButtonUp(0))
        {
            m_MouseMove = false;
        }
        //滑動位置
        if (m_MouseMove)
        {
            Check(Input.mousePosition);
        }
    }
    #endregion
    #endregion


}
CtrlInput腳本
using UnityEngine;
using System.Collections;
using System;

public class SquareControl : MonoBehaviour {
    
    float timer;//計時
    float stepTime = 0.8f;
    int multiple = 15;
    // 標示鼠標是不是按下的移動
    bool m_MouseMove = false;
    // 處理輸入
    CtrlInput m_CtrlInput = new CtrlInput();

    public bool isPause { get; set; }
    public DirectionType dirType { get; set; }
    private void Awake()
    {
        dirType = DirectionType.None;
        m_CtrlInput.handler += onMove;
    }

    void onMove(DirectionType res)
    {
        dirType = res;
    }

    // Update is called once per frame
    void Update () {

        if (isPause) return;
        
        timer += Time.deltaTime;
        if (timer > stepTime)
        {
            timer = 0;
            WhereAbouts();
        }
        InputControl();
        MouseControl();
        TouchControl();
    }

    //IEnumerator doWhereabouts()
    //{
    //    while (true)
    //    {
    //        while (!isPause)
    //        {
    //            Vector3 pos = transform.position;
    //            pos.y -= 1;
    //            transform.position = pos;
    //            if (!GameManager.instance.IsValidMapPosition(transform))
    //            {
    //                AudioManager.instance.PlayDrop();
    //                pos.y += 1;
    //                transform.position = pos;
    //                isPause = true;
    //                bool isLineclear = GameManager.instance.PlaceShape(transform);
    //                if (isLineclear)
    //                {
    //                    AudioManager.instance.PlayLineClear();
    //                }
    //                else
    //                {
    //                    AudioManager.instance.PlayControl();
    //                }

    //                GameManager.instance.FallDown();
    //                Destroy(this);
    //            }
    //            //調整速度
    //            if (!isSpeedup)
    //            {
    //                yield return new WaitForSeconds(1);
    //            }
    //            else
    //            {
    //                yield return new WaitForSeconds(0.2f);
    //            }
    //        }
    //        yield return null;
    //    }
        
    //    //while (isSpeedup)
    //    //{
    //    //    transform.position = new Vector2(transform.position.x, transform.position.y - 1);
    //    //    yield return new WaitForSeconds(0.5f);
    //    //}
    //}
    void WhereAbouts()
    {
        Vector3 pos = transform.position;
        pos.y -= 1;
        transform.position = pos;
        if (!GameManager.instance.IsValidMapPosition(transform))
        {
            AudioManager.instance.PlayDrop();
            pos.y += 1;
            transform.position = pos;
            isPause = true;
            bool isLineclear = GameManager.instance.PlaceShape(transform);
            if (isLineclear)
            {
                AudioManager.instance.PlayLineClear();
            }
            else
            {
                AudioManager.instance.PlayControl();
            }

            GameManager.instance.FallDown();
            Destroy(this);
        }
    }
    /// <summary>
    /// 鍵盤輸入控制
    /// </summary>
    void InputControl() {
        float h = 0;
        if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow) || dirType == DirectionType.Left)
        {
            h = -1;
            dirType = DirectionType.None;
        }
        else if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow) || dirType == DirectionType.Right)
        {
            h = 1;
            dirType = DirectionType.None;
        }
        else if (Input.GetKeyDown(KeyCode.S) || Input.GetKeyDown(KeyCode.DownArrow) || dirType == DirectionType.Down)
        {
            stepTime /= multiple;
            dirType = DirectionType.None;
        }
        if (Input.GetKeyDown(KeyCode.W) || Input.GetKeyDown(KeyCode.UpArrow) || dirType == DirectionType.Up)
        {
            dirType = DirectionType.None;
            transform.RotateAround(transform.Find("Pivot").transform.position, Vector3.forward, 90);
            if (!GameManager.instance.IsValidMapPosition(transform))
            {
                transform.RotateAround(transform.Find("Pivot").transform.position, Vector3.forward, -90);
            }
            AudioManager.instance.PlayDrop();
        }
        if (h != 0)
        {
            dirType = DirectionType.None;
            Vector3 pos = transform.position;
            pos.x += h;
            transform.position = pos;
            if (!GameManager.instance.IsValidMapPosition(transform))
            {
                pos.x -= h;
                transform.position = pos;
            }
            AudioManager.instance.PlayDrop();
        }
    }
    /// <summary>
    /// 鼠標輸入控制
    /// </summary>
    void MouseControl() {
        //鼠標按下時記錄當前位置
        if (Input.GetMouseButtonDown(0))
        {
            m_MouseMove = true;
            m_CtrlInput.Start(Input.mousePosition);
        }
        else if(Input.GetMouseButtonUp(0))
        {
            m_MouseMove = false;
        }
        //滑動位置
        if (m_MouseMove)
        {
            m_CtrlInput.Check(Input.mousePosition);
        }
    }
    /// <summary>
    /// 觸屏控制
    /// </summary>
    void TouchControl() {
        if (Input.touchCount == 0) return;
        Touch touch = Input.GetTouch(0);
        if (touch.phase == TouchPhase.Began)
        {
            m_CtrlInput.Start(touch.position);
        }else if(touch.phase == TouchPhase.Moved)
        {
            m_CtrlInput.Check(touch.position);
        }        
    }

    public void SetColor(Color color)
    {
        foreach (Transform t in transform)
        {
            if (t.tag == "Block")
            {
                t.GetComponent<SpriteRenderer>().color = color;
            }
        }
    }
}
SquareControl腳本

在Script文件夾下建立一個名爲GameManager的C#腳本,用來管理遊戲的總體邏輯,而且該腳本只在場景中掛在一遍。打開GameManager腳本將其作成單例模式:

1 public static GameManager instance { get; set; }
2     void Awake ()
3     {
4         instance = this;
5     }
單例模式

在遊戲打開的時候,要實現Map地圖,開始界面等初始化,因此在GameManager類中寫一個初始化方法和建立界面生成的方法

/// <summary>
    /// 初始化窗口
    /// </summary>
    public void Init()
    {
        GameObject tranMap = Resources.Load<GameObject>("Prefabs/Other/Map");
        Transform map = Instantiate(tranMap).transform;
        map.name = "Map";
        CreatePoint = map.Find("CreatePoint");
        
    }


    /// <summary>
    /// 建立面板
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public GameObject CreatePanel(PanelType type)
    {
        GameObject prefab = Resources.Load<GameObject>("Prefabs/Panel/" + type);
        if (prefab != null)
        {
            GameObject clone = Instantiate(prefab, transform, false) as GameObject;
            clone.name = type.ToString();
            switch (type)
            {
                case PanelType.StartPanel:
                    ctrl_start = clone.AddComponent<StartCtrl>();
                    break;
                case PanelType.RunPanel:
                    ctrl_run = clone.AddComponent<RunCtrl>();
                    break;
                case PanelType.SetPanel:
                    ctrl_set = clone.AddComponent<SetCtrl>();
                    break;
                case PanelType.RankPanel:
                    ctrl_rank = clone.AddComponent<RankCtrl>();
                    break;
                case PanelType.GameOverPanel:
                    ctrl_over = clone.AddComponent<OverCtrl>();
                    break;
                case PanelType.PasuePanel:
                    ctrl_pasue = clone.AddComponent<PasueCtrl>();
                    break;
            }
            return clone;
        }
        return null;
    }
初始化
using System.Collections;
using System.Collections.Generic;
using UnityEngine;



public class GameManager : MonoBehaviour {
    public StartCtrl ctrl_start { get; set; }
    public RunCtrl ctrl_run { get; set; }
    public SetCtrl ctrl_set { get; set; }
    public RankCtrl ctrl_rank { get; set; }
    public OverCtrl ctrl_over { get; set; }
    public PasueCtrl ctrl_pasue { get; set; }
    public int Score { get; set; }
    public bool Sound { get; set; }
    public int highScore { get; set; }
    public int numbersGame { get; set; }
    public bool isOver { get; set; }
    public SquareControl currentShape { get; set; }
    public Transform CreatePoint { get; set; }

    public const int NORMAL_ROWS = 12;
    public const int MAX_ROWS = 15;
    public const int MAX_COLUMNS = 10;

    Transform[,] map = new Transform[MAX_COLUMNS, MAX_ROWS];
    bool isUpData;

    public static GameManager instance { get; set; }
    void Awake ()
    {
        instance = this;

        //初始化
        Init();
        
        isOver = false;
        //添加音頻管理腳本
        gameObject.AddComponent<AudioManager>();
        //判斷是不是PC端
        if (Application.platform == RuntimePlatform.WindowsPlayer)
        {
            //只有在windows系統的平臺上纔會執行
            //設置窗口大小
            Screen.SetResolution(540, 960, false);
        }
    }
    void Start()
    {
        if (ctrl_start == null)
        {
            CreatePanel(PanelType.StartPanel);
        }
        DataModel.LoadData();
        AudioManager.instance.SetAudioSource(!Sound);
    }

    void Update () {
        if (!isOver)
        {
            if (IsGameOver())
            {
                //遊戲結束,暫停
                ctrl_run.isPause = true;
                isOver = true;
            }
        }
        if (Input.GetKeyDown(KeyCode.Escape) && isOver)
        {
            Application.Quit();
        }
    }
    /// <summary>
    /// 初始化窗口
    /// </summary>
    public void Init()
    {
        GameObject tranMap = Resources.Load<GameObject>("Prefabs/Other/Map");
        Transform map = Instantiate(tranMap).transform;
        map.name = "Map";
        CreatePoint = map.Find("CreatePoint");
        
    }


    /// <summary>
    /// 建立面板
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public GameObject CreatePanel(PanelType type)
    {
        GameObject prefab = Resources.Load<GameObject>("Prefabs/Panel/" + type);
        if (prefab != null)
        {
            GameObject clone = Instantiate(prefab, transform, false) as GameObject;
            clone.name = type.ToString();
            switch (type)
            {
                case PanelType.StartPanel:
                    ctrl_start = clone.AddComponent<StartCtrl>();
                    break;
                case PanelType.RunPanel:
                    ctrl_run = clone.AddComponent<RunCtrl>();
                    break;
                case PanelType.SetPanel:
                    ctrl_set = clone.AddComponent<SetCtrl>();
                    break;
                case PanelType.RankPanel:
                    ctrl_rank = clone.AddComponent<RankCtrl>();
                    break;
                case PanelType.GameOverPanel:
                    ctrl_over = clone.AddComponent<OverCtrl>();
                    break;
                case PanelType.PasuePanel:
                    ctrl_pasue = clone.AddComponent<PasueCtrl>();
                    break;
            }
            return clone;
        }
        return null;
    }

    /// <summary>
    /// 生成方塊
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public GameObject CreateSquare(SquareType type)
    {
        GameObject prefab = Resources.Load<GameObject>("Prefabs/Square/" + type);
        if (prefab != null)
        {
            GameObject clone = Instantiate(prefab, CreatePoint) as GameObject;
            clone.name = type.ToString();
            clone.transform.position = CreatePoint.position;
            currentShape = clone.AddComponent<SquareControl>();
            return clone;
        }
        return null;
    }


    /// <summary>
    /// 方塊位置是否有效
    /// </summary>
    /// <param name="t">方塊座標</param>
    /// <returns></returns>
    public bool IsValidMapPosition(Transform t)
    {
        //遍歷方塊子物體
        foreach (Transform child in t)
        {
            //跳過標籤不爲 Block 的子物體
            if (child.tag != "Block") continue;
            Vector2 pos = Round(child.position);
            //判斷是否在地圖中
            if (IsInsideMap(pos) == false) return false;
            //判斷是否在數組中
            if (map[(int)pos.x, (int)pos.y] != null) return false;
        }
        return true;
    }

    /// <summary>
    /// 是否在地圖中
    /// </summary>
    /// <param name="pos"></param>
    /// <returns></returns>
    private bool IsInsideMap(Vector2 pos)
    {
        return pos.x >= 0 && pos.x < MAX_COLUMNS && pos.y >= 0;
    }

    /// <summary>
    /// 放置方塊
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public bool PlaceShape(Transform t)
    {
        foreach (Transform child in t)
        {
            if (child.tag != "Block") continue;
            Vector2 pos = Round(child.position);
            map[(int)pos.x, (int)pos.y] = child;
        }
        return CheckMap();
    }

    /// <summary>
    /// 檢查地圖是否不要消除行
    /// </summary>
    /// <returns></returns>
    private bool CheckMap()
    {
        int count = 0;
        for (int i = 0; i < MAX_ROWS; i++)
        {
            bool isFull = CheckIsRowFull(i);
            if (isFull)
            {
                count++;
                DeleteRow(i);
                MoveDownRowsAbove(i + 1);
                i--;
            }
        }
        Debug.Log(count);
        if (count > 0)
        {
            Score += (count * 100);
            if (Score > highScore)
            {
                highScore = Score;
            }
            isUpData = true;
            return true;
        }
        else return false;
    }

    /// <summary>
    /// 是否滿行
    /// </summary>
    /// <param name="row"></param>
    /// <returns></returns>
    bool CheckIsRowFull(int row)
    {
        for (int i = 0; i < MAX_COLUMNS; i++)
        {
            if (map[i, row] == null) return false;
        }
        return true;
    }

    /// <summary>
    /// 刪除指定行
    /// </summary>
    /// <param name="row"></param>
    void DeleteRow(int row)
    {
        for (int i = 0; i < MAX_COLUMNS; i++)
        {
            Destroy(map[i, row].gameObject);
            map[i, row] = null;
        }
    }

    /// <summary>
    /// 向下移動多行
    /// </summary>
    /// <param name="row"></param>
    void MoveDownRowsAbove(int row)
    {
        for (int i = row; i < MAX_ROWS; i++)
        {
            MoveDownRow(i);
        }
    }

    /// <summary>
    /// 向下移動行
    /// </summary>
    /// <param name="row"></param>
    void MoveDownRow(int row)
    {
        for (int i = 0; i < MAX_COLUMNS; i++)
        {
            if (map[i, row] != null)
            {
                map[i, row - 1] = map[i, row];
                map[i, row] = null;
                map[i, row - 1].position += new Vector3(0, -1, 0);
            }
        }
    }

    /// <summary>
    /// 方塊落下
    /// </summary>
    public void FallDown()
    {
        currentShape = null;
        if (isUpData)
        {
            ctrl_run.view.SetScoreText(Score, highScore);
        }
        foreach (Transform t in CreatePoint)
        {
            if (t.childCount <= 1)
            {
                Destroy(t.gameObject);
            }
        }
    }

    /// <summary>
    /// V3轉V2
    /// </summary>
    /// <param name="v"></param>
    /// <returns></returns>
    public static Vector2 Round(Vector3 v)
    {
        //RoundToInt:返回四捨五入到最接近的整數。
        int x = Mathf.RoundToInt(v.x);
        int y = Mathf.RoundToInt(v.y);
        return new Vector2(x, y);
    }


    /// <summary>
    /// 遊戲是否結束
    /// </summary>
    /// <returns></returns>
    public bool IsGameOver()
    {
        for (int i = NORMAL_ROWS; i < MAX_ROWS; i++)
        {
            for (int j = 0; j < MAX_COLUMNS; j++)
            {
                if (map[j, i] != null)
                {
                    isOver = true;
                    ctrl_run.isPause = true;
                    //加載結束面板
                    CreatePanel(PanelType.GameOverPanel);
                    ctrl_run.view.SetScoreText(0, highScore);
                    
                    numbersGame++;
                    DataModel.SaveData();
                    return true;
                }
            }
        }
        return false;
    }
}
GameManager腳本完整版

留到最後的話,一款遊戲若是缺了音樂總會以爲少些動感,因此在程序的最後說一下本工程中的音頻管理。

本工程中音頻AudioSource有兩個分別用來控制有可能衝突的音效,兩個AudioSource分別添加到MainCamera和Canvas上。而且將音頻文件所在的文件夾放置到Resource文件夾下,方便調用。在腳本文件夾中建立一個AudioManager腳本用來控制程序的音頻。

using UnityEngine;
using System.Collections;

public class AudioManager : MonoBehaviour {
    /// <summary>
    /// 點擊音效
    /// </summary>
    public AudioClip cursor;
    /// <summary>
    /// 放置音效
    /// </summary>
    public AudioClip balloon;
    /// <summary>
    /// 下落音效
    /// </summary>
    public AudioClip drop;
    /// <summary>
    /// 消除音效
    /// </summary>
    public AudioClip lineclear;
    /// <summary>
    /// 結束音效
    /// </summary>
    public AudioClip gameover;

    AudioSource audioSource1 { get; set; }
    AudioSource audioSource2 { get; set; }

    /// <summary>
    /// 單例模式
    /// </summary>
    public static AudioManager instance;

    private void Awake()
    {
        instance = this;
        audioSource1 = GameObject.Find("Main Camera").GetComponent<AudioSource>();
        audioSource2 = GameObject.Find("Canvas").GetComponent<AudioSource>();
        cursor = Resources.Load<AudioClip>("Audio/Cursor");
        balloon = Resources.Load<AudioClip>("Audio/Balloon");
        drop = Resources.Load<AudioClip>("Audio/Drop");
        lineclear = Resources.Load<AudioClip>("Audio/Lineclear");
        gameover = Resources.Load<AudioClip>("Audio/Gameover");
    }

    public void SetAudioSource(bool iscan = true) {
        audioSource1.enabled = audioSource2.enabled = iscan;
    }

    /// <summary>
    /// 點擊音效
    /// </summary>
    public void PlayCursor()
    {
        PlayAudio2(cursor);
    }

    /// <summary>
    /// 下落音效
    /// </summary>
    public void PlayDrop()
    {
        PlayAudio1(drop);
    }

    /// <summary>
    /// 放置音效
    /// </summary>
    public void PlayControl()
    {
        PlayAudio2(balloon);
    }

    /// <summary>
    /// 消除音效
    /// </summary>
    public void PlayLineClear()
    {
        PlayAudio2(lineclear);
    }

    /// <summary>
    /// 結束音效
    /// </summary>
    public void PlayGameOver() {
        PlayAudio2(gameover);
    }

    /// <summary>
    /// 播放音效
    /// </summary>
    /// <param name="clip">音效源</param>
    void PlayAudio1(AudioClip clip)
    {
        audioSource1.clip = clip;
        audioSource1.Play();
        //audioSource1.Pause();
    }
    void PlayAudio2(AudioClip clip)
    {
        audioSource2.clip = clip;
        audioSource2.Play();
    }
}
AudioSource

至此,俄羅斯方塊的代碼也完成了,代碼偏多,閱讀不便。如下是程序的工程源碼和打包好的可執行程序

unity3d俄羅斯方塊源碼5.4.3版(有動畫版)

https://download.csdn.net/download/u012433546/11037870

unity3d俄羅斯方塊源碼2018.2.14版(無動畫):

連接:https://pan.baidu.com/s/1-2UhD7_A-4IQf8qMMBm_Wg 
提取碼:w8rr 

unity3d俄羅斯方塊PC端程序:

https://download.csdn.net/download/u012433546/11038002

連接:https://pan.baidu.com/s/16vdhu6rRC5nx1Rz7W2qGsg 提取碼:ypa2 

相關文章
相關標籤/搜索