[Realize 10 of 100 Games with Unity] Re-enactment of the classic Tetris game

Preface

Remastering classic games has always been a popular and challenging task in today's gaming industry. Tetris is a deeply rooted, addictive classic game that has enjoyed widespread popularity over the past few decades. Its simple and strategic gameplay has attracted the attention of countless players. Therefore, I decided to use the Unity engine to reproduce this classic game so that more people can experience the fun again.

By using the Unity engine, I was able to take advantage of its powerful tools and features to build a game from scratch similar to the original Tetris game. I will try to keep the core elements of the original game, including seven different shapes of blocks (Tetris) that the player can rotate and move to fill complete horizontal rows, completing the goal of eliminating and scoring points.

In addition to retaining the original gameplay, I also plan to add some additional features and improvements to the game. For example, adding diverse difficulty levels makes the game suitable for any player's skill level. I also plan to add special props or skills to make the game richer and more interesting. In addition, I will also focus on the visual effects and sound effects of the game to enhance the player's immersion.

My goal is to create an irresistible game experience that allows players to reminisce about classics while also experiencing new fun. Whether it is to challenge high scores alone or compete with friends, this replica version of the Tetris game will bring players memories and joy of their childhood.

I'm very excited to be able to use the Unity engine to realize this dream, and look forward to sharing this Tetris replica with everyone in the future. In this process, I will work hard to improve and perfect the game to ensure that it can run smoothly on various platforms and bring players the best gaming experience.

Thank you for your support and attention! Let's reminisce about the classics and enjoy the fun of the game!

Let’s first take a look at the final effect achieved
Insert image description here

Start project

Link: https://pan.baidu.com/s/1BMlwclVV2gOdnNppSc7v6A
Extraction code: hxfs

mesh generation

Generic singleton

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

public class SingleBase<T> : MonoBehaviour where T : class
{
    
    
    public static T Instance;

    protected virtual void Awake()
    {
    
    
        Instance = this as T;
    }
}

Game management

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

public class GameManager : SingleBase<GameManager>
{
    
    
    protected override void Awake()
    {
    
    
        base.Awake();
    }

    private void Start()
    {
    
    
        OnStartGame();
    }

    //开始游戏
    void OnStartGame()
    {
    
    
        //网格生成
        MapManager.Instance.InitMap();
    }
}

Simple factory class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
///<summary>
//简单工厂
///</summary>
public static class SimpleFactory
{
    
    
	//创建Resources/Model中的物体
	public static GameObject CreateModel(string name, Transform parent)
    {
    
    
        return Object.Instantiate(Resources.Load("Model/" + name), parent) as GameObject;
    }
}

Grid map generation

//常量类
public static class Defines
{
    
    
    public static readonly int RowCount = 15;//网格行数
    public static readonly int ColCount = 10;//网格列数
    public static readonly float Offset = 0.9f;//格子间隔
}

Grid map manager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//网格地图管理器
public class MapManager : SingleBase<MapManager>
{
    
    
    //初始化网格地图
    public void InitMap()
    {
    
    
        for (int row = 0; row < Defines.RowCount; row++)
        {
    
    
            for (int col = 0; col < Defines.ColCount; col++)
            {
    
    
                GameObject obj = SimpleFactory.CreateModel("block", transform);
                obj.transform.localPosition = new Vector3(col * Defines.Offset, -row * Defines.Offset, 0);
            }
        }
    }
}


Insert image description here
The effect of mounting GameManager and MapManager
Insert image description here

Block script

Create a new Block script

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

public enum BlockType
{
    
    
    Empty,//空
    Static,//不动
    Model,//模型
}

//方块脚本
public class Block : MonoBehaviour
{
    
    
    public BlockType type;
    public int RowIndex;
    public int ColIndex;
    public SpriteRenderer sp;
    public Sprite normalSprite;//默认的图片
    public Sprite modelSprite;//怪物图片
    public void Init(int row, int col, BlockType type)
    {
    
    
        this.type = type;
        this.RowIndex = row;
        this.ColIndex = col;
    }

