小時候,大家都應玩過或聽說過《俄羅斯方塊》,它是紅白機,掌機等一些電子設備中最常見的一款遊戲。而隨着時代的發展,信息的進步,遊戲畫面從簡單的黑白方塊到彩色方塊,遊戲的玩法機制從最簡單的消方塊到現在的多人pk等,無一不是在體現它的火爆。在這裏,通過這篇文章向大家分享一下自己在製作俄羅斯方塊的經驗和心得,以及文章最後的源碼和pc程序。
首先,看標題都知道這篇文章中所用到的遊戲引擎是:unity3d,版本不限,但最好是5.4.3以上的,原因是因爲作者自己沒有用過5.4.3以下的版本。
準備工具都有:unity3d + Visual Studio 2015
素材準備有(密碼:1vp5):字體方正粗圓_GBK,以及一些圖集
項目分析:
當一切準備就緒後,就可以開始創建我們的俄羅斯方塊工程(2d)的。
一、遊戲框架的搭建和方塊預設物的製作
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中所有方塊的屬性。
至此,俄羅斯方塊界面框架和預設物算是完成了。
二、遊戲代碼邏輯編寫
在unity中提供兩種常見的代碼方式:C#和JavaScript,在這裏採用C#去編寫。遊戲代碼的編寫風格以及框架有很多種,可以根據自己的習慣去書寫自己滿意的代碼風格,但爲了他人在閱讀瀏覽自己代碼是有可能會造成的一些問題,我們應該規範自己的代碼風格和框架。在本教程中採用的是簡單的MVC框架。
在Script文件夾下創建三個文件夾:Ctrl,View,Data,分別用來存放控制腳本,視圖腳本和數據腳本。
在View文件夾下分別創建對應UIPanel的C#腳本:StartView,RunView,SetView,RankView,PasueView,OverView。
StartView腳本:
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 () {
}
}
RunView腳本:
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;
}
}
SetView腳本:
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);
}
}
RankView腳本:
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();
}
}
PasueView腳本:
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>();
}
}
OverView腳本:
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。
StartCtrl腳本:
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);
}
}
}
RunCtrl腳本:
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);
}
}
}
SetCtrl腳本:
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 () {
}
}
RankCtrl腳本:
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 () {
}
}
PasueCtrl腳本:
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);
}
}
}
OverCtrl腳本:
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。
DataModel腳本:
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;
}
}
}
CtrlInput腳本:
using UnityEngine;
using System.Collections;
using System;
//委託定義好了
public delegate void InputEventHandler(DirectionType res = DirectionType.None);
/// <summary>
/// 觸點滑動操作類
/// </summary>
/// 1、可用於移動端觸點移動
/// 2、可用於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 1、移動端觸點移動示例
/// <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 2、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
}
SquareControl腳本:
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;
}
}
}
}
在Script文件夾下創建一個名爲GameManager的C#腳本,用來管理遊戲的整體邏輯,並且該腳本只在場景中掛在一遍。打開GameManager腳本將其做成單例模式:
public static GameManager instance { get; set; }
void Awake ()
{
instance = this;
}
在遊戲打開的時候,要實現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;
}
留到最後的話,一款遊戲如果缺了音樂總會覺得少些動感,所以在程序的最後說一下本工程中的音頻管理。
本工程中音頻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();
}
}
至此,俄羅斯方塊的代碼也完成了,代碼偏多,閱讀不便。以下是程序的工程源碼和打包好的可執行程序
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