2D Tetris Game Tutorial – Unity3D (C#)

Play Demo

Do you remember the most famous single player game of early 90s? Yes, it’s Tetris. Developed by a Russian engineer, Aleksey Pajitnov, in 1985. Tetris is the 6th most played game ever. We will make our Tetris game in this tutorial.

The goal is to collect moving downward blocks at bottom with no gap in 2D. Game ends when there is no available area for new block.

Before start coding, please create or download your block texture (square) and wall texture. You can find the on google or download images below.

Open a 2D project in Unity3D and import these images. Cube images size is 32×32, so please edit Pixel Per Unit as 32 which means 32 pixels will be placed in 1 game unit. We need 7 different game object for blocks. Every block is created by 4 independent cubes because we will delete cubes separately when a row is full, not whole the object. By the way, we need J, T, S, Z, L, I, O-shaped blocks as you know.

To create blocks, create an empty game object named Block_J and create 4 cubes from cube image. Assume that there is a 4×4 square and Block_J is at the center (0, 0). Place cube objects in this 4×4 area. Be careful, you must placed cubes on squares perfectly to get best fit on game. You can see an example below.

Place all cubes in block objects as seen as picture below. Create prefabs (create a Prefabs folder under Assets in Project window and drag blocks) and delete blocks in scene.

We will check available area and restricted grid for cube movement, so you don’t need to add 2D collider to boxes and walls.

Create a C# script name SpawnBox and write code below.

public class SpawnBox : MonoBehaviour { public GameObject[] boxList; void Start () { SpawnNewBox(); } public void SpawnNewBox() { int i = Random.Range(0, boxList.Length); Instantiate(boxList[i], transform.position, Quaternion.identity); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 public class SpawnBox : MonoBehaviour { public GameObject [ ] boxList ; void Start ( ) { SpawnNewBox ( ) ; } public void SpawnNewBox ( ) { int i = Random . Range ( 0 , boxList . Length ) ; Instantiate ( boxList [ i ] , transform . position , Quaternion . identity ) ; } }

Create an empty game object and attach this code. New block will be created with position of empty game object. You can change position of empty game object where you want to create blocks. Drag your 7 prefabs to boxList.

Create another C# script named Boxes. Just remember the game rules, blocks move in grid(like snake but there are limits in this tutorial), we can move and rotate blocks, when we fill any row with full of cubes, that row should be deleted. This script will control valid positions, grid updates, rotate round and row actions.

Lets write some code for grid and row operations.

public static int gridWeight = 20; public static int gridHeight = 40; public static Transform[,] grid = new Transform[gridWeight, gridHeight]; public static Vector2 round(Vector2 v) { return new Vector2(Mathf.Round(v.x), Mathf.Round(v.y)); } public static bool isInsideGrid(Vector2 pos) { return ((int)pos.x >= 0 && (int)pos.x < gridWeight && (int)pos.y >= 0); } public static void Delete(int y) { for (int x = 0; x < gridWeight; ++x) { Destroy(grid[x, y].gameObject); grid[x, y] = null; } } public static bool isFull(int y) { for (int x = 0; x < gridWeight; ++x) if (grid[x, y] == null) return false; return true; } public static void DeleteRow() { for (int y = 0; y < gridHeight; ++y) { if (isFull(y)) { Delete(y); RowDownAll(y+1); --y; } } } public static void RowDown(int y) { for (int x = 0; x < gridWeight; ++x) { if (grid[x, y] != null) { grid[x, y-1] = grid[x, y]; grid[x, y] = null; grid[x, y-1].position += new Vector3(0, -1, 0); } } } public static void RowDownAll(int y) { for (int i = y; i < gridHeight; ++i) RowDown(i); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public static int gridWeight = 20 ; public static int gridHeight = 40 ; public static Transform [ , ] grid = new Transform [ gridWeight , gridHeight ] ; public static Vector2 round ( Vector2 v ) { return new Vector2 ( Mathf . Round ( v . x ) , Mathf . Round ( v . y ) ) ; } public static bool isInsideGrid ( Vector2 pos ) { return ( ( int ) pos . x & gt ; = 0 & amp ; & amp ; ( int ) pos . x & lt ; gridWeight & amp ; & amp ; ( int ) pos . y & gt ; = 0 ) ; } public static void Delete ( int y ) { for ( int x = 0 ; x & lt ; gridWeight ; ++ x ) { Destroy ( grid [ x , y ] . gameObject ) ; grid [ x , y ] = null ; } } public static bool isFull ( int y ) { for ( int x = 0 ; x & lt ; gridWeight ; ++ x ) if ( grid [ x , y ] == null ) return false ; return true ; } public static void DeleteRow ( ) { for ( int y = 0 ; y & lt ; gridHeight ; ++ y ) { if ( isFull ( y ) ) { Delete ( y ) ; RowDownAll ( y + 1 ) ; -- y ; } } } public static void RowDown ( int y ) { for ( int x = 0 ; x & lt ; gridWeight ; ++ x ) { if ( grid [ x , y ] != null ) { grid [ x , y - 1 ] = grid [ x , y ] ; grid [ x , y ] = null ; grid [ x , y - 1 ] . position += new Vector3 ( 0 , - 1 , 0 ) ; } } } public static void RowDownAll ( int y ) { for ( int i = y ; i & lt ; gridHeight ; ++ i ) RowDown ( i ) ; }

Now, write some code for control buttons and to check valid position.

float fall = 0; void Start () { if (!isValidPosition()) { Application.LoadLevel(0); Destroy(gameObject); } } void Update() { if (Input.GetKeyDown(KeyCode.RightArrow)) { transform.position += new Vector3(1, 0, 0); if (isValidPosition()) GridUpdate(); else transform.position += new Vector3(-1, 0, 0); } else if (Input.GetKeyDown(KeyCode.LeftArrow)) { transform.position += new Vector3(-1, 0, 0); if (isValidPosition()) GridUpdate(); else transform.position += new Vector3(1, 0, 0); } else if (Input.GetKeyDown(KeyCode.UpArrow)) { transform.Rotate(0, 0, -90); if (isValidPosition()) GridUpdate(); else transform.Rotate(0, 0, 90); } else if (Input.GetKeyDown(KeyCode.DownArrow) || Time.time - fall >= 1) { transform.position += new Vector3(0, -1, 0); if (isValidPosition()) { GridUpdate(); } else { transform.position += new Vector3(0, 1, 0); DeleteRow(); FindObjectOfType().SpawnNewBox(); enabled = false; } fall = Time.time; } } bool isValidPosition() { foreach (Transform child in transform) { Vector2 v = round(child.position); if (!isInsideGrid(v)) return false; if (grid[(int)v.x, (int)v.y] != null && grid[(int)v.x, (int)v.y].parent != transform) return false; } return true; } void GridUpdate() { for (int y = 0; y < gridHeight; ++y) for (int x = 0; x < gridWeight; ++x) if (grid[x, y] != null) if (grid[x, y].parent == transform) grid[x, y] = null; foreach (Transform child in transform) { Vector2 v = round(child.position); grid[(int)v.x, (int)v.y] = child; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 float fall = 0 ; void Start ( ) { if ( ! isValidPosition ( ) ) { Application . LoadLevel ( 0 ) ; Destroy ( gameObject ) ; } } void Update ( ) { if ( Input . GetKeyDown ( KeyCode . RightArrow ) ) { transform . position += new Vector3 ( 1 , 0 , 0 ) ; if ( isValidPosition ( ) ) GridUpdate ( ) ; else transform . position += new Vector3 ( - 1 , 0 , 0 ) ; } else if ( Input . GetKeyDown ( KeyCode . LeftArrow ) ) { transform . position += new Vector3 ( - 1 , 0 , 0 ) ; if ( isValidPosition ( ) ) GridUpdate ( ) ; else transform . position += new Vector3 ( 1 , 0 , 0 ) ; } else if ( Input . GetKeyDown ( KeyCode . UpArrow ) ) { transform . Rotate ( 0 , 0 , - 90 ) ; if ( isValidPosition ( ) ) GridUpdate ( ) ; else transform . Rotate ( 0 , 0 , 90 ) ; } else if ( Input . GetKeyDown ( KeyCode . DownArrow ) || Time . time - fall & gt ; = 1 ) { transform . position += new Vector3 ( 0 , - 1 , 0 ) ; if ( isValidPosition ( ) ) { GridUpdate ( ) ; } else { transform . position += new Vector3 ( 0 , 1 , 0 ) ; DeleteRow ( ) ; FindObjectOfType ( ) . SpawnNewBox ( ) ; enabled = false ; } fall = Time . time ; } } bool isValidPosition ( ) { foreach ( Transform child in transform ) { Vector2 v = round ( child . position ) ; if ( ! isInsideGrid ( v ) ) return false ; if ( grid [ ( int ) v . x , ( int ) v . y ] != null & amp ; & amp ; grid [ ( int ) v . x , ( int ) v . y ] . parent != transform ) return false ; } return true ; } void GridUpdate ( ) { for ( int y = 0 ; y & lt ; gridHeight ; ++ y ) for ( int x = 0 ; x & lt ; gridWeight ; ++ x ) if ( grid [ x , y ] != null ) if ( grid [ x , y ] . parent == transform ) grid [ x , y ] = null ; foreach ( Transform child in transform ) { Vector2 v = round ( child . position ) ; grid [ ( int ) v . x , ( int ) v . y ] = child ; } }

Add Boxes script to all 7 block prefabs(select all with Shift key, add component>script>SpawnBox). You see that your blocks moves in a limited area (20×40). Put your wall borders near these horizontal points.

We use Transform grid so, we don’t need transform.position for blocks. It provides 1 unit move in 1 second. You can change it to change speed of blocks. Also, you can add score, time or colorful blocks in your game.

You can download source files here.

©Coffee Break Codes – 2D Tetris Game Tutorial – Unity3D (C#)