    private void Awake()
    {
    
    
        sp = GetComponent<SpriteRenderer>();
        normalSprite = sp.sprite;
        modelSprite = Resources.Load<Sprite>("Icon/gbl");
    }

    private void Start()
    {
    
    
        SetTypeToSp();
    }

    public void SetTypeToSp()
    {
    
    
        switch (this.type)
        {
    
    
            case BlockType.Empty:
                sp.sprite = normalSprite;
                sp.color = Color.white;
                break;
            case BlockType.Static:
                sp.color = Color.red;
                break;
            case BlockType.Model:
                sp.sprite = modelSprite;
                sp.color = Color.white;
                break;
        }

    }
}

Grid map manager MapManager call

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//网格地图管理器
public class MapManager : SingleBase<MapManager>
{
    
    
    public Block[,] blockArr;
    //初始化网格地图
    public void InitMap()
    {
    
    
        blockArr = new Block[Defines.RowCount,Defines.ColCount];
        for (int row = 0; row < Defines.RowCount; row++)
        {
    
    
            for (int col = 0; col < Defines.ColCount; col++)
            {
    
    
                GameObject obj = SimpleFactory.CreateModel("block", transform);
                obj.transform.localPosition = new Vector3(col * Defines.Offset, -row * Defines.Offset, 0);
                
                Block b = obj.AddComponent<Block>();
                b.Init(row, col, BlockType.Model);
                //存储到二维数组
                blockArr[row, col] = b;
            }
        }
    }
}

running result
Insert image description here

Tetris base class, drawing block shapes

MapManager new method

//切换对应下标的方块的类型
public void ChangeType(int row, int col, BlockType type)
{
    
    
    Block b = blockArr[row, col];
    b.type = type;
    b.SetTypeToSp();
}

Tetris base class

//俄罗斯方块基类
public class TetrisBase
{
    
    
    public int rowIndex;//对应整个网格的行坐标
    public int colIndex;//对应整个网格的列坐标
    public int rowCount;//存储形状的行总数
    public int colCount;//存储形状的列总数
    public BlockType[,] blockArr;//存储枚举的二维数组
                                 //初始化
    public virtual void Init()
    {
    
    
    }

    //画自己(更改网格对应的图片精灵)
    public virtual void DrawMe()
    {
    
    
        for (int row = 0; row < rowCount; row++)
        {
    
    
            for (int col = 0; col < colCount; col++)
            {
    
    
                if (blockArr[row, col] != BlockType.Empty)
                {
    
    
                    int map_rowIndex = rowIndex + row;
                    int map_colIndex = colIndex + col;
                    MapManager.Instance.ChangeType(map_rowIndex, map_colIndex, blockArr[row, col]);
                }

            }

        }

    }
    //擦除自己
    public virtual void WipeMe()
    {
    
    
        for (int row = 0; row < rowCount; row++)
        {
    
    
            for (int col = 0; col < colCount; col++)
            {
    
    
                if (blockArr[row, col] != BlockType.Empty)
                {
    
    
                    int map_rowIndex = rowIndex + row;
                    int map_colIndex = colIndex + col;
                    MapManager.Instance.ChangeType(map_rowIndex, map_colIndex, BlockType.Empty);
                }

            }

        }
    }

    //向下移动
    public virtual void MoveDown()
    {
    
    
        rowIndex++;
    }

    //左移动
    public virtual void MoveLeft()
    {
    
    
        colIndex--;
    }

    //右移动
    public virtual void MoveRight()
    {
    
    
        colIndex++;
    }
}

T-shaped Tetris

//T形状的俄罗斯方块
public class Tetris_T : TetrisBase
{
    
    
    public override void Init()
    {
    
    
        rowCount = 2;
        colCount = 3;
        blockArr = new BlockType[,]{
    
    
            {
    
    BlockType.Model,BlockType.Model,BlockType.Model},
            {
    
    BlockType.Empty,BlockType.Model,BlockType.Empty}
        };
    }
}

