Unity制作俄罗斯方块

一:演示


二:实现思路

——创建每一个方块可移动到的位置点,可以理解为创建一个游戏地图,从(0,0)点开始依次向x轴和y轴延伸,例如x最大为9,y最大为19,则创建了一个20行10列的地图


——制作每一个形状的预制体,Shape是每一个整体形状,Block是每一块小方块,CenterPos代表这个形状的旋转中心


——创建GameController脚本控制游戏逻辑,挂载到面板物体上。创建Shape脚本控制每个形状的操作,挂载到每个形状上


——在GameController脚本中编写生成形状的逻辑

//当前方块
public Shape CurShape { get; set; }

private void Update()
{
    if (CurShape == null)
    {
        SpawnBlock();
    }
}

/// <summary>
/// 生成方块
/// </summary>
private void SpawnBlock()
{
    int randomType = Random.Range(1, 8);
    GameObject shape = Instantiate(Resources.Load<GameObject>("Prefabs/Item/Shape" + randomType));
    CurShape = shape.GetComponent<Shape>();
    CurShape.transform.SetParent(transform);
    CurShape.transform.position = new Vector2(4, 20);
}


——在Shape脚本中编写形状下落的逻辑

private float fallTimer;//下落的计时器
private float fallTimeval = 0.5f;//下落的时间间隔

private void Update()
{
    //控制下落
    if (fallTimer >= fallTimeval)
    {
        //下落
        Fall();

        fallTimer = 0;
    }
    else
    {
        fallTimer += Time.deltaTime;
    }
}


/// <summary>
/// 下落
/// </summary>
private void Fall()
{
    Vector3 curPos = transform.position;
    Vector3 targetPos = curPos;
    targetPos.y -= 1;
    transform.position = targetPos;
}


——但是此时形状是可以无限下落的,到达底部也不会停止下落,所以我们需要编写判定能否下落的方法
在GameController脚本编写方法判定能否下落的逻辑,需要判定两点,每个方块的位置是否到达边界和每个方块的位置是否存在于地图数组中,注意必须对每个方块的位置进行四舍五入取整操作

public const int row_max = 23;
public const int column_max = 10;
public Transform[,] mapArray = new Transform[column_max, row_max];//地图数组(保存每个方块的位置信息)

/// <summary>
/// 判断是否为合法的位置
/// </summary>
/// <param name="shape">每一个形状</param>
/// <returns></returns>
public bool IsValidPos(Transform shape)
{
    foreach (Transform block in shape.transform)
    {
        if (block.GetComponent<SpriteRenderer>() == null) continue;

        Vector2 blockPos = block.transform.position;
        blockPos = new Vector2(Mathf.RoundToInt(blockPos.x), Mathf.RoundToInt(blockPos.y));
        if (IsBorder(blockPos) || mapArray[(int)blockPos.x, (int)blockPos.y] != null)
        {
            return false;
        }
    }
    return true;
}

/// <summary>
/// 判断是否到达边界
/// </summary>
/// <param name="pos">每一个方块的位置</param>
/// <returns></returns>
private bool IsBorder(Vector2 blockPos)
{
    if (blockPos.x < 0 || blockPos.x >= column_max || blockPos.y < 0)
    {
        return true;
    }
    return false;
}


——在形状下落的方法中添加判断去控制是否能够下落

private bool canFall = true;//能否下落

/// <summary>
/// 下落
/// </summary>
private void Fall()
{
    if (canFall == false)
    {
        return;
    }

    Vector3 curPos = transform.position;
    Vector3 targetPos = curPos;
    targetPos.y -= 1;
    transform.position = targetPos;

    if (GameController.Instance.IsValidPos(transform) == false)
    {
        targetPos.y += 1;
        transform.position = targetPos;

        FallToBottom();
    }
}

/// <summary>
/// 下落到了底部
/// </summary>
private void FallToBottom()
{
    canFall = false;
    GameController.Instance.CurBlock = null;
}


——现在形状与形状之间并不会叠加,而是会重叠在一起,在GameController脚本中编写方法,将每个方块位置添加到地图数组中(注意必须对每个方块的位置进行四舍五入取整操作)

