Aprende a crear videojuegos en Unity desde cero con C#. Tutoriales paso a paso, mecánicas completas, optimización de físicas y recursos gratuitos para desarrolladores."
Tetris en Unity (Parte 1): La Matriz y el Tablero Principal
Tutorial Tetris en Unity (Parte 1): La Matriz y el Tablero Principal
Cómo Crear un Juego de Tetris en Unity (Parte 1: La Matriz y el Tablero)
¡Bienvenido a esta serie de tutoriales paso a paso! En esta primera entrega aprenderemos a construir la columna vertebral de cualquier Tetris en Unity: el Tablero y la Matriz Bidimensional de datos que controla las colisiones y la eliminación de filas.
1. ¿Cómo Funciona la Lógica del Tablero en Tetris?
A diferencia de otros juegos 2D con físicas complejas, Tetris funciona como una rejilla matemática. En lugar de usar BoxCollider2D o Rigidbody2D para detectar cuándo una pieza toca el suelo, utilizamos un array bidimensional en C#:
private Transform[,] grid = new Transform[10, 30];
Cada vez que una ficha se fija en el fondo, guardamos cada uno de sus bloques individuales en las coordenadas (x, y) de la matriz. Si la casilla está vacía, su valor es null.
2. Paso a Paso: Implementando el Script `Board.cs`
A continuación te explico detalladamente la responsabilidad de cada método que vamos a programar:
Inicialización: Reservamos espacio para la rejilla y dibujamos opcionalmente sprites vacíos de fondo con DrawEmptyCells().
Validación de Posición: El método IsValidPosition() comprueba si la posición objetivo de una pieza está dentro de los límites y no choca contra bloques ya almacenados.
Almacenamiento: Con StoreShapeInGrid() guardamos las posiciones finales de los bloques hijos cuando la pieza toca suelo.
Detección y Limpieza de Filas: La corrutina ClearAllRows() recorre el tablero de abajo hacia arriba detectando filas completas, ejecutando efectos visuales y haciendo bajar el resto de las piezas.
3. Código C# Completo: `Board.cs`
Crea un nuevo script llamado Board.cs en tu proyecto de Unity y pega el siguiente código optimizado:
using System.Collections;
using UnityEngine;
public class Board : MonoBehaviour
{
private Transform[,] grid;
[Header("Efectos Visuales")]
public ParticlePlayer[] rowGlowFx = new ParticlePlayer[4];
public Transform emptySprite;
[Header("Configuración del Tablero")]
public int height = 30;
public int width = 10;
public int header = 8; // Zona superior donde nacen las piezas
[HideInInspector]
public int completedRows = 0;
private void Awake()
{
grid = new Transform[width, height];
}
private void Start()
{
DrawEmptyCells();
}
private bool IsWithinBoard(int x, int y)
{
return x >= 0 && x < width && y >= 0 && y < height;
}
private bool IsOccupied(int x, int y, Shape shape)
{
return grid[x, y] != null && grid[x, y].parent != shape.transform;
}
private void DrawEmptyCells()
{
if (emptySprite == null) return;
for (int y = 0; y < height - header; y++)
{
for (int x = 0; x < width; x++)
{
Transform clone = Instantiate(emptySprite, new Vector3(x, y, 0), Quaternion.identity);
clone.name = $"Board Space ({x}, {y})";
clone.parent = transform;
}
}
}
public bool IsValidPosition(Shape shape)
{
foreach (Transform child in shape.transform)
{
Vector2 pos = Vectorf.Round(child.position);
if (!IsWithinBoard((int)pos.x, (int)pos.y) || IsOccupied((int)pos.x, (int)pos.y, shape))
{
return false;
}
}
return true;
}
public void StoreShapeInGrid(Shape shape)
{
if (shape == null) return;
foreach (Transform child in shape.transform)
{
Vector2 pos = Vectorf.Round(child.position);
if (IsWithinBoard((int)pos.x, (int)pos.y))
{
grid[(int)pos.x, (int)pos.y] = child;
}
}
}
public IEnumerator ClearAllRows()
{
completedRows = 0;
for (int y = 0; y < height; y++)
{
if (IsComplete(y))
{
ClearRowFx(completedRows, y);
completedRows++;
}
}
if (completedRows > 0) yield return new WaitForSeconds(0.25f);
for (int y = 0; y < height; y++)
{
if (IsComplete(y))
{
ClearRow(y);
ShiftRowsDown(y + 1);
yield return new WaitForSeconds(0.15f);
y--;
}
}
}
private bool IsComplete(int y)
{
for (int x = 0; x < width; x++)
{
if (grid[x, y] == null) return false;
}
return true;
}
private void ClearRow(int y)
{
for (int x = 0; x < width; x++)
{
if (grid[x, y] != null)
{
Destroy(grid[x, y].gameObject);
grid[x, y] = null;
}
}
}
private void ShiftRowsDown(int startY)
{
for (int i = startY; i < height; i++)
{
for (int x = 0; x < width; x++)
{
if (grid[x, i] != null)
{
grid[x, i - 1] = grid[x, i];
grid[x, i] = null;
grid[x, i - 1].position += new Vector3(0, -1, 0);
}
}
}
}
private void ClearRowFx(int idx, int y)
{
if (idx < rowGlowFx.Length && rowGlowFx[idx] != null)
{
rowGlowFx[idx].transform.position = new Vector3(0, y, -2);
rowGlowFx[idx].Play();
}
}
public bool IsOverLimit(Shape shape)
{
foreach (Transform child in shape.transform)
{
if (child.transform.position.y >= height - header - 1) return true;
}
return false;
}
}
Siguiente Lección
En el siguiente tutorial aprenderemos a programar las piezas (`Shape.cs`), implementar la rotación y la proyección translúcida de caída (`Ghost.cs`).
Deja un comentario