【Unity】俄罗斯方块

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39574690/article/details/89221233

一、使用SpriteRenderer作为小方块图片,创建7种由若干个小方块图片组成的方块,如下图:

Shape-1是一个空物体,其子物体Block、Block(1)、Block(2)、Block(3)是小方块,Pivot是锚点(空物体),锚点用作于旋转中心点,方块旋转是以它为中心进行旋转的。旋转方块的代码如下:

transform.RotateAround(pivot.position, Vector3.forward, -90);

 二、通过测试划分出一个俄罗斯方块操作区域(游戏区域),在z轴相同 的xy平面上的 每个坐标作为二维数组map的索引,如:map[1,0]保存(1,0,z)坐标上的小方块物体的Transform组件,游戏区域上x是横轴、y是纵轴,左下角的小方块坐标(0,0),右上角是的小方块坐标(x-1,y-1)。这样,将游戏区域划分成 一个map数组后,就可以管理全部小方块,实现判断整行满并消除行,方块是否可以下落一行,方块是否可以变形,方块是否可以水平移动等功能,下面贴出相关代码。

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

public class Model : MonoBehaviour
{
    public const int NORMAL_ROWS = 20;//最大行数(这个也是用于判断游戏结束逻辑)
    public const int MAX_ROWS = 23;//最大行数+3(用于判断游戏结束逻辑)
    public const int MAX_COLUMNS = 10;//最大列数
    private Transform[,] map = new Transform[MAX_COLUMNS, MAX_ROWS];//地图数组map
    //下面这些不用管,是UI的一些参数
    private int score = 0;
    private int highScore = 0;
    private int numbersGame = 0;

    public int Score { get { return score; } }
    public int HighScore { get { return highScore; } }
    public int NumbersGame { get { return numbersGame; } }

    public bool isDataUpdate = false;