GameManager is called to draw the square shape

TetrisBase currentTetris;//当前操作的俄罗斯

//开始游戏
void OnStartGame()
{
    
    
    //网格生成
    MapManager.Instance.InitMap();
    currentTetris = CreateTetris();
    //画出来
    currentTetris.DrawMe();
}

//创建
TetrisBase CreateTetris()
{
    
    
    TetrisBase t = new Tetris_T();
    t.Init();
    t.colIndex = Defines.ColCount / 2 - t.colCount / 2;
    return t;
}

Effect
Insert image description here

move logic

Define movement direction enum

//移动方向
public enum Direction
{
    
    
    None,
    Right,
    Left,
    Down
}

Modify the Tetris base class TetrisBase

//根据方向移动
public virtual void MoveByDir(Direction dir)
{
    
    
    //擦除自己
    WipeMe();
    switch (dir)
    {
    
    
        case Direction.None:
            break;
        case Direction.Right:
            MoveRight();
            break;
        case Direction.Left:
            MoveLeft();
            break;
        case Direction.Down:
            MoveDown();
            break;
    }
    DrawMe();//画自己
}

GameManager adds new user operations

void Update(){
    
    
    InputCtl();
}
//用户操作
public void InputCtl()
{
    
    
    if (Input.GetKeyDown(KeyCode.A))
        currentTetris.MoveByDir(Direction.Left);
    if (Input.GetKeyDown(KeyCode.D))
        currentTetris.MoveByDir(Direction.Right);
    if (Input.GetKeyDown(KeyCode.S))
        currentTetris.MoveByDir(Direction.Down);
}

running result
Insert image description here

Restrict movement

New method in Tetris base class TetrisBase

//是否能移动的方向
public virtual bool IsCanMove(Direction dir)
{
    
    
    int _rowIndex = rowIndex;
    int _colIndex = colIndex;
    switch (dir)
    {
    
    
        case Direction.None:
            break;
        case Direction.Right:
            _colIndex++;
            break;
        case Direction.Left:
            _colIndex--;
            break;
        case Direction.Down:
            _rowIndex++;
            break;
    }

    //超出网格
    if (_colIndex < 0 || _colIndex + colCount > Defines.ColCount || _rowIndex + rowCount > Defines.RowCount)
    {
    
    
        return false;
    }
    return true;
}

GameManager call

//用户操作
public void InputCtl()
{
    
    
    if (Input.GetKeyDown(KeyCode.A))
    {
    
    
        if (currentTetris.IsCanMove(Direction.Left))
        {
    
    
            currentTetris.MoveByDir(Direction.Left);
        }
    }
    if (Input.GetKeyDown(KeyCode.D))
    {
    
    
        if (currentTetris.IsCanMove(Direction.Right))
        {
    
    
            currentTetris.MoveByDir(Direction.Right);
        }
    }
    if (Input.GetKeyDown(KeyCode.S))
    {
    
    
        if (currentTetris.IsCanMove(Direction.Down))
        {
    
    
            currentTetris.MoveByDir(Direction.Down);
        }
    }
}

running result
Insert image description here

free fall

Defines new common classes

public static readonly float downTime = 1;//下落时间间隔

Modify GameManager

float timer;

//开始游戏
void OnStartGame()
{
    
    
    timer = Defines.downTime;
}
void Update()
{
    
    
    AutoMoveDown();
}

//自动下落
public void AutoMoveDown()
{
    
    
    timer -= Time.deltaTime;
    if (timer <= 0)
    {
    
    
        timer = Defines.downTime;
        if (currentTetris.IsCanMove(Direction.Down))
        {
    
    
            currentTetris.MoveByDir(Direction.Down);
        }
        else
        {
    
    
            //不能移动重新创建
            currentTetris = CreateTetris();
            currentTetris.DrawMe();
        }
    }
}

Effect
Insert image description here

After falling, set the corresponding style to the immovable type.

MapManager new method

