Assignment: | Recreate a retro game and give it a twist |
Duration: | 2 months |
Team size: | Solo project |
Role: | Development |
Skills: | ![]() ![]() |
Links: | ![]() |
For this game I created a unique twist to one of my favourite retro games, Tetris. The twist is that the game is also a platformer. A character needs to jump on top of the tetris blocks to stay alive. The player controls both the tetris blocks and the character. It is really hard to focus on both of them. The game turned out to be really fun to play and I actually still play this game every now and then.
The challenge in creating this game was to combine the grid based gameplay of tetris with the physics based gameplay of a platformer. There are a lot of rules to consider about how the falling blocks interact with other blocks, enemies and the player. Tetris is designed in a really clever way and you can break the gameplay really fast if you implement new mechanics in a wrong way.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Block : MonoBehaviour {
private Collectable collectable;
private Grid grid;
// Set colour of block
public void SetColour (Color colour) {
transform.GetComponent<SpriteRenderer>().color = colour;
}
// Set grid referance of block
public void SetGrid (Grid newGrid) {
grid = newGrid;
}
// Add collectable to Block
public bool AddCollectable(Collectable newCollactable) {
if (!collectable) {
collectable = newCollactable;
collectable.transform.parent = transform;
collectable.transform.localPosition = new Vector3(0, 0, -1);
return true;
}
return false;
}
// Destroy block and release collectables
public void Destroy () {
if (collectable != null) {
int column = grid.GetLocalPosition(transform.position)[0];
int row = grid.getHeighestAvailableCellInColumn(column);
collectable.transform.parent = grid.transform;
collectable.transform.localPosition = new Vector3(column, row, -1);
collectable.SetActive(true);
}
Destroy(gameObject);
}
// Gets called every time a block moves
public void CheckCharacterCollision() {
Collider2D characterCollider = Physics2D.OverlapBox(transform.position, transform.lossyScale, 0f, LayerMask.GetMask("Character"));
if (characterCollider) {
Vector2 characterPosition = characterCollider.transform.position;
if (Mathf.Abs(characterPosition.x - transform.position.x) < .158f && characterPosition.y - transform.position.y < .158f && characterPosition.y - transform.position.y > -.63f) {
characterCollider.GetComponent<ICharacterController>().HitByBlock();
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tetromino {
private Grid grid;
public enum TetrominoShape { I, J, L, O, S, T, Z };
public TetrominoShape shape;
private Color colour;
public int[,] blockPositions;
private int rotation;
private Collectable[] collectables = new Collectable[4];
// Constructor
public Tetromino () {
shape = (TetrominoShape)Random.Range(0, 6);
switch (shape) {
case TetrominoShape.I:
colour = Color.red;
break;
case TetrominoShape.J:
colour = Color.blue;
break;
case TetrominoShape.L:
colour = new Color(1, .5f, 0);
break;
case TetrominoShape.O:
colour = Color.yellow;
break;
case TetrominoShape.S:
colour = Color.magenta;
break;
case TetrominoShape.T:
colour = Color.cyan;
break;
case TetrominoShape.Z:
colour = Color.green;
break;
}
rotation = 0;
}
// Adds collactable to tetronimo list. When it is added to the grid it will be added to the newly created block
public bool AddCollectable (Collectable newCollectable) {
for (int i = 0; i < 4; i++) {
if (collectables[i] == null) {
collectables[i] = newCollectable;
return true;
}
}
return false;
}
// Add this tetromino to a grid. New Blocks will be constructed.
public bool AddToGrid (Grid _grid) {
grid = _grid;
blockPositions = GetBlockPositions();
for (int i = 0; i < 4; i++) {
bool added = grid.AddBlock(blockPositions[i, 0], blockPositions[i, 1], colour);
if (!added) {
return false;
}
// Add collectables to blocks
if (collectables[i]) {
grid.GetBlock(blockPositions[i, 0], blockPositions[i, 1]).AddCollectable(collectables[i]);
collectables[i].SetActive(false);
}
}
return true;
}
// Public move function
public bool Move (int xOffset, int yOffset) {
int[,] targetPositions = (int[,])blockPositions.Clone();
for (int i = 0; i < 4; i++) {
targetPositions[i, 0] += xOffset;
targetPositions[i, 1] += yOffset;
}
bool tetrominoCanMove = ((GameGrid) grid).TransformTetromino(blockPositions, targetPositions);
if (tetrominoCanMove) {
blockPositions = targetPositions;
}
return tetrominoCanMove;
}
// Public rotate function
public bool Rotate () {
//O can not rotate
if (shape == TetrominoShape.O) {
return true;
}
//calculate new positions for rotated tetromino
int[,] targetPositions = new int[4, 2];
int[,] relativePosition = new int[4, 2];
int rotationBlock = GetRotationBlock();
for (int i = 0; i < 4; i++) {
relativePosition[i, 0] = blockPositions[i, 0] - blockPositions[rotationBlock, 0];
relativePosition[i, 1] = blockPositions[i, 1] - blockPositions[rotationBlock, 1];
targetPositions[i, 0] = blockPositions[rotationBlock, 0] + relativePosition[i, 1];
targetPositions[i, 1] = blockPositions[rotationBlock, 1] - relativePosition[i, 0];
}
//I gets repositioned on rotation
if (shape == TetrominoShape.I) {
if (rotation % 2 == 1) {
for (int i = 0; i < 4; i++) {
targetPositions[i, 0]--;
}
}
}
bool tetrominoCanMove = ((GameGrid)grid).TransformTetromino(blockPositions, targetPositions);
if (tetrominoCanMove) {
rotation = ++rotation % 4;
blockPositions = targetPositions;
}
return tetrominoCanMove;
}
// Gets called on initialisation.
private int[,] GetBlockPositions() {
int[,] positions = new int[,] { };
switch (shape) {
case TetrominoShape.I:
positions = new int[,] { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 } };
break;
case TetrominoShape.J:
positions = new int[,] { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 1, -1 } };
break;
case TetrominoShape.L:
positions = new int[,] { { -1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 } };
break;
case TetrominoShape.O:
positions = new int[,] { { 0, 0 }, { 0, -1 }, { 1, 0 }, { 1, -1 } };
break;
case TetrominoShape.S:
positions = new int[,] { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 1, 0 } };
break;
case TetrominoShape.T:
positions = new int[,] { { -1, 0 }, { 0, 0 }, { 0, -1 }, { 1, 0 } };
break;
case TetrominoShape.Z:
positions = new int[,] { { -1, 0 }, { 0, 0 }, { 0, -1 }, { 1, -1 } };
break;
}
for (int i = 0; i < 4; i++) {
positions[i, 0] += grid.spawnPosition[0];
positions[i, 1] += grid.spawnPosition[1];
}
return positions;
}
// Get anchor block for tetromino rotation, Tetrominos can't always rotate around its center
// Returns the index of the block to rotate around
private int GetRotationBlock() {
switch (shape) {
case TetrominoShape.I:
if (rotation < 2) return 2;
return 1;
case TetrominoShape.J:
return 1;
case TetrominoShape.L:
return 2;
case TetrominoShape.S:
if (rotation < 2) return 1;
return 2;
case TetrominoShape.T:
return 1;
case TetrominoShape.Z:
if (rotation < 2) return 1;
return 2;
}
return 0;
}
}
![]() |
contact@rubenbimmel.nl | ![]() |
![]() |
Artstation | ![]() |
GitHub |