How to Make Video Games 12

How to Make TetrisIn this part of my How to Make Video Games Tutorial series we finish Tetris!!! We add sounds, increase game difficulty, create a game over scene, display the next Tetris shape before it is displayed and much much more.

Like always all of the code follows the video below. The images and sounds are here. The next game I’ll be making will be a Super Mario Clone! I’ll start uploading it this Saturday.

Download all of my Unity game files so far for Pong, Space Invaders, Tetris and Mario

If you like videos like this consider donating $1, or simply turn off AdBlock. Either helps a lot!

[googleplusone]

Code From the Video

GameBoard.cs

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

using UnityEngine.UI;

// Surround all GameObjects with GameObjectGroup
// and move it the amount of space to make the 
// gameboard lie at the 0 0 mark

public class GameBoard : MonoBehaviour {

	public static Transform[,] gameBoard = new Transform[10, 20];

	public static void PrintArray(){

		string arrayOutput = "";

		int iMax = gameBoard.GetLength (0) - 1;
		int jMax = gameBoard.GetLength (1) - 1;

		for (int j = jMax; j >= 0; j--) {

			for (int i = 0; i <= iMax; i++) {

				if (gameBoard [i, j] == null) {
					arrayOutput += "N ";
				} else {
					arrayOutput += "X ";
				}
					
			}

			arrayOutput += "\n \n";

		}

		var myArrayComp = GameObject.Find("MyArray").GetComponent<Text>();

		myArrayComp.text = arrayOutput;

		// Debug.Log (arrayOutput);

	}

	// ---------- NEW STUFF ----------

	public static bool DeleteAllFullRows(){

		// Cycle through all rows 
		for (int row = 0; row < 20; ++row) {

			// Check for a full row
			if (IsRowFull (row)) {

				// Delete the row
				DeleteGBRow (row);

				// Play the sound
				SoundManager.Instance.PlayOneShot (SoundManager.Instance.rowDelete);

				return true;
			}
				
		}
		return false;

	}

	// This test is done in a 2nd function because it 
	// answers a specific question being is a row full
	public static bool IsRowFull(int row){

		// Cycle through each column in the row
		for (int col = 0; col < 10; ++col) {

			// If any nulls are found return false
			if (gameBoard [col, row] == null) {
				return false;
			}
		}
		return true;
	}

	public static void DeleteGBRow(int row){

		// Cycle through row deleting in both the array
		// as well as in the scene
		for (int col = 0; col < 10; ++col) {

			// Destroy the cubes in the scene
			Destroy (gameBoard [col, row].gameObject);

			// Destroy the cubes in the array
			gameBoard [col, row] = null;
		}

		// Increment up a row to start moving them down
		row++;

		// Cycle through all rows
		for (int j = row; j < 20; ++j) {

			// Cycle through all columns
			for (int col = 0; col < 10; ++col) {

				// Check if there is a block in a cell
				if (gameBoard [col, j] != null) {

					// Move whats above down
					gameBoard [col, j - 1] = gameBoard [col, j];

					// Delete the cube that was moved down
					gameBoard [col, j] = null;

					// Move the cube in the scene as well
					gameBoard[col, j-1].position += new Vector3(0, -1, 0);

				}
			}
		}
	}

	// ---------- END OF NEW STUFF ----------

}

Shape.cs

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

// Needed to edit text UI components
using UnityEngine.UI;

// NEW STUFF
// Used to switch scenes
using UnityEngine.SceneManagement;

public class Shape : MonoBehaviour {

	// ---------- NEW STUFF ----------
	// Used to slowly increase shape fall speed
	public static float speed = 1;

	// ---------- END OF NEW STUFF ----------

	// Tracks the last time the shape moved down
	float lastMoveDown = 0;

	// ---------- NEW STUFF ----------
	// Create a new Scene in the Scenes folder named GameOver
	// Add a Text Component
	// Set Canvas Render Mode to World Space -> Drag in Camera -> 
	// Dynamic Pixels Per Unit 10 -> 0 0 Position
	// Width 40 -> Height 30
	// Drag Text component to center of camera scale to .3

	// Add GameOver Scene to Build Settings 
	// File -> Build Settings

