zoukankan      html  css  js  c++  java
  • unity3d俄罗斯方块源码教程+源码和程序下载

    小时候,大家都应玩过或听说过《俄罗斯方块》,它是红白机,掌机等一些电子设备中最常见的一款游戏。而随着时代的发展,信息的进步,游戏画面从简单的黑白方块到彩色方块,游戏的玩法机制从最简单的消方块到现在的多人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 = 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 + "
    " + GameManager.instance.numbersGame + "
    " + 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('
    ')[0]);
                GameManager.instance.numbersGame = int.Parse(content.Split('
    ')[1]);
                GameManager.instance.Sound = content.Split('
    ')[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>
    /// 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
    
    
    }
    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 

  • 相关阅读:
    中金所金融业务知识学习笔记(含股指期货、股指期权、国债期货)
    人工智能中小样本问题相关的系列模型演变及学习笔记(四):知识蒸馏、增量学习
    人工智能中小样本问题相关的系列模型演变及学习笔记(三):迁移学习、深度迁移学习
    人工智能中小样本问题相关的系列模型演变及学习笔记(二):生成对抗网络 GAN
    人工智能中小样本问题相关的系列模型演变及学习笔记(一):元学习、小样本学习
    行业知识图谱的构建及应用
    Verilog代码和FPGA硬件的映射关系(一)
    FPGA内部硬件结构简介
    Altera的Cyclone系列器件命名规则
    学会使用Hdlbits网页版Verilog代码仿真验证平台
  • 原文地址:https://www.cnblogs.com/CLXiao-1029/p/10562062.html
Copyright © 2011-2022 走看看