Good day all – in this post, we will incorporate full ‘tetrominoes’ (shapes consisting of multiple squares) into our mobile ‘Tetris’ game. We have already finished implementing much of the functionality which does this, however, some of the code which was used to power the grid system when only single blocks were used will need to be rewritten in order to accommodate the more complex shapes. Additionally, we will also extend the code which moves the blocks sideways such that a tetromino will only be moved if there are no other fixed blocks in the path.
The final product of this post can be downloaded here.
Staying true to the original Tetris, our version will use the same tetromino shapes which the classic version used – as shown below:
In order to create these tetromino objects, we first need to create the ‘materials’ – these are Unity’s method of storing the ways 2D graphics are mapped onto 3D objects (note that we are using 3D cubes in our game, and they are made to appear 2D by the camera’s orthographic projection). In this case, creating the required materials is relatively simple, as each tetromino is a single solid colour. To create a new material, right-click the project directory, move to the ‘create’ drop-down menu and choose the ‘material’ option. This new material’s properties will then appear the in the inspector tab – from here, we switch the material’s type to ‘Unlit/Color’. We can then select the colour of the material using a hue/saturation grid by clicking on the sole ‘main colour option’. There are seven different tetromino colours used in the original Tetris, so we will create seven different materials.
Now that we have created the required materials, we can use the Unity editor to construct the actual tetromino prefabs – this can be done by dragging a copy of the ‘SingleBlock’ prefab created in the previous post into the scene tab, duplicating it to make multiple separate blocks and then using the level editing tools to create the desired shape. A material can then be dragged from the project tab onto a block in the scene view in order for the material to be applied to the block. The Unity editor boasts a useful feature called ‘vertex snapping’ – if the ‘V’ key is held, a vertex on a single game object can be selected, and then dragged onto a vertex of another object such that the two vertices occupy the same position in the world space. This feature can thus be used to position two cubes next to each other perfectly.
The level-editing features built into the Unity engine can be used to build replicas of every possible tetromino shape present in the original game (we only need to build a tetromino for every single colour we intend to use, as we will implement a block rotation mechanic later on). In order to convert a group of blocks into a single tetromino, select all of the blocks from a tetromino except one in the hierarchy and then drag the selected blocks onto the unselected block (effectively making the unselected block the ‘parent’ of the selected blocks). The model in the hierarchy will then appear as follows:
The parent block of a tetromino can then be dragged into the project tab in order for a prefab to be created.
Once we have created the tetromino prefabs, we will need to alter the code we wrote in the previous posts such that it accommodates shapes consisting of multiple blocks. In the last post, we designed our ‘Tick ()’ function such that it processed every single falling block from a list of all falling blocks – so our first course of action now will be to create a new script for our tetromino models. This script should get attached to the parent blocks (as this makes accessing the child blocks relatively simple). In the ‘Start ()’ function of this script (the function which gets called as the object is instantiated), we first obtain access to the ‘MainControl’ script which we wrote in earlier posts. We also save every single block of the tetromino in question into a list of GameObjects using a ‘while’ loop – as each block is saved, it has its initial position saved into the grid array and is separated from the parent block.
Given the manner in which the ‘Tick ()’ function works (it iterates through a list of moving blocks and processes the movement of each block in turn), it is necessary for us to ensure that the blocks saved in the list of moving blocks are ordered by their positions along the y axis, with the lowest blocks saved into the lowest array indices. This is because the ‘Tick ()’ functions sets the grid value referenced by a given object’s current position to ‘null’ (which effectively states that the variable references nothing) before moving the block downwards and updating the grid value referenced by the block’s new position. Therefore, if the blocks at the top of a tetromino (that is, a group of moving blocks) had their movement processed first, there is a high risk of their corresponding grid values being incorrectly reset to ‘null’ when the lower blocks are processed (since all blocks are moved downwards, this is guaranteed to happen if one block is one unit above another block, and is moved downwards first). This would cause blocks to appear to ‘fall through’ other blocks, since the values stored in the grid array are not representative of what is actually being shown on the screen. We can avoid this issue by making sure that the list of moving blocks in MainControl is always sorted appropriately. Since tetrominoes are relatively small – that is, they only consist of several block objects each, a simple selection sorting algorithm can be used to create a new, sorted list in the script attached to a tetromino’s parent block. Moreover, since this script has access to MainControl, the value of the list of moving blocks in MainControl can be directly assigned to the sorted list in the tetromino script.
One of the other issues which has cropped up as a result of multiple blocks falling at the same time is related to the check performed during every ‘Tick ()’ call – every time the function is executed, each block in the falling block list is checked to ensure if there are any blocks under it. If this is the case, all of the blocks in the list are frozen in place and the list is cleared. This logic allows falling squares to be stacked on top of each other, however, the tetromino models do contain blocks already positioned on top of each other (for instance, the square tetromino has two rows of blocks on top of each other, with the rows containing two blocks each). The problem here is that this code causes tetrominoes to become frozen while still falling (as the stacks of blocks in the tetrominoes causes the check mentioned earlier to return true). In order to remedy this, the algorithm must be able to distinguish between blocks with are part of the current falling tetromino, and blocks which have already fallen and are already frozen in place. This can be done using Unity’s ‘tag’ system, which allows us to give each GameObject in the world a string property which can be referenced within the code. In order to create a tag, we select the GameObject, open the tag menu from the inspector window, type in the string value of the new tag and then assign this tag value to the tag of the selected GameObject. The object’s tag can then be accessed within the code using ‘gameObject.tag’ – in our solution, we can create two tags: one which states that a block is moving and another which states that a block is frozen. Newly created tetrominoes should, by default, have all of their blocks use the ‘moving’ tag (so we need to assign this tag value to every block in every tetromino prefab. Furthermore, tetrominoes which have had their falls halted will have all of their blocks’ tag values switched to the ‘frozen’ tag. The screenshot below shows the ‘tag’ menu of a block in the yellow tetromino prefab – notice that it has already had its tag value set to ‘moving’:
The following screenshot shows how the mentioned fix has been implemented in the code:
Another problem which we must tackle is accounting for the varying sizes of each tetromino object (since we are no longer working with 1×1 blocks, we run a risk of having out-of-bounds errors occur when we reference grid positions, if a tetromino is instantiated and positioned on the edges of the grid). this has been done by ensuring that the tetrominoes are not instantiated on the top row, and saving the width of each individual shape so that the value can be used to pick a suitable set of columns for each newly created set of blocks. In order to instantiate a tetromino instead of a single block, we can save all of the tetromino prefabs into an array (using the inspector) and then use ‘Random.Range ()’ to pseudo-randomly select a prefab to clone.
The following images are of the final tetromino prefab script, and the changes to the block instantiation mechanics:
It is possible for the player to create an excessive number of falling tetrominoes through aggressively hitting the ‘speed up time’ button immediately after a new tetromino is created. In order to ensure that each newly created tetromino has an opportunity to be passed through the ‘Tick ()’ function at least once, we can introduce a boolean flag which is checked whenever we attempt to create a new set of falling blocks, and is reset whenever we perform a full ‘Tick ()’ with the current set.
Finally, we will edit the functions used to move tetrominoes left and right such that they do not cause the falling blocks to move if other blocks are in the way (in the last post, the functions only prevented movement if the blocks were on the edges of the grid). This is done relatively easily – we can check the values of the cells immediately to the left of right of each falling block, and if these values are not null (that is, a block is already in that grid cell), then the function returns without the blocks being moved.
We have made great strides on this project! In the next post, we will implement the tetromino rotation mechanic.