/// <summary>
/// 将每个方块位置添加到地图数组中
/// </summary>
/// <param name="shapeTran"></param>
public void AddEachBlockTransToMapArray(Transform shape)
{
    foreach (Transform block in shape.transform)
    {
        if (block.GetComponent<SpriteRenderer>() == null)
        {
            continue;
        }

        Vector2 blockPos = block.position;
        blockPos = new Vector2(Mathf.RoundToInt(blockPos.x), Mathf.RoundToInt(blockPos.y));
        mapArray[(int)blockPos.x, (int)blockPos.y] = block;
    }
}


——当形状下落到底部时,将每一个方块的位置添加到地图数组中

/// <summary>
/// 下落到底部
/// </summary>
private void FallToBottom()
{
    canFall = false;
    GameController.Instance.CurShape = null;

    //方块下落到底部时将每个方块位置添加到地图数组中
    GameController.Instance.AddEachBlockTransToMapArray(transform);
}


——控制形状的左右移动

private void Update()
{
    //控制左右移动
    ControlMovement();
}

/// <summary>
/// 控制左右移动
/// </summary>
private void ControlMovement()
{
    float h = 0;
    if (Input.GetKeyDown(KeyCode.RightArrow))
    {
        h = 1;
    }
    else if (Input.GetKeyDown(KeyCode.LeftArrow))
    {
        h = -1;
    }

    Vector3 curPos = transform.position;
    Vector3 targetPos = curPos;
    targetPos.x += h;
    transform.position = targetPos;

    if (GameController.Instance.IsValidPos(transform) == false)
    {
        targetPos.x -= h;
        transform.position = targetPos;
    }
}


————控制形状的旋转

private void Update()
{
    //控制旋转
    ControlRotate();
}

/// <summary>
/// 控制旋转
/// </summary>
private void ControlRotate()
{
    if (Input.GetKeyDown(KeyCode.UpArrow))
    {
        Transform rotateTrans = transform.Find("CenterPos");
        transform.RotateAround(rotateTrans.position, Vector3.forward, -90);

        if (GameController.Instance.IsValidPos(transform) == false)
        {
            transform.RotateAround(rotateTrans.position, Vector3.forward, 90);
        }
    }
}


——控制加速下落

private void Update()
{
    //控制加速下落
    ControlUpspeed();
}

/// <summary>
/// 控制加速
/// </summary>
private void ControlUpspeed()
{
    if (Input.GetKeyDown(KeyCode.DownArrow))
    {
        fallTimeval = 0.05f;
    }
    if (Input.GetKeyUp(KeyCode.DownArrow))
    {
        fallTimeval = 0.5f;
    }
}



——判断整行的消除
先去判断是否有行满了,如果有则进行消除操作,消除时先删除掉当前行的每一个方块再将地图数组中当前行置空,之后再将满的行的上面行依次向下移动一行

/// <summary>
/// 检查是否有行满了
/// </summary>
private void CheckRowFull()
{
    for (int row = 0; row < row_max; row++)
    {
        bool isFull = true;
        for (int column = 0; column < column_max; column++)
        {
            if (mapArray[column, row] == null)
            {
                isFull = false;
                break;
            }
        }

        //如果有行满了
        if (isFull)
        {
            //————————————————————消除操作
            ClearRow(row);
            MoveDownRow(row + 1);
            row--;
        }
    }
}

/// <summary>
/// 清除行
/// </summary>
/// <param name="row">清除的行(满的行)</param>
private void ClearRow(int _row)
{
    for (int coloum = 0; coloum < column_max; coloum++)
    {
        Destroy(mapArray[coloum, _row].gameObject);
        mapArray[coloum, _row] = null;
    }
}

/// <summary>
/// 将清除的行上面的每一行依次向下移动一行
/// </summary>
/// <param name="row">依次移动的起始行(清除的行的上面一行)</param>
private void MoveDownRow(int _row)
{
    for (int row = _row; row < row_max; row++)
    {
        for (int column = 0; column < column_max; column++)
        {
            if (mapArray[column, row] != null)
            {
                mapArray[column, row - 1] = mapArray[column, row];
                mapArray[column, row] = null;
                mapArray[column, row - 1].position -= Vector3.up;
            }
        }
    }
}


——判断游戏是否失败

