小時候,你們都應玩過或據說過《俄羅斯方塊》,它是紅白機,掌機等一些電子設備中最多見的一款遊戲。而隨着時代的發展,信息的進步,遊戲畫面從簡單的黑白方塊到彩色方塊,遊戲的玩法機制從最簡單的消方塊到如今的多人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 () { } }
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; } }
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); } }
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(); } }
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>(); } }
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(); } }
在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); } } }
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); } } }
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 () { } }
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 () { } }
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); } } }
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); } }
在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; } } }
在遊戲打開的時候,要實現Map地圖,開始界面等初始化,因此在GameManager類中寫一個初始化方法和建立界面生成的方法
留到最後的話,一款遊戲若是缺了音樂總會以爲少些動感,因此在程序的最後說一下本工程中的音頻管理。
本工程中音頻AudioSource有兩個分別用來控制有可能衝突的音效,兩個AudioSource分別添加到MainCamera和Canvas上。而且將音頻文件所在的文件夾放置到Resource文件夾下,方便調用。在腳本文件夾中建立一個AudioManager腳本用來控制程序的音頻。
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