方块类型 BlockType

枚举(墙、O字型、I字型、S字型、Z字型、L字形、J字形、T字形)

public enum BlockType 
{
    Wall,
    O,
    I,
    S,
    Z,
    L,
    J,
    T
}

绘制地图 MapManager

地图分为

  • 固定墙壁:始终不动

  • 动态墙壁:可以消除的墙壁

功能分为:

  • 绘制地图(绘制左墙、右墙、底墙)

  • 添加动态墙壁(什么时候添加动态墙布)

  • 消除某行

难点1:某行满了,怎么消除

做一个line数组,数组的大小等于方块的高度。

line[0]表示第1行的数量,如果数量等于宽度-2,则说明这一行满了

满了之后,消除这一行,这一行上面的动态砖块下移,同时line数组也要进行偏移。

可能出现两行或者3行同时满,那么我们可以使用递归进行。

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

public class MapManager : MonoSingleton<MapManager>
{

    [SerializeField] Transform wallParent;

    public int Width;
    public int Height;

    public Vector2Int originPos = new Vector2Int(-30, -5);

    private List<Transform> staticWalls = new List<Transform>();
    public List<Transform> dynamicWalls = new List<Transform>();

    int[] lines;

    private void Start()
    {
        lines = new int[Height-1];
        DrawMaps();
    }

    private void DrawMaps()
    {
        // 1. 绘制左墙
        for(int i = 0; i < Height - 1; i++)
        {
            GenerateWall(originPos.x, originPos.y + 1 + i);
        }
        // 2. 绘制右墙
        for(int i = 0; i < Height - 1; i++)
        {
            GenerateWall(originPos.x + Width - 1, originPos.y + 1 + i);
        }
        // 3. 绘制底墙
        for(int i = 0; i < Width; i++)
        {
            GenerateWall(originPos.x + i, originPos.y);
        }


    }

    private void GenerateWall(int x, int y)
    {
        Transform walltf = ResourcesManager.Instance.GenerateBlock(BlockType.Wall,x,y,wallParent);
        staticWalls.Add(walltf);
    }

    public void AddWalls(List<Transform> walls)
    {
        for(int i = 0; i < walls.Count; i++)
        {
           
            int y = (int)walls[i].position.y;
            if(RelativeOriginPosY(y) >= lines.Length)
            {
                print("Game Over");
                Time.timeScale = 0f;
                return;
            }
            ResourcesManager.Instance.SetColor(BlockType.Wall, walls[i]);
            dynamicWalls.Add(walls[i]);
            SetLine(y);
        }

        CheckClear();
    }

    private void CheckClear()
    {
        for(int i = 0; i < lines.Length; i++)
        {
            if (lines[i] == Width - 2)
            {
                ClearLine(i);
                // 整体下移
                FallOne(i);
                // line下移
                LineFall(i);
                CheckClear();
                break;
            }
        }
    }

    private void LineFall(int y)
    {
        for(int i = y +1 ; i < lines.Length; i++)
        {
            lines[i-1] = lines[i];
        }
    }

    private void FallOne(int relativeY)
    {
        for(int i = 0; i< dynamicWalls.Count; i++)
        {
            int x = (int)dynamicWalls[i].position.x;
            int y = (int)dynamicWalls[i].position.y;
            if(RelativeOriginPosY(y) > relativeY)
            {
                dynamicWalls[i].position = new Vector3(x, --y);
            }
        }
    }

    private void ClearLine(int y)
    {
        List<Transform> delLine = new List<Transform>();
        for(int i = 0; i < dynamicWalls.Count; i++)
        {
            if (RelativeOriginPosY((int)(dynamicWalls[i].position.y)) == y)
            {
                delLine.Add(dynamicWalls[i]);
            }
        }

        for(int i = 0; i < delLine.Count; i++)
        {
            dynamicWalls.Remove(delLine[i]);
            Destroy(delLine[i].gameObject);
        }
    }

    private void SetLine(int y)
    {
        int relativeY = RelativeOriginPosY(y);
        lines[relativeY]++;
    }