/// <summary>
/// 判断游戏失败
/// </summary>
/// <returns></returns>
private bool IsGameover()
{
    for (int row = row_max - 3; row < row_max; row++)
    {
        for (int column = 0; column < column_max; column++)
        {
            if (mapArray[column, row] != null)
            {
                return true;
            }
        }
    }
    return false;
}

三:完整代码

——GameController脚本,控制游戏整体逻辑

using UnityEngine;

public class GameController : MonoSingleton<GameController>
{
    public const int row_max = 23;
    public const int column_max = 10;
    private Transform[,] mapArray;

    //当前方块
    public Shape CurShape { get; set; }

    /// <summary>
    /// 初始化数据
    /// </summary>
    public void InitData()
    {
        mapArray = new Transform[column_max, row_max];
    }

    /// <summary>
    /// 清空地图
    /// </summary>
    public void ClearMap()
    {
        for (int row = 0; row < row_max; row++)
        {
            for (int column = 0; column < column_max; column++)
            {
                if (mapArray[column, row] != null)
                {
                    Destroy(mapArray[column, row].gameObject);
                    mapArray[column, row] = null;
                }
            }
        }
    }

    private void Update()
    {
        //if (GameManager.Instance.IsPause)
        //{
        //    return;
        //}

        if (CurShape == null)
        {
            SpawnBlock();
        }
    }

    /// <summary>
    /// 生成方块
    /// </summary>
    private void SpawnBlock()
    {
        Color randomColor = Random.ColorHSV();
        int randomType = Random.Range(1, 8);
        GameObject shape = Instantiate(Resources.Load<GameObject>("Prefabs/Item/Shape" + randomType));
        CurShape = shape.GetComponent<Shape>();
        CurShape.transform.SetParent(transform);
        CurShape.Init(new Vector2(4, 20), randomColor);
    }

    /// <summary>
    /// 判断是否为合法的位置
    /// </summary>
    /// <param name="shape">每一个形状</param>
    /// <returns></returns>
    public bool IsValidPos(Transform shape)
    {
        foreach (Transform block in shape.transform)
        {
            if (block.GetComponent<SpriteRenderer>() == null) continue;

            Vector2 blockPos = block.transform.position;
            blockPos = new Vector2(Mathf.RoundToInt(blockPos.x), Mathf.RoundToInt(blockPos.y));
            if (IsBorder(blockPos) || mapArray[(int)blockPos.x, (int)blockPos.y] != null)
            {
                return false;
            }
        }
        return true;
    }

    /// <summary>
    /// 判断是否到达边界
    /// </summary>
    /// <param name="pos">每一个方块的位置</param>
    /// <returns></returns>
    private bool IsBorder(Vector2 blockPos)
    {
        if (blockPos.x < 0 || blockPos.x >= column_max || blockPos.y < 0)
        {
            return true;
        }
        return false;
    }

    /// <summary>
    /// 将每个方块位置添加到地图数组中
    /// </summary>
    /// <param name="shapeTran"></param>
    public void AddEachBlockTransToMapArray(Transform shape)
    {
        foreach (Transform block in shape.transform)
        {
            if (block.GetComponent<SpriteRenderer>() == null)
            {
                continue;
            }

            Vector2 blockPos = block.position;
            blockPos = new Vector2(Mathf.RoundToInt(blockPos.x), Mathf.RoundToInt(blockPos.y));
            mapArray[(int)blockPos.x, (int)blockPos.y] = block;
        }

        //检查是否有行满了
        CheckRowFull();
    }

    /// <summary>
    /// 检查是否有行满了
    /// </summary>
    private void CheckRowFull()
    {
        for (int row = 0; row < row_max; row++)
        {
            bool isFull = true;
            for (int column = 0; column < column_max; column++)
            {
                if (mapArray[column, row] == null)
                {
                    isFull = false;
                    break;
                }
            }

            //如果有行满了
            if (isFull)
            {
                //————————————————————消除操作
                ClearRow(row);
                MoveDownRow(row + 1);
                row--;
            }
        }
    }

    /// <summary>
    /// 清除行
    /// </summary>
    /// <param name="row">清除的行(满的行)</param>
    private void ClearRow(int _row)
    {
        for (int coloum = 0; coloum < column_max; coloum++)
        {
            Destroy(mapArray[coloum, _row].gameObject);
            mapArray[coloum, _row] = null;
        }

        UIManager.Instance.FindUI<UI_Game>().UpdateCurScore();
    }

