Unreal Engine provides a powerful framework for implementing games like Tetris. Below is a comprehensive guide and implementation of Tetris in Unreal Engine, including core mechanics such as block movement, rotation, row clearing, random block generation, scoring, and game-over conditions.
1. Tetromino Class
Each Tetromino (Tetris piece) is a custom AActor
class that handles player input, movement, and rotation.
// Tetromino.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Tetromino.generated.h"
UCLASS()
class TETRIS_API ATetromino : public AActor
{
GENERATED_BODY()
public:
ATetromino();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
void Move(FVector Direction);
void Rotate();
private:
float FallTime = 1.0f;
float FallTimer = 0.0f;
bool IsValidPosition();
};
// Tetromino.cpp
#include "Tetromino.h"
#include "GridManager.h"
#include "Kismet/GameplayStatics.h"
ATetromino::ATetromino()
{
PrimaryActorTick.bCanEverTick = true;
}
void ATetromino::BeginPlay()
{
Super::BeginPlay();
}
void ATetromino::Tick(float DeltaTime)
{
FallTimer += DeltaTime;
if (FallTimer >= FallTime)
{
Move(FVector(0, 0, -1));
FallTimer = 0;
}
if (IsInputKeyDown(EKeys::Left))
Move(FVector(-1, 0, 0));
if (IsInputKeyDown(EKeys::Right))
Move(FVector(1, 0, 0));
if (IsInputKeyDown(EKeys::Down))
Move(FVector(0, 0, -1));
if (IsInputKeyDown(EKeys::Up))
Rotate();
}
void ATetromino::Move(FVector Direction)
{
SetActorLocation(GetActorLocation() + Direction * 100.0f);
if (!IsValidPosition())
SetActorLocation(GetActorLocation() - Direction * 100.0f);
}
void ATetromino::Rotate()
{
AddActorLocalRotation(FRotator(0, 0, -90));
if (!IsValidPosition())
AddActorLocalRotation(FRotator(0, 0, 90));
}
bool ATetromino::IsValidPosition()
{
TArray<USceneComponent*> Children;
GetComponents<USceneComponent>(Children);
for (USceneComponent* Child : Children)
{
FVector Location = Child->GetComponentLocation() / 100.0f;
Location = FVector(FMath::RoundToInt(Location.X), FMath::RoundToInt(Location.Y), FMath::RoundToInt(Location.Z));
if (!AGridManager::IsInsideGrid(Location) || AGridManager::IsOccupied(Location))
return false;
}
return true;
}
2. Grid Management
The grid manages block positions, detects collisions, and handles row clearing.
// GridManager.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GridManager.generated.h"
UCLASS()
class TETRIS_API AGridManager : public AActor
{
GENERATED_BODY()
public:
static const int Width = 10;
static const int Height = 20;
static bool IsInsideGrid(FVector Location);
static bool IsOccupied(FVector Location);
static void AddToGrid(AActor* Block);
static void CheckRows();
private:
static TArray<AActor*> Grid[Width][Height];
};
// GridManager.cpp
#include "GridManager.h"
TArray<AActor*> AGridManager::Grid[Width][Height];
bool AGridManager::IsInsideGrid(FVector Location)
{
return Location.X >= 0 && Location.X < Width && Location.Z >= 0;
}
bool AGridManager::IsOccupied(FVector Location)
{
return Grid[(int)Location.X][(int)Location.Z].Num() > 0;
}
void AGridManager::AddToGrid(AActor* Block)
{
TArray<USceneComponent*> Children;
Block->GetComponents<USceneComponent>(Children);
for (USceneComponent* Child : Children)
{
FVector Location = Child->GetComponentLocation() / 100.0f;
Location = FVector(FMath::RoundToInt(Location.X), FMath::RoundToInt(Location.Y), FMath::RoundToInt(Location.Z));
Grid[(int)Location.X][(int)Location.Z].Add(Child->GetOwner());
}
CheckRows();
}
void AGridManager::CheckRows()
{
for (int Z = 0; Z < Height; ++Z)
{
bool IsFull = true;
for (int X = 0; X < Width; ++X)
{
if (Grid[X][Z].Num() == 0)
{
IsFull = false;
break;
}
}
if (IsFull)
{
for (int X = 0; X < Width; ++X)
{
for (AActor* Actor : Grid[X][Z])
Actor->Destroy();
Grid[X][Z].Empty();
}
for (int ZAbove = Z + 1; ZAbove < Height; ++ZAbove)
{
for (int X = 0; X < Width; ++X)
{
for (AActor* Actor : Grid[X][ZAbove])
{
Actor->SetActorLocation(Actor->GetActorLocation() - FVector(0, 0, 100));
Grid[X][ZAbove - 1].Add(Actor);
}
Grid[X][ZAbove].Empty();
}
}
Z--;
}
}
}
3. Game Manager
The game manager spawns Tetrominoes, tracks the score, and detects game-over conditions.
// GameManager.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameManager.generated.h"
UCLASS()
class TETRIS_API AGameManager : public AActor
{
GENERATED_BODY()
public:
void SpawnTetromino();
void AddScore(int RowsCleared);
void CheckGameOver();
private:
int Score = 0;
TSubclassOf<class ATetromino> TetrominoClass;
};
// GameManager.cpp
#include "GameManager.h"
#include "Kismet/GameplayStatics.h"
#include "Tetromino.h"
void AGameManager::SpawnTetromino()
{
FVector SpawnLocation(5, 0, 20);
FActorSpawnParameters SpawnParams;
GetWorld()->SpawnActor<ATetromino>(TetrominoClass, SpawnLocation, FRotator::ZeroRotator, SpawnParams);
}
void AGameManager::AddScore(int RowsCleared)
{
Score += RowsCleared * 100;
UE_LOG(LogTemp, Warning, TEXT("Score: %d"), Score);
}
void AGameManager::CheckGameOver()
{
for (int X = 0; X < AGridManager::Width; ++X)
{
if (AGridManager::IsOccupied(FVector(X, 0, AGridManager::Height - 1)))
{
UE_LOG(LogTemp, Warning, TEXT("Game Over!"));
UGameplayStatics::SetGamePaused(GetWorld(), true);
break;
}
}
}
Features and Setup
- Tetromino Prefabs: Use modular components for each Tetromino block.
- Game Grid: A 2D grid system that detects collisions and clears rows.
- Game Over: Detect overflow at the top of the grid.
This implementation uses Unreal’s Blueprint system where necessary, making it easily extendable and functional for real-world applications.