	void Start () {

		// If the Shape starts outside of the grid which
		// would happen if it hits the grid switch to the
		// GameOver scene
		if (!IsInGrid ()) {

			// Play Game Over sound
			SoundManager.Instance.PlayOneShot (SoundManager.Instance.gameOver);

			// Call function OpenNextScene after 2 seconds
			Invoke("OpenGameOverScene", .5f);
		}

		// After 2 seconds increase shape speed every 2 seconds
		InvokeRepeating("IncreaseSpeed", 2.0f, 2.0f);

	}
		
	void OpenGameOverScene () {
		// Destroy the Shape
		Destroy(gameObject);

		// Load defined scene
		SceneManager.LoadScene ("GameOver");

	}

	void IncreaseSpeed(){
		Shape.speed -= .001f;
	}
	// ---------- END OF NEW STUFF ----------


	void Update() {

			// Move Left
			if (Input.GetKeyDown ("a")) {
				// Modify position
				transform.position += new Vector3 (-1, 0, 0);

				if (!IsInGrid ()) {
					// Revert to previous
					transform.position += new Vector3 (1, 0, 0);
				} else {
					UpdateGameBoard ();

					// NEW STUFF
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.shapeMove);
				}

			}

			if (Input.GetKeyDown ("d")) {
				transform.position += new Vector3 (1, 0, 0);

				if (!IsInGrid ()) {
					transform.position += new Vector3 (-1, 0, 0);
				} else {
					UpdateGameBoard ();

					// NEW STUFF
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.shapeMove);
				}
			}

			// Move Shape down when s is pressed, or once every second
			// First time through Time.time will have a value of 1 and
			// then 2, etc. 
			// lastMoveDown will increment each time through as well

			// NEW STUFF
			// Change speed to Shape.speed to increase speed over time
			if (Input.GetKeyDown ("s") || Time.time - lastMoveDown >= Shape.speed) {
				transform.position += new Vector3 (0, -1, 0);

				if (!IsInGrid ()) {
					transform.position += new Vector3 (0, 1, 0);

					// ---------- NEW STUFF ----------

					// Delete full rows if any exist
					bool rowDeleted = GameBoard.DeleteAllFullRows ();

					// If a row was deleted verify that no other rows
					// exist
					if (rowDeleted) {
						GameBoard.DeleteAllFullRows ();

						increaseTextUIScore ();
					}

					// ---------- END OF NEW STUFF ----------

					// If I can't go down then disable shape
					enabled = false;

					// Spawn a new Shape
					FindObjectOfType<ShapeSpawner> ().SpawnShape ();

					// NEW STUFF
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.shapeStop);

				} else {
					UpdateGameBoard ();

					// ---------- NEW STUFF ----------
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.shapeMove);
				}

				// Reset lastMoveDown
				lastMoveDown = Time.time;

				// ---------- END OF NEW STUFF ----------
			}
			
			// Add another rotate option Edit -> Project Settings
			// Input -> Add e

			if (Input.GetKeyDown ("e")) {
				transform.Rotate (0, 0, -90);

				if (!IsInGrid ()) {
					transform.Rotate (0, 0, 90);
				} else {
					UpdateGameBoard ();

					// NEW STUFF
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.rotateSound);
				}
			}
			
			if (Input.GetKeyDown ("w")) {
				transform.Rotate (0, 0, 90);

				if (!IsInGrid ()) {
					transform.Rotate (0, 0, -90);
				} else {
					UpdateGameBoard ();

					// NEW STUFF
					// Play the sound
					SoundManager.Instance.PlayOneShot (SoundManager.Instance.rotateSound);
				}
			}
	}

	public bool IsInGrid(){
		foreach (Transform childBlock in transform) {

			Vector2 vect = RoundVector(childBlock.position);

			if (!IsInBorder(vect)) {
				return false;
			}
	
			// Checks to see if empty cells are available for
			// our shape
			if (GameBoard.gameBoard [(int)vect.x, (int)vect.y] != null &&
				GameBoard.gameBoard [(int)vect.x, (int)vect.y].parent != transform) 
			{
				return false;
			}

		}
		return true;
	}

	// Rounds vectors up
	public Vector2 RoundVector(Vector2 vect){
		return new Vector2 (Mathf.Round (vect.x), 
			Mathf.Round (vect.y));
	}

	// Increase from 8 to 9 to accomodate rounding
	public static bool IsInBorder(Vector2 pos) {
		return ((int)pos.x >= 0 &&
			(int)pos.x <= 9 &&
			(int)pos.y >= 0);
	}

	// Add else UpdateGameBoard to all shape movement blocks above

	public void UpdateGameBoard(){

		for (int y = 0; y < 20; ++y) {

			for (int x = 0; x < 10; ++x) {

				// If the 1st isn't null that means there
				// is a cube at that position
				// Then we check if the shape passed in
				// is already there
				if (GameBoard.gameBoard [x, y] != null &&
					GameBoard.gameBoard[x, y].parent == transform){

					// If the shape moves down then we want
					// to remove it from the gameBoard array
					GameBoard.gameBoard[x, y] = null;
				}
			}

		}

		// Iterate over all spaces on our gameboard
		// and add the new cubes for out shape
		foreach (Transform childBlock in transform) {

			// Shorten our position object
			// Vector2 pos = childBlock.position;

			Vector2 vect = RoundVector(childBlock.position);

			// Put our cube in the gameboard array
			GameBoard.gameBoard [(int)vect.x, (int)vect.y] = childBlock;
		}

		// Don't need this when testing is over
		// Uncheck MyArray in Inspector to hide it 
		// or delete it all together
		// GameBoard.PrintArray ();

	}

	// Increases the score the text UI 
	void increaseTextUIScore(){

		// Find the matching text UI component
		var textUIComp = GameObject.Find("Score")
			.GetComponent<Text>();

		// Get the string stored in it and convert to an int
		int score = int.Parse(textUIComp.text);

		// Increment the score
		score++;

		// Convert the score to a string and update the UI
		textUIComp.text = score.ToString();
	}

}