    private int RelativeOriginPosY(int y)
    {
        return y + Mathf.Abs(originPos.y) - 1;
    }
}

方块信息BlockInfo

存储各种方块各种形态的信息

using System.Collections.Generic;
using UnityEngine;
public class BlockInfo
{

    List<Vector2Int[]> list = new List<Vector2Int[]>();

    public BlockInfo(BlockType blockType)
    {
        switch (blockType)
        {
            case BlockType.O:
                // 只有一种状态
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(1, 0),
                    new Vector2Int(0, -1),
                    new Vector2Int(1, -1),
                    new Vector2Int(0, 0)
                });
                break;
            case BlockType.I:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(1, 0),
                    new Vector2Int(2, 0),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(0, 1),
                    new Vector2Int(0, -1),
                    new Vector2Int(0, -2),
                    new Vector2Int(0, 0)
                });
                break;
            case BlockType.Z:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1,1),
                    new Vector2Int(0, 1),
                    new Vector2Int(1, 0),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(-1, -1),
                    new Vector2Int(0, 0)
                });
                break;
            case BlockType.S:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1,0),
                    new Vector2Int(0, 1),
                    new Vector2Int(1, 1),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(-1, 1),
                    new Vector2Int(0, -1),
                    new Vector2Int(0, 0)
                });
                break;
            case BlockType.T:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 1),
                    new Vector2Int(0, 1),
                    new Vector2Int(1, 1),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(0, -1),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(1, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(0, 1),
                    new Vector2Int(0, -1),
                    new Vector2Int(1, 0),
                    new Vector2Int(0, 0)
                });
                break;
            case BlockType.L:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 1),
                    new Vector2Int(-1, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(1, 1)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 1),
                    new Vector2Int(0, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(0, -1)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(1, 1),
                    new Vector2Int(-1, 0),
                    new Vector2Int(1, 0),
                    new Vector2Int(0, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(-1, 1),
                    new Vector2Int(-1, -1),
                    new Vector2Int(0, -1)
                });
                break;
            case BlockType.J:
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 1),
                    new Vector2Int(0, 1),
                    new Vector2Int(1, 1),
                    new Vector2Int(1, 0)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(0, 0),
                    new Vector2Int(0, 1),
                    new Vector2Int(0, -1),
                    new Vector2Int(-1, -1)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(0, 0),
                    new Vector2Int(-1, 0),
                    new Vector2Int(1, 0),
                    new Vector2Int(-1, 1)
                });
                list.Add(new Vector2Int[]
                {
                    new Vector2Int(-1, 0),
                    new Vector2Int(-1, 1),
                    new Vector2Int(-1, -1),
                    new Vector2Int(0, 1)
                });
                break;

            default:
                break;
        }
        
    }

    public Vector2Int[] this[int index]
    {
        get {
            if (index < 0) return list[0];
            else if(index >= list.Count) return list[list.Count - 1];
            else return list[index]; 
        }
    }
    public int Count { get { return list.Count; }}
}

BlockWorker类

功能有:

  • 随机创建一个方块(7种类型)

  • 方块变形

  • 方块变形前,判断是否能够变形

  • 方块左右移动

  • 方块左右移动前,判断是否能够移动

  • 方块向下移动

  • 方块向下移动前,判断是否能够移动

using System.Collections.Generic;
using UnityEngine;

public class BlockWorker : MonoBehaviour
{

    Dictionary<BlockType, BlockInfo> blocksDic = new Dictionary<BlockType, BlockInfo>();

    List<Transform> blocks = new List<Transform>();

    Vector2Int birthPos;

    BlockInfo currentBlock;
    int currentBlockIndex = 0;

    float nextUpdate;
    [SerializeField] float fallSpeed;

    BlockType currentBlockType = BlockType.O;

    // 基准点
    Vector2Int originPos = Vector2Int.zero;

    private void Start()
    {
        SetBirthPos();
        InitDic();
        RandomBlock();
    }

