In 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);
}
}