小时候,大家都应玩过或听说过《俄罗斯方块》,它是红白机,掌机等一些电子设备中最常见的一款游戏。而随着时代的发展,信息的进步,游戏画面从简单的黑白方块到彩色方块,游戏的玩法机制从最简单的消方块到现在的多人pk等,无一不是在体现它的火爆。在这里,通过这篇文章向大家分享一下自己在制作俄罗斯方块的经验和心得,以及文章最后的源码和pc程序。

首先,看标题都知道这篇文章中所用到的游戏引擎是:unity3d,版本不限,但最好是5.4.3以上的,原因是因为作者自己没有用过5.4.3以下的版本。

准备工具都有:unity3d + Visual Studio 2015

素材准备有(密码:m6gz):字体方正粗圆_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。

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 = .ToString();
Txt_Count.text = .ToString();
GameManager.instance.Score = ;
GameManager.instance.highScore = ;
GameManager.instance.numbersGame = ;
} 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(, 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(, );
//测试生成
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 = ; i < GameManager.instance.transform.childCount; i++)
{
Destroy(GameManager.instance.transform.GetChild(i).gameObject);
}
for (int i = ; 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 = ; i < GameManager.instance.CreatePoint.childCount; i++)
{
Destroy(GameManager.instance.CreatePoint.GetChild(i).gameObject);
}
//清除当前分数
GameManager.instance.Score = ;
GameManager.instance.ctrl_run.view.Txt_Curr.text = .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')[]);
GameManager.instance.numbersGame = int.Parse(content.Split('\n')[]);
GameManager.instance.Sound = content.Split('\n')[] == "true" ? true : false;
}
else
{
GameManager.instance.highScore = ;
GameManager.instance.numbersGame = ;
GameManager.instance.Sound = false;
} }
}

DataModel脚本

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 = ; /// <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 > ) 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 (- >= degree && degree >= -)
{ // 左
m_Flag = false;
handler(DirectionType.Left);
}
else if ( <= degree && degree <= )
{ // 右
m_Flag = false;
handler(DirectionType.Right); }
else if (- <= degree && degree <= )
{ // 上 m_Flag = false;
handler(DirectionType.Up); }
else if ( <= degree || degree <= )
{ // 下
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 == ) return; Touch touch = Input.GetTouch();
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())
{
m_MouseMove = true;
Start(Input.mousePosition);
}
else if (Input.GetMouseButtonUp())
{
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 = ;
// 标示鼠标是否是按下的移动
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 = ;
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 -= ;
transform.position = pos;
if (!GameManager.instance.IsValidMapPosition(transform))
{
AudioManager.instance.PlayDrop();
pos.y += ;
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 = ;
if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow) || dirType == DirectionType.Left)
{
h = -;
dirType = DirectionType.None;
}
else if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow) || dirType == DirectionType.Right)
{
h = ;
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, );
if (!GameManager.instance.IsValidMapPosition(transform))
{
transform.RotateAround(transform.Find("Pivot").transform.position, Vector3.forward, -);
}
AudioManager.instance.PlayDrop();
}
if (h != )
{
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())
{
m_MouseMove = true;
m_CtrlInput.Start(Input.mousePosition);
}
else if(Input.GetMouseButtonUp())
{
m_MouseMove = false;
}
//滑动位置
if (m_MouseMove)
{
m_CtrlInput.Check(Input.mousePosition);
}
}
/// <summary>
/// 触屏控制
/// </summary>
void TouchControl() {
if (Input.touchCount == ) return;
Touch touch = Input.GetTouch();
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脚本将其做成单例模式:

 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;
}