    private void Awake()
    {
        LoadData();
    }
    //检查一个方块(如Sharp-1)的位置是否合理,参数t是方块的Transform
    public bool IsValidMapPosition(Transform t)
    {
        foreach (Transform child in t)
        {
            //子物体带"Block"字符的标签tag都是"Block",它们就是小方块
            if (child.tag != "Block") continue;
            //如果遍历到的子物体是小方块的话进行下面的逻辑判断
            Vector2 pos = child.position.Round();//Round是我扩展的方法,==贴出来.
            //若不在地图范围内,则不可用
            if (IsInsideMap(pos) == false) return false;
            //有其他图形占用着,则不可用
            if (map[(int)pos.x, (int)pos.y] != null)
            {
                return false;//不可用
            }
        }
        return true;
    }
    //不在地图范围内返回false,在返回true
    private bool IsInsideMap(Vector2 pos)
    {
        return pos.x >= 0 && pos.x < MAX_COLUMNS && pos.y >= 0;
    }
    /// <summary>
    /// 在方块进行检查位置不合理后,会固定自身位置 放入map[,]保存小方块Block的Transform组件
    /// </summary>
    /// <param name="t">shape的父物体transform</param>
    public bool PlaceShape(Transform t)
    {
        foreach (Transform child in t)
        {
            if (child.tag != "Block") continue;
            Vector2 pos = child.position.Round();
            map[(int)pos.x, (int)pos.y] = child;
        }
        //放进map后需要检查整张地图是否需要消行
        return CheckMap();
    }
    /// <summary>
    /// 检查地图 是否需要消除行
    /// </summary>
    private bool CheckMap()
    {
        int count = 0;//消行数
        for (int i = 0; i < MAX_ROWS; i++)
        {
            //若第i行填满小方块
            if (CheckIsRowFull(i))
            {
                count++;
                DeleteRow(i);//消除第i行
                MoveDownRowsAbove(i + 1);//第i+1行及其上方的所有行向下移动一行(从下到上移动)
                i--; //因为上面的那一行已经移动下来了,这里将i--,目的是为了继续检查上面那一行是否满,因为for会将i++,这样就抵消了i++,让i继续保持原样
            }
        }
        //下面不用管,是UI部分的逻辑
        if (count > 0)
        {
            score += count * 100;
            if(score>highScore)
            {
                highScore = score;
            }
            isDataUpdate = true;
            return true;
        }
        else
        {
            return false;
        }

    }
    //检查第row行是否满
    private bool CheckIsRowFull(int row)
    {
        for (int i = 0; i < MAX_COLUMNS; i++)
        {
            if (map[i, row] == null)
            {
                return false;
            }
        }
        return true;
    }
    private void DeleteRow(int row)
    {
        for (int i = 0; i < MAX_COLUMNS; i++)
        {
            Destroy(map[i, row].gameObject);
            map[i, row] = null;
        }
    }
    //将索引row行至最上面的行都往下移动一行(从下开始移动)
    public void MoveDownRowsAbove(int row)
    {
        for (int i = row; i < MAX_ROWS; i++)
        {
            MoveDownRow(i);
        }
    }
    //将row行 下移一行,处理了逻辑位置 和 物体位置
    private 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);//物理上的移动
            }
        }
    }
    //判断游戏结束逻辑
    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)
                {
                    numbersGame++;
                    SaveData();
                    return true;
                }
            }
        }
        return false;
    }
    public void Restart()
    {
        for(int i=0;i<MAX_COLUMNS;i++)
        {
            for(int j=0;j<MAX_ROWS;j++)
            {
                if(map[i,j])
                {
                    Destroy(map[i, j].gameObject);
                    map[i, j] = null;
                }
            }
        }
        score = 0;
    }
    private void LoadData()
    {
        highScore= PlayerPrefs.GetInt("HighScore", 0);
        numbersGame = PlayerPrefs.GetInt("NumbersGame",0);
    }
    private void SaveData()
    {
        PlayerPrefs.SetInt("HighScore", highScore);
        PlayerPrefs.SetInt("NumbersGame", numbersGame);
    }
    public void ClearData()
    {
        score = 0;
        highScore = 0;
        numbersGame = 0;
        SaveData();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Shape : MonoBehaviour {

    private GameManager gameManager;
    private Ctrl ctrl;
    private bool isPause = false;
    private bool isSpeedUp = false;
    private float timer = 0;
    private float stepTime = 0.8f;//0.8s方块下落一行
    private Transform pivot;//锚点(旋转中心点)
    private int multiple = 15; 

    private void Awake()
    {
        pivot = transform.Find("Pivot");
    }

    private void Update()
    {
        if (isPause) return;
        timer += Time.deltaTime;
        if(timer>stepTime)
        {
            timer = 0;
            Fall();
        }
        InputControl();
    }
    /// <summary>
    /// 方块下落一行逻辑
    /// </summary>
    private void Fall()
    {
        //先尝试将方块下移一行
        Vector3 pos = transform.position;
        pos.y -= 1;
        transform.position = pos;
        //使用Model的方法检查方块位置是否合理,若不合理返回false进入if
        if(ctrl.model.IsValidMapPosition(this.transform)==false)
        {
            pos.y += 1;//因为当前位置是不可用的,而上一个位置肯定是可用的,所以这里移动回到上一个位置处
            transform.position = pos;
            isPause = true;//暂停当前shape的下移
            bool isLineclear = ctrl.model.PlaceShape(this.transform);//使用Model的方法固定shape,即放入map数组保存小方块信息,并且检查地图消除行
            if (isLineclear) ctrl.audioManager.PlayLineClear();//isLineclear是true代表消除行了,播放声音
            gameManager.FallDown();//设置GameManager中的currentShape=null,这样就会实例化下一个shape方块,并且更新分数UI
            return;
        }
        ctrl.audioManager.PlayDrop();//方块固定的声音
    }
    //输入控制
    private void InputControl()
    {
       // if (isSpeedUp) return;
        float h=0;
        if(Input.GetKeyDown(KeyCode.LeftArrow))
        {
            h = -1;
        }
        else if(Input.GetKeyDown(KeyCode.RightArrow))
        {
            h = 1;
        }
        if(h!=0)
        {
            //这里和Fall下移逻辑处理手法一样,不再阐述!
            Vector3 pos = transform.position;
            pos.x += h;
            transform.position = pos;
            if (ctrl.model.IsValidMapPosition(this.transform) == false)
            {
                pos.x -= h;
                transform.position = pos;
            }
            else
            {
                ctrl.audioManager.PlayControl();
            }
        }

        //按键盘↑对方块进行旋转(即变形)
        if(Input.GetKeyDown(KeyCode.UpArrow))
        {
            //以pivot位置为旋转中心,绕z轴旋转-90°
            transform.RotateAround(pivot.position, Vector3.forward, -90);
            //旋转后自然也要检查下位置是否合理
            if (ctrl.model.IsValidMapPosition(this.transform) == false)
            {
                //不合理,旋转90° 变回之前那样子(注意:这个过程是瞬间发生的,玩家不会看到这个变化过程!)
                transform.RotateAround(pivot.position, Vector3.forward, 90);
            }
            else
            {
                ctrl.audioManager.PlayControl();
            }
        }
        //按键盘↓会进行加速方块下移
        if(Input.GetKeyDown(KeyCode.DownArrow))
        {
            isSpeedUp = true;
            stepTime /= multiple;
        }
    }
    public void Init(Color color,Ctrl ctrl,GameManager gameManager)
    {
        this.gameManager = gameManager;
        this.ctrl = ctrl;
        foreach(Transform t in transform)
        {
            if(t.tag=="Block")
            {
                t.GetComponent<SpriteRenderer>().color = color;
            }
        }
    }
    public void Pause()
    {
        isPause = true;
    }
    public void Resume()
    {
        isPause = false;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameManager : MonoBehaviour {

    private Ctrl ctrl;
    private bool isPause = true;//是否暂停状态
    private Shape currentShape = null;
    public Shape[] shapes;//shape prefabs
    public Color[] colors;//shape colors
    private Transform blockHolder;
    private void Awake()
    {
        ctrl = GetComponent<Ctrl>();
        blockHolder = transform.Find("BlockHolder");
    }   
	
	void Update () {
		if(isPause)
        {
            return;
        }
        //如果当前方块为空,生成一个方块(这个currentShape是由Shape
        if (currentShape == null)
        {
            SpawnShape();
        }
	}
    /// <summary>
    /// 删除已经没有小方块Block的sharp方块物体,即childCount小于等于1的
    /// 并且判断游戏结束逻辑
    /// </summary>
    public void FallDown()
    {
        currentShape = null;//将当前方块设置为空,这一步是为了生成新的方块
        //更新UI逻辑
        if(ctrl.model.isDataUpdate)
        {
            ctrl.view.UpdateGameUI(ctrl.model.Score, ctrl.model.HighScore);
        }
        //删除已经没有小方块的sharp物体
        foreach(Transform t in blockHolder)
        {
            if(t.childCount<=1)
            {
                Destroy(t.gameObject);
            }
        }
        //判断游戏结束
        if(ctrl.model.isGameOver())
        {
            PauseGame();
            ctrl.view.ShowGameOverUI(ctrl.model.Score);
        }
    }
    public void StartGame()
    {
        isPause = false;
        if (currentShape != null)
        {
            currentShape.Resume();
        }
    }
    public void PauseGame()
    {
        isPause = true;
        if(currentShape!=null)
        {
            currentShape.Pause();
        }
    }
    /// <summary>
    /// 生成一个方块sharp
    /// </summary>
    void SpawnShape()
    {
        //随机方块类型
        int index = Random.Range(0, shapes.Length);//random create a shape
        //随机颜色
        int indexColor = Random.Range(0, colors.Length);//random shape color
        //实例化方块
        currentShape = GameObject.Instantiate(shapes[index]);
        //放入blockholder物体下
        currentShape.transform.parent = blockHolder;
        //初始化其方块颜色等工作
        currentShape.Init(colors[indexColor],ctrl,this);

    }
    /// <summary>
    /// 删除当前正在下落的方块
    /// </summary>
    public void ClearShape()
    {
        if(currentShape!=null)
        {
            Destroy(currentShape.gameObject);
            currentShape = null;
        }
    }
}

大致上,俄罗斯方块的核心逻辑都总结在这3个脚本,Sharp是处理了方块的移动逻辑,Model是真正的核心处理方块的位置是否合理、消行、消行后将上方方块整体下移一行等逻辑,GameManager是处理游戏结束之类的逻辑。

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/89221233