    /// <summary>
    /// 将清除的行上面的每一行依次向下移动一行
    /// </summary>
    /// <param name="row">依次移动的起始行(清除的行的上面一行)</param>
    private void MoveDownRow(int _row)
    {
        for (int row = _row; row < row_max; row++)
        {
            for (int column = 0; column < column_max; column++)
            {
                if (mapArray[column, row] != null)
                {
                    mapArray[column, row - 1] = mapArray[column, row];
                    mapArray[column, row] = null;
                    mapArray[column, row - 1].position -= Vector3.up;
                }
            }
        }
    }

    /// <summary>
    /// 判断游戏失败
    /// </summary>
    /// <returns></returns>
    public bool IsGameover()
    {
        for (int row = row_max - 3; row < row_max; row++)
        {
            for (int column = 0; column < column_max; column++)
            {
                if (mapArray[column, row] != null)
                {
                    return true;
                }
            }
        }
        return false;
    }
}


——Shape脚本,控制每个形状的操作

using UnityEngine;

public class Shape : MonoBehaviour
{
    private bool canFall = true;//能否下落

    private float fallTimer;//下落的计时器
    private float fallTimeval = 0.5f;//下落的时间间隔

    /// <summary>
    /// 初始化形状
    /// </summary>
    /// <param name="pos"></param>
    /// <param name="color"></param>
    public void Init(Vector2 pos, Color color)
    {
        transform.position = pos;

        foreach (Transform child in transform)
        {
            if (child.GetComponent<SpriteRenderer>() != null)
            {
                child.GetComponent<SpriteRenderer>().color = color;
            }
        }
    }

    private void Update()
    {
        if (canFall == false)
        {
            return;
        }

        //控制下落
        if (fallTimer >= fallTimeval)
        {
            //下落
            Fall();

            fallTimer = 0;
        }
        else
        {
            fallTimer += Time.deltaTime;
        }

        //控制加速下落
        ControlUpspeed();

        //控制左右移动
        ControlMovement();

        //控制旋转
        ControlRotate();
    }

    /// <summary>
    /// 控制左右移动
    /// </summary>
    private void ControlMovement()
    {
        float h = 0;
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            h = 1;
        }
        else if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            h = -1;
        }

        Vector3 curPos = transform.position;
        Vector3 targetPos = curPos;
        targetPos.x += h;
        transform.position = targetPos;

        if (GameController.Instance.IsValidPos(transform) == false)
        {
            targetPos.x -= h;
            transform.position = targetPos;
        }
    }

    /// <summary>
    /// 控制旋转
    /// </summary>
    private void ControlRotate()
    {
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            Transform rotateTrans = transform.Find("CenterPos");
            transform.RotateAround(rotateTrans.position, Vector3.forward, -90);

            if (GameController.Instance.IsValidPos(transform) == false)
            {
                transform.RotateAround(rotateTrans.position, Vector3.forward, 90);
            }
        }
    }

    /// <summary>
    /// 控制加速
    /// </summary>
    private void ControlUpspeed()
    {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            fallTimeval = 0.05f;
        }
        if (Input.GetKeyUp(KeyCode.DownArrow))
        {
            fallTimeval = 0.5f;
        }
    }

    /// <summary>
    /// 下落
    /// </summary>
    private void Fall()
    {
        Vector3 curPos = transform.position;
        Vector3 targetPos = curPos;
        targetPos.y -= 1;
        transform.position = targetPos;

        if (GameController.Instance.IsValidPos(transform) == false)
        {
            targetPos.y += 1;
            transform.position = targetPos;

            FallToBottom();
        }
    }

    /// <summary>
    /// 下落到底部
    /// </summary>
    private void FallToBottom()
    {
        canFall = false;
        GameController.Instance.CurShape = null;

        //方块下落到底部时将每个方块位置添加到地图数组中
        GameController.Instance.AddEachBlockTransToMapArray(transform);

        if (GameController.Instance.IsGameover())
        {
            GameManager.Instance.Gameover();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/LLLLL__/article/details/106807069