2D Tetris Game Tutorial – Unity3D (C#)
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.
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.
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 >= 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); } |
Now, write some code for control buttons and to check valid position.
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 >= 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; } } |
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#)
nice!
There is some issues with your code. First – some blocks stacks with corners. Second – I guess you meant width no weigth in variable names
Thank you for your correction. Yes it should be width:) I will fix variable name and this stacking thing asap..
What do i attach the grid script to?
You should attach it to prefabs for grid movement..
Sorry, I meant the Boxes script. I didn’t quite understand. Should I attach it to all the “Box” GameObjects?
Yes. Attach it to all prefabs.
For some reason when I apply these codes to my parent object for the blocks, it constantly returns that my object is not within the grid would you know why that is? Whenever I get it to function (only when I am using a game object with no children as it doesn’t function otherwise) it doesn’t recognize the boundaries of the grid at all
Nevermind! Completely misread there my apologies! Thank you!
This tutorial is really hard to follow, your directions are super vague in parts. I cant get past “To create blocks, create an empty game object named Block_J and create 4 cubes from cube image.”
you don’t explain HOW to do these things. I have no idea how to “create 4 cubes from cube image”… do you mean make a new sprite outside of unity with those graphics? Do you mean make a 3D model cube? I really cannot tell what I’m supposed to do, or where I’m supposed to do it.
As you see in the pictures, all Blocks are created by combining 4 small cube images. This “cube” word doesn’t mean 3D because this tutorial isn’t 3D. You can get it as “square” if you want. Import the small cube image to Unity and just drag-drop to scene. You will see that a copy of the image will be created as a game object in scene. Do it 4 times and combine it to create a J-shaped object. After that, create an empty game object and drag these 4 objects into it. By this, you get a parent object with 4 child objects.
ok, dragging the cube sprites to the scene was the part I was unclear on. thanks for clarifying this for me!
Encountered following issue:
Assets/Scripts/Boxes.cs(100,33): error CS0411: The type arguments for method `UnityEngine.Object.FindObjectOfType()’ cannot be inferred from the usage. Try specifying the type arguments explicitly
in reference to the line: FindObjectOfType().SpawnNewBox(); in Box.cs
I’ve attached Box.cs to all prefab blocks, and I have spawnBox attached to the new game object. using Unity 5.0 am I missing anything?
nevermind, I see in attached scripts you’ve fixed. but honestly this tutorial is a mess… most times your wording isnt clear and at the end of the tutorial there is no working game, your instructions are VERY unclear throughout with the last paragraph you rush through several important things w/o detail.
by following this tutorial all I have is a screen where I can rotate 1 tetris piece and move it horizontally off screen… not even close to a game.
I feel as if this was a waste of time.
Thank you for your opinion. I’ve never shared a code that doesn’t work. All tutorials have a demo and all demos are built by these codes in Demo page. If you are looking for a full version of games and projects, this is wrong place. I only share the logic of the things.
I have to agree with Josh, at the very least I’d rename “Complete Projects”, I arrived here from Google so it was super misleading.
Im close to finishing this. What do i attach boxes script to. You never mention. Thanks
Sorry, Great tutorial. Just stuck on one thing. I downloaded the source because my scripts where getting errors.
When I apply Spawn box to new gameobject and apply all prefabs to the box list.
Works great, when played shows random shape in area chosen
But I apply boxes.cs to to prefabs and its blank when played
Any help greatly appreciated.
Sorry for misdirection. Attach “SpawnBox” to an empty game object and “Boxes” to all boxes.
Again Thanks a bunch almost got my first project done. But am not drawing anything with the boxes.cs
IndexOutOfRangeException: Array index is out of range.
SpawnBox.SpawnNewBox () (at Assets/SpawnBox.cs:16)
SpawnBox.Start () (at Assets/SpawnBox.cs:11)
Is this just user error?
When Game is run the Hierarchy says deleted gameobject
Same Problem, have you gotten a fix?
Hi Mocha,
is there a way to do this that isn’t static?
I managed to get the game working but I wanted to add a second player and since its static, the players are on top of each when I attempt to make another set.
Any ideas?
otherwise, good stuff!
Actually static methods are used in “Util” classes and you can call them from everywhere without an instance. They are independently used from object. Remove it from “Boxes” class, create a new “Util” class and put them in. Then, you can call them like Util.method(). This tutorial is single player. If you need multiplayer mode, you must create a player class which creates its own spawns, controllers, score etc. The structure must be changed.
Awesome. Ok. Thanks.
hello
i am new in Programming .. i just tried to make this game . your tutorial was really awesome and helpful.
thanks for this. and i am also looking for your some more amazing and awesome tutorials.
so i have a problem please guide me.
i wanna increase speed of blocks downward. i tried many ways but can not do so .. please me where i have to change..
“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.”
i cant understand this line please guide me . i am waiting for your reply . thanks
hello
i am new in Programming .. i just tried to make this game . your tutorial was really awesome and helpful.
thanks for this. and i am also looking for your some more amazing and awesome tutorials.
so i have a problem please guide me.
i wanna increase speed of blocks downward. i tried many ways but can not do so .. please me where i have to change..
“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.”
i cant understand this line please guide me . i am waiting for your reply . thanks
Pingback: Unity3D 玩一把 | ZhuZhuBlog
Pingback: 3 Beginner Unity 2D Tutorials - Eimear Game Studios
What >= 0 && (int)pos.x < means???
Sorry for addon typo error. < means <=. And & means &&.
I have a problem in which when I run the level it stays in a load cycle, that is, it loads the level, closes it and reloads the same level and so on
Probably it’s about isValidPosition() function. Check it if there is anything wrong.
Can you explain this line for me, please, thanks you.
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;
}
Siempre me entra en posición no válida, ¿cómo puedo solucionarlo?
Hey, I need some help! How do you make the walls and bottom? Great tutorial btw!
I need some help! How do I make the playing field?
If i start the game there are just clones unlimited blocks and doesn’t display any of them.