    private void SetBirthPos()
    {
        MapManager maps = MapManager.Instance;
        int x = maps.originPos.x + maps.Width / 2;
        int y = maps.originPos.y + maps.Height + 2;
        birthPos = new Vector2Int(x, y);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (CanChageShape(MapManager.Instance))
            {
                ChangeShape();
            }
        }

        if(Input.GetKeyDown(KeyCode.LeftArrow) ||
            Input.GetKeyDown(KeyCode.A))
        {
            if (CanMoveLR(Vector2Int.left, MapManager.Instance))
            {
                Move(Vector2Int.left);
            }
            
        }else if (Input.GetKeyDown(KeyCode.RightArrow) ||
            Input.GetKeyDown(KeyCode.D))
        {
            if (CanMoveLR(Vector2Int.right, MapManager.Instance))
            {
                Move(Vector2Int.right);
            }
        }

    }

    private void FixedUpdate()
    {
        if (Time.time < nextUpdate) return;
        if(CanFallMove(MapManager.Instance))
            Move(Vector2Int.down);

        nextUpdate = Time.time + (1 / fallSpeed);
    }

    private void Move(Vector2Int direction)
    {
        originPos += direction;
        for(int i = 0; i <blocks.Count; i++)
        {
            int x = (int)blocks[i].position.x + direction.x;
            int y = (int)blocks[i].position.y + direction.y;
            blocks[i].position = new Vector2(x, y);
        }
        
    }



    private bool CanMoveLR(Vector2Int direction, MapManager maps)
    {
        // 检测墙
        for (int i = 0; i < blocks.Count; i++)
        {
            int x = (int)blocks[i].position.x + direction.x;
            int y = (int)blocks[i].position.y + direction.y;
            if(x <= maps.originPos.x || x >= maps.originPos.x + maps.Width - 1
                || y <= maps.originPos.y)
            {
                return false;
            }
            for(int j = 0; j < maps.dynamicWalls.Count; j++)
            {
                if (maps.dynamicWalls[j].position.x == x &&
                maps.dynamicWalls[j].position.y == y)
                {
                    return false;
                }
            }
        }
        return true;
    }

    private bool CanFallMove(MapManager maps)
    {
        for (int i = 0; i < blocks.Count; i++)
        {
            int x = (int)blocks[i].position.x + Vector2Int.down.x;
            int y = (int)blocks[i].position.y + Vector2Int.down.y; 
            if (y <= maps.originPos.y)
            {
                maps.AddWalls(blocks);
                RandomBlock();
                return false;
            }
            for (int j = 0; j < maps.dynamicWalls.Count; j++)
            {
                if (maps.dynamicWalls[j].position.x == x &&
                maps.dynamicWalls[j].position.y == y)
                {
                    maps.AddWalls(blocks);
                    RandomBlock();
                    return false;
                }
            }
        }
        return true;
    }

    private void InitDic()
    {
        blocksDic.Add(BlockType.I, new BlockInfo(BlockType.I));
        blocksDic.Add(BlockType.O, new BlockInfo(BlockType.O));
        blocksDic.Add(BlockType.S, new BlockInfo(BlockType.S));
        blocksDic.Add(BlockType.L, new BlockInfo(BlockType.L));
        blocksDic.Add(BlockType.T, new BlockInfo(BlockType.T));
        blocksDic.Add(BlockType.Z, new BlockInfo(BlockType.Z));
        blocksDic.Add(BlockType.J, new BlockInfo(BlockType.J));
    }

    private void RandomBlock()
    {
        blocks.Clear();
        currentBlockType = (BlockType)Random.Range(1, 8);
        currentBlock = blocksDic[currentBlockType];
        originPos = birthPos;
        for (int i = 0; i < currentBlock[currentBlockIndex].Length; i++) {
            int x = currentBlock[currentBlockIndex][i].x + originPos.x;
            int y = currentBlock[currentBlockIndex][i].y + originPos.y;
            blocks.Add(GenerateBlock(x, y));
        }
    }


    private void ChangeShape()
    {
        currentBlockIndex = (currentBlockIndex + 1) % currentBlock.Count;

        for (int i = 0; i < currentBlock[currentBlockIndex].Length; i++)
        {
            int x = currentBlock[currentBlockIndex][i].x + originPos.x;
            int y = currentBlock[currentBlockIndex][i].y + originPos.y;
            blocks[i].position = new Vector2(x, y);
        }
    }

    private bool CanChageShape(MapManager maps)
    {
        int tempIndex = (currentBlockIndex + 1) % currentBlock.Count;
        for (int i = 0; i < currentBlock[tempIndex].Length; i++)
        {
            int x = currentBlock[tempIndex][i].x + originPos.x;
            int y = currentBlock[tempIndex][i].y + originPos.y;
            if (x <= maps.originPos.x || x >= maps.originPos.x + maps.Width - 1
                || y <= maps.originPos.y)
            {
                return false;
            }
            for (int j = 0; j < maps.dynamicWalls.Count; j++)
            {
                if (maps.dynamicWalls[j].position.x == x &&
                maps.dynamicWalls[j].position.y == y)
                {
                    return false;
                }
            }   
        }

        return true;
    }



    private Transform GenerateBlock(int x, int y)
    {
        return ResourcesManager.Instance.GenerateBlock(currentBlockType, x, y);
    }

}