ShapeSpawner.cs

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

public class ShapeSpawner : MonoBehaviour {

	// Array that holds all the shapes
	public GameObject[] shapes;

	// ---------- NEW STUFF ----------

	// Array that holds all NextShape Sprites
	public GameObject[] nextShapes;

	// Holds a reference to the game object
	// that represents 
	GameObject upNextObject = null;

	int shapeIndex = 0;
	int nextShapeIndex = 0;

	public void SpawnShape(){

		// Generate a random index
		shapeIndex = nextShapeIndex;

		// Create the shape at the ShapeSpawners location
		Instantiate (shapes [shapeIndex],
			transform.position,
			Quaternion.identity);

		// Grab IShape and other shapes and drag them into the scene
		// rename them with a NS at the bottom -> Drag to Prefabs
		// Remove the Shape script -> Drag into the nextShapes array
		nextShapeIndex = Random.Range (0, 6);

		// Define where up next shape is positioned
		Vector3 nextShapePos = new Vector3 (-7.7f, 16.5f, 0);

		// Destory next shape sprite if it exists
		if (upNextObject != null)
			Destroy (upNextObject);

		// Get the next shape up and display it
		upNextObject = Instantiate (nextShapes [nextShapeIndex], 
			nextShapePos,
			Quaternion.identity);
	}

	// Use this for initialization
	void Start () {

		// Generate a random next shape
		nextShapeIndex = Random.Range (0, 6);

		// Spawn the default first shape
		SpawnShape();

	}
	
	// Update is called once per frame
	void Update () {
		
	}
}

SoundManager.cs

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

// Create -> Audio -> Audio Source in Hierarchy
// Name it SoundManager
// Drag SoundManager.cs to SoundManager GameObject
// Drag sounds into Sounds folder
// Drag sounds to AudioClips below in the Inspector

public class SoundManager : MonoBehaviour {

	// Holds the single instance of the SoundManager that
	// you can access from any script
	public static SoundManager Instance = null;

	// All sound effects in the game
	// All are public so you can set them in the Inspector
	public AudioClip rotateSound;
	public AudioClip rowDelete;
	public AudioClip shapeMove;
	public AudioClip shapeStop;
	public AudioClip gameOver;

	// Refers to the audio source added to the SoundManager
	// to play sound effects
	private AudioSource soundEffectAudio;

	// Use this for initialization
	void Start() {

		// This is a singleton that makes sure you only
		// ever have one Sound Manager
		// If there is any other Sound Manager created destroy it
		if (Instance == null) {
			Instance = this;
		} else if (Instance != this) {
			Destroy (gameObject);
		}

		AudioSource theSource = GetComponent<AudioSource> ();
		soundEffectAudio = theSource;
	}

	// Other GameObjects can call this to play sounds
	public void PlayOneShot(AudioClip clip) {
		soundEffectAudio.PlayOneShot(clip);
	}
}

Leave a Reply

Your email address will not be published. Required fields are marked *