Tetris
Gra polega na układaniu spadających bloków (tetrominoes) w rzędy. Celem jest budowanie pełnych poziomych linii bez przerw. Każda kompletna linia jest kasowana, a punkty są naliczane. Gra kończy się, gdy bloki nagromadzą się i dotkną górnej granicy planszy, uniemożliwiając pojawienie się nowej figury.
Written By Coder Matthew
Last updated 6 months ago
1) Grafiki użyte w Tetris

1.
BorderGrafika tworząca ramkę wokół planszy.
Używany do wizualnego oddzielenia planszy Tetrisa (Tilemapy) od reszty interfejsu użytkownika lub tła.
2.
GridGrafika tła dla siatki lub pola do wyświetlania wyniku/informacji.
Używany do rysowania siatki planszy.
3.
GhostGrafika reprezentująca cień spadającej figury.
4.
Blue,Cyan,Green,Orange,Purple,Red,YellowGrafiki kolorowych kafelków dla poszczególnych Tetromino.
Są to płaskie grafiki 2D, używane do rysowania figur Tetromino w miejscach, gdzie nie da się użyć standardowych
Tiles(np. w oknie podglądu następnej figury lub jako ikony w UI).
2) Hierarchia w Unity

Main CameraOdpowiada za widok gry, czyli za wyświetlanie planszy i spadających figur. Ustawia projekcję ortograficzną, idealną dla gier 2D opartych na siatce (Tilemap).
Jest statyczna i skupiona na obszarze planszy, aby uchwycić całe pole gry Tetrisa.
GridObiekt Unity Grid (Siatka), który definiuje globalny system współrzędnych labiryntu. Jest to niezbędna struktura nadrzędna dla komponentów
Tilemap.Organizator, który zapewnia, że wszystkie
Tilemapy(plansza i cień) używają tego samego układu komórek (np. 1x1 jednostka na kafelek).
BoardJest to obiekt, do którego przypięty jest skrypt
Board, będący sercem mechaniki gry.Kontroluje generowanie nowych figur (
SpawnPiece), logikę Game Over, sprawdzanie kolizji (IsValidPosition) oraz usuwanie pełnych linii (ClearLines).
GhostReprezentuje obiekt wizualizujący miejsce, w którym figura spadnie.
Do tego obiektu przypięty jest skrypt
Ghost(cień), który śledzi aktywną figurę.Ułatwia graczowi planowanie, pokazując docelową pozycję opadającej figury.
BorderObiekt wizualny tworzący ramkę wokół pola gry. Jest to graficzny element (Sprite), który definiuje granice planszy (np. czarna ramka).
Służy celom estetycznym, wizualnie oddzielając planszę gry od reszty ekranu i interfejsu.
CanvasJest kontenerem nadrzędnym dla wszystkich elementów interfejsu użytkownika (UI). Zarządza skalowaniem i rozmieszczeniem tekstu na ekranie.
Niezbędny do hostowania tekstu wyniku (
ScoreText).
ScoreText(wewnątrz Canvas)Element tekstowy (
TextMeshPro) wyświetlający aktualny wynik gracza. Pokazuje wynik w formacie czterech cyfr.Skrypt
Board(w metodzieUpdateScore) na bieżąco aktualizuje ten obiekt.
3) Skrypty użyte w tej grze