//设置不可移动后的俄罗斯方块对应的位置为Static类型
public void SetStatic(TetrisBase t)
{
    
    
    for (int row = 0; row < t.rowCount; row++)
    {
    
    
        for (int col = 0; col < t.colCount; col++)
        {
    
    
            if (t.blockArr[row, col] != BlockType.Empty)
            {
    
    
                int map_rowIndex = row + t.rowIndex;
                int map_colIndex = col + t.colIndex;
                ChangeType(map_rowIndex, map_colIndex, BlockType.Static);
            }
        }
    }
}

GameManager call

if (!currentTetris.IsCanMove(Direction.Down))
{
    
    
    //设置不可移动类型
    MapManager.Instance.SetStatic(currentTetris);
}

running result
Insert image description here

Check whether the current block can move in the specified direction

Solve the problem of overlapping blocks falling

Modify the IsCanMove method of Tetris base class TetrisBase

Traverse each cell of the current block. If the cell is not empty and the corresponding nearest map cell is static (that is, already occupied by other blocks), return false to indicate that it cannot be moved, otherwise return true to indicate that it can be moved.

//是否能移动的方向
public virtual bool IsCanMove(Direction dir)
{
    
    
    int _rowIndex = rowIndex;
    int _colIndex = colIndex;
    switch (dir)
    {
    
    
        case Direction.None:
            break;
        case Direction.Right:
            _colIndex++;
            break;
        case Direction.Left:
            _colIndex--;
            break;
        case Direction.Down:
            _rowIndex++;
            break;
    }

    //超出网格
    if (_colIndex < 0 || _colIndex + colCount > Defines.ColCount || _rowIndex + rowCount > Defines.RowCount)
    {
    
    
        return false;
    }
	//检查当前方块是否可以向指定方向移动
    for (int row = 0; row < rowCount; row++)
    {
    
    
        for (int col = 0; col < colCount; col++)
        {
    
    
            if (blockArr[row, col] != BlockType.Empty)
            {
    
    
                int map_rowIndex = _rowIndex + row;
                int map_colIndex = _colIndex + col;
                Block b = MapManager.Instance.blockArr[map_rowIndex, map_colIndex];
                if (b.type == BlockType.Static)
                {
    
    
                    return false;
                }
            }
        }
    }
    return true;
}

Effect
Insert image description here

rotation logic

TetrisBase Tetris base class adds new control rotation method

//旋转
public void Rotate()
{
    
    
    //二维数组互换
    int new_rowCount = colCount;
    int new_colCount = rowCount;
    //互换行列后是否超出网格
    if (rowIndex + new_rowCount > Defines.RowCount || colIndex + new_colCount > Defines.ColCount) return;
    BlockType[,] tempArr = new BlockType[new_rowCount, new_colCount];
    for (int row = 0; row < new_rowCount; row++)
    {
    
    
        for (int col = 0; col < new_colCount; col++)
        {
    
    
            tempArr[row, col] = blockArr[new_colCount - 1 - col, row];
            if (tempArr[row, col] != BlockType.Empty)
            {
    
    
                //对应位置是静态类型static不能旋转
                if (MapManager.Instance.blockArr[row + this.rowIndex, col + this.colIndex].type == BlockType.Static) return;
            }
        }
    }
    //擦除
    WipeMe();
    rowCount = new_rowCount;
    colCount = new_colCount;
    blockArr = tempArr;
    DrawMe();//画
}

GameManager call

if (Input.GetKeyDown(KeyCode.W)) currentTetris.Rotate();

Effect
Insert image description here

Eliminate logic

GameManager new method

# 调用
//检测删除行
CheckDelete();