ResourcesManager

  • 实例化生成对象

using UnityEngine;

public class ResourcesManager:MonoSingleton<ResourcesManager>
{
    private const string RES_PATH = "Square";

    public Transform GenerateBlock(BlockType blockType, int x, int y, Transform parent = null)
    {
        Transform blockgo = Resources.Load<Transform>(RES_PATH);
        Transform block = Instantiate(blockgo, parent);
        SetColor(blockType, block);
        block.localPosition = new Vector2(x, y);
        return block;
    }

    public void SetColor(BlockType blockType, Transform block)
    {
        switch (blockType)
        {
            case BlockType.Wall:
                block.GetComponent<SpriteRenderer>().color = Color.red;
                break;
            case BlockType.O:
                block.GetComponent<SpriteRenderer>().color = Color.white;
                break;
            case BlockType.I:
                block.GetComponent<SpriteRenderer>().color = Color.yellow;
                break;
            case BlockType.S:
                block.GetComponent<SpriteRenderer>().color = Color.cyan;
                break;
            case BlockType.Z:
                block.GetComponent<SpriteRenderer>().color = Color.magenta;
                break;
            case BlockType.L:
                block.GetComponent<SpriteRenderer>().color = Color.gray;
                break;
            case BlockType.J:
                block.GetComponent<SpriteRenderer>().color = Color.grey;
                break;
            case BlockType.T:
                block.GetComponent<SpriteRenderer>().color = Color.yellow;
                break;
            default:
                break;
        }
    }
}

MonoSingleton类

using UnityEngine;

public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T>
{

    private static T m_Instance = null;

    // 在设计阶段 写脚本 没有挂载物体上 希望脚本 单例模式
    public static T instance
    {
        get
        {
            if (m_Instance == null)
            {
                // 从场景中查找
                m_Instance = GameObject.FindObjectOfType(typeof(T)) as T;
                if (m_Instance == null)
                {
                    // 1. 直接创建一个游戏对象
                    // 2. 为这个游戏对象添加脚本组件
                    m_Instance = new GameObject("Singleton of " + typeof(T).ToString(), typeof(T)).GetComponent<T>();
                    // 初始化
                    m_Instance.Init();
                }

            }
            return m_Instance;
        }
    }

    // 设计阶段,写脚本,挂在物体上
    // 项目运行时【系统会帮助我们将脚本类实例化为对象(new 脚本)】
    // 项目运行时 在Awake时,在场景中找到唯一实例,记录在m_Instance中
    private void Awake()
    {

        if (m_Instance == null)
        {
            m_Instance = this as T;
        }
    }

    // 提供初始化的一种选择,可以使用Init、Start都可以
    public virtual void Init() { }

    // 当程序退出做清理工作! 单例模式 = null
    private void OnApplicationQuit()
    {
        m_Instance = null;
    }
}