初始化

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 = ;
public const int MAX_ROWS = ;
public const int MAX_COLUMNS = ; 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(, , 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 >= && pos.x < MAX_COLUMNS && pos.y >= ;
} /// <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 = ;
for (int i = ; i < MAX_ROWS; i++)
{
bool isFull = CheckIsRowFull(i);
if (isFull)
{
count++;
DeleteRow(i);
MoveDownRowsAbove(i + );
i--;
}
}
Debug.Log(count);
if (count > )
{
Score += (count * );
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 = ; 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 = ; 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 = ; i < MAX_COLUMNS; i++)
{
if (map[i, row] != null)
{
map[i, row - ] = map[i, row];
map[i, row] = null;
map[i, row - ].position += new Vector3(, -, );
}
}
} /// <summary>
/// 方块落下
/// </summary>
public void FallDown()
{
currentShape = null;
if (isUpData)
{
ctrl_run.view.SetScoreText(Score, highScore);
}
foreach (Transform t in CreatePoint)
{
if (t.childCount <= )
{
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 = ; j < MAX_COLUMNS; j++)
{
if (map[j, i] != null)
{
isOver = true;
ctrl_run.isPause = true;
//加载结束面板
CreatePanel(PanelType.GameOverPanel);
ctrl_run.view.SetScoreText(, 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

unity3d俄罗斯方块源码教程+源码和程序下载的更多相关文章

  1. [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

    本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...

  2. 直播平台源码搭建教程:微信小程序中的直播如何去掉水印

    直播平台源码搭建教程:微信小程序中的直播如何去掉水印 本文与大家分享一下直播平台源码搭建教程,如何去掉直播视频的水印 var services = require('../../lib/service ...

  3. ios源码-ios游戏源码-ios源码下载

    游戏源码   一款休闲类的音乐小游戏源码 该源码实现了一款休闲类的音乐小游戏源码,该游戏的源码很简单,而且游戏的玩法也很容易学会,只要我们点击视图中的grid,就可以 人气:2943运行环境:/Xco ...

  4. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  5. 转--2014年最新810多套android源码2.46GB免费一次性打包下载

    转载自:http://www.eoeandroid.com/thread-497046-1-1.html 感谢该博客主人无私奉献~~ 下面的源码是从今年3月份开始不断整理源码区和其他网站上的安卓例子源 ...

  6. C#UDP(接收和发送源码)源码完整

    C#UDP(接收和发送源码)源码完整 最近做了一个UDP的服务接收和发送的东西.希望能对初学的朋友一点帮助. 源码如下: 一.逻辑--UdpServer.cs using System;using S ...

  7. 二维码zxing源码分析(五)精简代码

    由于工作的需要,我并不是需要二维码扫描的所有的功能,我只是需要扫一扫,并显示出来图片和url就行,于是我们就要精简代码了,源码已经分析完了,精简起来就方便多了,源码分析请看 二维码zxing源码分析( ...

  8. android源码-安卓源码-Android源码下载-安卓游戏源码

    android源码   高仿精仿金山手机卫士应用源码V1.2 高仿精仿金山手机卫士应用源码,该应用的级别实现了金山卫士的级别功能了,可以说跟现实中我们使用的金山卫士应用的功能几乎差不 人气:9286  ...

  9. 2014年最新720多套Android源码2.0GB免费一次性打包下载

    之前发过一个帖子,但是那个帖子有点问题我就重新发一个吧,下面的源码是我从今年3月份开始不断整理源码区和其他网站上的android源码,目前总共有720套左右,根据实现的功能被我分成了100多个类,总共 ...

随机推荐

  1. Confluence 6 PostgreSQL 问题解决

    如果 Confluence 提示没有 class 文件,你可能将你的 JDBC 驱动放置到了错误的文件夹. 如果你不能从你从 Confluence 中连接到 PostgreSQL ,并且这 2 个服务 ...

  2. Confluence 6 用户目录图例 - 使用 LDAP 授权的内部目录

    上面的图:Confluence 连接 LDAP 服务器仅用做授权 https://www.cwiki.us/display/CONFLUENCEWIKI/Diagrams+of+Possible+Co ...

  3. Python基础之类方法和静态方法

    小叙一会儿: 通常情况下,在类中定义的所有函数(注意了,这里说的就是所有,跟self啥的没关系,self也只是一个再普通不过 的参数而已)都是对象的绑定方法,对象在调用绑定方法时会自动将自己作为参数传 ...

  4. laravel 路由缓存

    使用路由缓存之前,需要知晓路由缓存只能用于控制器路由,不能用于闭包路由,如果路由定义中包含闭包路由将无法进行路由缓存,只有将所有路由定义转化为控制器路由或资源路由后才能执行路由缓存命令: php ar ...

  5. Best Free Hacking E-Books 2017 In PDF Format

    1.Best Free Hacking E-Books 2017 In PDF Format: 电子书籍下载地址 后续我会更新在我的百度云资源 上,需要的留言Black Belt Hacking &a ...

  6. Android定位元素与操作

    一.常用识别元素的工具 uiautomator:Android SDK自带的一个工具,在tools目录下 monitor:Android SDK自带的一个工具,在tools目录下 Appium Ins ...

  7. idea的操作

  8. python爬虫-淘宝商品密码(图文教程附源码)

    今天闲着没事,不想像书上介绍的那样,我相信所有的数据都是有规律可以寻找的,然后去分析了一下淘宝的商品数据的规律和加密方式,用了最简单的知识去解析了需要的数据. 这个也让我学到了,解决问题的方法不止一个 ...

  9. Atom插件下载失败解决办法

    转自:http://www.cnblogs.com/20145221GQ/p/5334762.html#正题 一般方法(Atom自动安装) 打开Atom >> Packages >& ...

  10. 史上最简单的SpringCloud教程 | 第七篇: 高可用的分布式配置中心(Spring Cloud Config)

    上一篇文章讲述了一个服务如何从配置中心读取文件,配置中心如何从远程git读取配置文件,当服务实例很多时,都从配置中心读取文件,这时可以考虑将配置中心做成一个微服务,将其集群化,从而达到高可用,架构图如 ...