//检测删除行
public void CheckDelete()
{
    
    
    //最后一行开始遍历
    for (int row = Defines.RowCount - 1; row >= 0; row--)
    {
    
    
        int count = 0;//静态类型的个数
        for
        (int col = 0; col < Defines.ColCount; col++)
        {
    
    
            BlockType type = MapManager.Instance.blockArr[row, col].type;
            if (type == BlockType.Static)
                count++;
        }
        if
        (count == Defines.ColCount)
        {
    
    
            for (int dropRow = row; dropRow > 1; dropRow--)
            {
    
    
                for (int dropCol = 0; dropCol < Defines.ColCount; dropCol++)
                {
    
    
                    //上一行类型覆盖当前行
                    BlockType type = MapManager.Instance.blockArr[dropRow - 1, dropCol].type;
                    MapManager.Instance.ChangeType(dropRow, dropCol, type);
                }
            }
            row++;
        }
    }
}

Effect
Insert image description here

game over logic

Modify GameManager code

bool isStop = false;//游戏结束标识

//开始游戏
void OnStartGame()
{
    
    
    isStop = false;
}

void Update()
{
    
    
    if (isStop == true)
    {
    
    
        return;
    }
}

//当前俄罗斯生成的时候对应位置是不可移动(覆盖)说明游戏结束
public bool IsGameOver()
{
    
    
    for (int row = 0; row < currentTetris.rowCount; row++)
    {
    
    
        for (int col = 0; col < currentTetris.colCount; col++)
        {
    
    
            BlockType type = currentTetris.blockArr[row, col];
            if (type != BlockType.Empty)
            {
    
    
                int map_rowIndex = row + currentTetris.rowIndex;
                int map_colIndex = col + currentTetris.colIndex;
                if (MapManager.Instance.blockArr[map_rowIndex, map_colIndex].type == BlockType.Static) return true;
            }
        }
    }
    return false;
}

transfer

//自动下落
public void AutoMoveDown()
{
    
    
    timer -= Time.deltaTime;
    if (timer <= 0)
    {
    
    
        timer = Defines.downTime;
        if (currentTetris.IsCanMove(Direction.Down))
        {
    
    
            currentTetris.MoveByDir(Direction.Down);
        }
        else
        {
    
    
            //设置不可移动类型
            MapManager.Instance.SetStatic(currentTetris);
            //检测删除行
            CheckDelete();
            //不能移动重新创建
            currentTetris = CreateTetris();
            if (IsGameOver() == true)
            {
    
    
                isStop = true;
                Debug.Log("game over");
                return;
            }
            currentTetris.DrawMe();
        }
    }
}

Effect
Insert image description here

Monster spawn

Modify GameManager code

//检测删除行
public void CheckDelete()
{
    
    
    //最后一行开始遍历
    for (int row = Defines.RowCount - 1; row >= 0; row--)
    {
    
    
   		//。。。
        if(count == Defines.ColCount)
        {
    
    
            for (int dropCol = 0; dropCol < Defines.ColCount; dropCol++)
            {
    
    
                //当前行生成哥布林
                SimpleFactory.CreateModel("gbl", null).transform.position = MapManager.Instance.blockArr[row, dropCol].transform.position;
            }
            //。。。
        }
    }
}

When a monster touches the boss, it will cause damage and special effects, as well as the game-ending effect, which is expanded by itself. It is also very simple, and there are blocks of different shapes.

Effect
Insert image description here

Source code

What source code do you want? Read it and learn it!

reference

【Video】https://www.bilibili.com/video/BV1Fr4y1x7mx

end

Gifts of roses, hand a fragrance! If the content of the article is helpful to you, please don't be stingy with your 点赞评论和关注feedback so that I can receive feedback as soon as possible. Your every feedback 支持is the biggest motivation for me to continue creating. Of course, if you find 存在错误something in the article 更好的解决方法, please feel free to comment and send me a private message!

Okay, I am 向宇, https://xiangyu.blog.csdn.net

A developer who has been working quietly in a small company recently started studying Unity by himself out of interest. If you encounter any problems, you are also welcome to comment and send me a private message. Although I may not necessarily know some of the problems, I will check the information from all parties and try to give the best suggestions. I hope to help more people who want to learn programming. People, encourage each other~
Insert image description here

Guess you like

Origin blog.csdn.net/qq_36303853/article/details/132743932