BoardJest to centralny menedżer i kontroler całej planszy gry. Zarządza logiką czyszczenia i ustawiania bloków na
Tilemap, generowaniem nowych figur (SpawnPiece) oraz sprawdzaniem warunków Game Over.Kontroluje kluczowe mechaniki: usuwanie pełnych linii (
ClearLines) oraz walidację pozycji (IsValidPosition) każdej figury, zanim ta się przesunie lub obróci.
DataStatyczny skrypt (klasa) przechowujący wszystkie stałe dane niezbędne do działania mechaniki Tetrisa.
Zawiera definicje kształtów wszystkich bloków (
Cells).Zapewnia spójność i zgodność z oficjalnymi regułami Tetrisa, dostarczając geometryczne zasady, których używają skrypty
TetrominoDataiPiece.
GhostSkrypt tworzący wizualny cień (preview) spadającej figury, wskazujący, gdzie nastąpi Hard Drop.
Śledzi aktywną figurę (
Piece) i oblicza jej najniższą możliwą pozycję na planszy.Używa oddzielnej warstwy
Tilemapdo rysowania cienia. Logika jest uruchamiana wLateUpdate, aby cień był zawsze aktualny po wszelkich ruchach figury.
PieceSkrypt sterujący pojedynczą, aktywną, spadającą figurą (Tetromino).
Zarządza inputem gracza (ruch boczny, Soft/Hard Drop, rotacja) oraz kontroluje timery spadania i blokady.
Implementuje zaawansowaną logikę rotacji oraz
TestWallKicks, aby umożliwić obrót figury, nawet gdy jest ona zablokowana przy ścianie lub innym bloku.
TetrominoDataUżywa struktury (struct w C++, pozdrawiamy mgr Tomasza Kożaka) używana do definiowania konfiguracji każdego typu bloku.
Łączy w sobie wygląd (Tile), typ Tetromino (np. 'I', 'J', 'L') oraz odpowiednie dane geometryczne z klasy
Data.Jest używany przez skrypt
Boarddo losowania nowej figury i przekazywania wszystkich jej cech geometrycznych do aktywnego obiektuPiece.
4) Całe skrypty
Tutaj znajdziesz całe skrypty bez owijania w bawełne 😊, każdy blok kodu jest starannie zakomentowany tak żebyś w miarę mógł ogarnąć o co chodzi nawet jak nie znasz unity i C#.
Exampleusing UnityEngine;
using UnityEngine.Tilemaps;
using UnityEngine.UI;
using TMPro;
using UnityEngine.Networking;
using System.Collections;
[DefaultExecutionOrder(-1)]
public class Board : MonoBehaviour
{
// --- Komponenty Planszy i Aktywne Elementy ---
public Tilemap tilemap { get; private set; }
public Piece activePiece { get; private set; } // Aktualnie spadająca figura.
// --- Konfiguracja i Ustawienia ---
public TetrominoData[] tetrominoes; // Definicje wszystkich możliwych bloków.
public Vector2Int boardSize = new Vector2Int(10, 20); // Standardowy rozmiar planszy Tetrisa.
public Vector3Int spawnPosition = new Vector3Int(-1, 8, 0); // Pozycja, z której spadają nowe figury.
// --- Punktacja i UI ---
public int score { get; private set; } = 0;
public TextMeshProUGUI scoreText;
public RectInt Bounds
{
// [BLOK KOMENTARZY: Właściwość Określająca Granice Planszy]
// Oblicza granice (min/max X i Y) planszy, co jest kluczowe do sprawdzania,
// czy figura nie wyszła poza obszar gry.
get
{
Vector2Int position = new Vector2Int(-boardSize.x / 2, -boardSize.y / 2);
return new RectInt(position, boardSize);
}
}
private void Awake()
{
// [BLOK KOMENTARZY: Inicjalizacja]
// Pobiera komponenty (Tilemap, Piece) i inicjalizuje dane wszystkich Tetromino.
tilemap = GetComponentInChildren<Tilemap>();
activePiece = GetComponentInChildren<Piece>();
for (int i = 0; i < tetrominoes.Length; i++) {
tetrominoes[i].Initialize();
}
}
private void Start()
{
// [BLOK KOMENTARZY: Uruchomienie Gry]
// Zeruje wynik i rozpoczyna generowanie pierwszej figury.
UpdateScore(0);
SpawnPiece();
}
// --- Logika Spawnowania i Zakończenia Gry ---
public void SpawnPiece()
{
// [BLOK KOMENTARZY: Generowanie Nowej Figury]
// 1. Losuje następny blok (Tetromino) z dostępnych typów.
int random = Random.Range(0, tetrominoes.Length);
TetrominoData data = tetrominoes[random];
// 2. Inicjalizuje aktywną figurę na pozycji startowej.
activePiece.Initialize(this, spawnPosition, data);
// 3. Sprawdza, czy figura może w ogóle się pojawić (czy górne pole jest puste).
if (IsValidPosition(activePiece, spawnPosition)) {
Set(activePiece); // Jeśli tak, rysuje ją.
} else {
GameOver(); // Jeśli nie, koniec gry.
}
}
public void GameOver()
{
// [BLOK KOMENTARZY: Koniec Gry]
// 1. Wysyła wynik gracza na zewnętrzny serwer.
StartCoroutine(
SendScore(
20, // id
score // score
)
);
// 2. Czyści całą planszę (usuwa wszystkie bloki).
tilemap.ClearAllTiles();
}
// --- Rysowanie i Usuwanie Bloków ---
public void Set(Piece piece)
{
// [BLOK KOMENTARZY: Rysowanie Figury na Tilemapie]
// Rysuje wszystkie 4 komórki aktywnej figury na planszy.
for (int i = 0; i < piece.cells.Length; i++)
{
Vector3Int tilePosition = piece.cells[i] + piece.position;
tilemap.SetTile(tilePosition, piece.data.tile);
}
}
public void Clear(Piece piece)
{
// [BLOK KOMENTARZY: Usuwanie Figury z Tilemapy]
// Czyści wszystkie 4 komórki aktywnej figury. Zawsze wywoływane przed jej przesunięciem.
for (int i = 0; i < piece.cells.Length; i++)
{
Vector3Int tilePosition = piece.cells[i] + piece.position;
tilemap.SetTile(tilePosition, null);
}
}
// --- Logika Kolizji ---
public bool IsValidPosition(Piece piece, Vector3Int position)
{
// [BLOK KOMENTARZY: Sprawdzanie Ważności Pozycji]
RectInt bounds = Bounds;
// Sprawdza każdą komórkę figury:
for (int i = 0; i < piece.cells.Length; i++)
{
Vector3Int tilePosition = piece.cells[i] + position;
// 1. Sprawdzenie granic: Czy figura nie wyszła poza krawędź planszy.
if (!bounds.Contains((Vector2Int)tilePosition)) {
return false;
}
// 2. Sprawdzenie zajętości: Czy w docelowej pozycji nie ma już innego bloku.
if (tilemap.HasTile(tilePosition)) {
return false;
}
}
return true;
}
// --- Logika Usuwania Linii ---
public void ClearLines()
{
// [BLOK KOMENTARZY: Wykrywanie i Usuwanie Pełnych Linii]
RectInt bounds = Bounds;
int row = bounds.yMin;
int linesCleared = 0;
// Iteruje od najniższego rzędu do najwyższego.
while (row < bounds.yMax)
{
if (IsLineFull(row))
{
// Jeśli linia pełna, usuwa ją i nie przechodzi do następnego rzędu
// (ponieważ nowa linia spadła w to samo miejsce).
LineClear(row);
linesCleared++;
}
else
{
// Jeśli linia niepełna, przechodzi do następnego rzędu.
row++;
}
}
// Nalicza punkty za usunięte linie.
if (linesCleared > 0)
{
UpdateScore(linesCleared * 100);
}
}
public bool IsLineFull(int row)
{
// [BLOK KOMENTARZY: Sprawdzenie Pełnej Linii]
// Iteruje przez wszystkie kolumny w danym rzędzie. Jeśli znajdzie choć jedną pustą komórkę,
// oznacza to, że linia nie jest pełna.
RectInt bounds = Bounds;
for (int col = bounds.xMin; col < bounds.xMax; col++)
{
Vector3Int position = new Vector3Int(col, row, 0);
if (!tilemap.HasTile(position)) {
return false;
}
}
return true;
}
public void LineClear(int row)
{
// [BLOK KOMENTARZY: Usuwanie Linii i Przesuwanie Bloków]
RectInt bounds = Bounds;
// 1. Czyści usuwaną linię.
for (int col = bounds.xMin; col < bounds.xMax; col++)
{
Vector3Int position = new Vector3Int(col, row, 0);
tilemap.SetTile(position, null);
}
// 2. Przesuwa wszystkie bloki znajdujące się powyżej usuniętej linii w dół o jeden rząd.
while (row < bounds.yMax)
{
for (int col = bounds.xMin; col < bounds.xMax; col++)
{
// Kopiuje Tile z rzędu wyżej (row + 1).
Vector3Int position = new Vector3Int(col, row + 1, 0);
TileBase above = tilemap.GetTile(position);
// Wkleja Tile do aktualnego rzędu (row).
position = new Vector3Int(col, row, 0);
tilemap.SetTile(position, above);
}
row++;
}
}
// --- Aktualizacja Wyniku i Komunikacja Sieciowa ---
public void UpdateScore(int points)
{
// [BLOK KOMENTARZY: Aktualizacja Wyniku]
// Dodaje punkty do wyniku i aktualizuje tekst w UI (formatuje na 4 cyfry).
score += points;
if (scoreText != null)
{
scoreText.text = score.ToString("D4");
}
}
public IEnumerator SendScore(int gameId, int score)
{
// [BLOK KOMENTARZY: Procedura HTTP]
// Standardowa korutyna do wysyłania wyniku na zewnętrzny serwer.
string url = "https://bit.elita.fun/execs/api/set_score.php?game_id=" + gameId + "&score=" + score;
WWWForm f = new WWWForm();
f.AddField("pass", System.DateTime.Now.ToString("mm") + "EcS9YcvMtWNnW0N8yi1mVK");
using (UnityWebRequest r = UnityWebRequest.Post(url, f))
{
yield return r.SendWebRequest();
Debug.Log(r.result == UnityWebRequest.Result.Success
? "Wynik przesłany: " + r.downloadHandler.text
: "BŁĄD WYSYŁANIA WYNIKU: " + r.error);
}
}
}