Falling Blocks Part 1: Introduction
What this tutorial covers
- The creation of a Tetris clone
- Collision detection
- Victory and losing conditions
- Moving 2D objects
- Using user-defined objects in a game project
- Storing multiple images in a single file
Introduction
Welcome to this series' first actual game tutorial! By the end of this tutorial you'll know how to write a full featured Tetris clone. Before you start, you might want to familiarize yourself with the std::vector. It's essentially just an array with added features. I've written a very brief tutorial that can be found here that will teach you all you need to know right now about the std::vector.
Note that this tutorial, as well as the other tutorials in this series, all build off of the Introduction tutorial. If you haven't read it yet, you should do it now.
Setting Up
The first thing we need to do is start a new project called "Falling Blocks" and copy in "Main.cpp" and "Defines.h" from the Introduction tutorial.
Also remember to copy "SDL.dll", "SDL_ttf.dll", and "ARIAL.TTF" into you project directory and set Project->Falling Blocks Properties->C/C++->Code Generation->Runtime Library to Multi-threaded DLL (/MD).
You'll also need the bitmap for this tutorial, which can be found in the downloadable source code. See the table of contents for a link to the downloadable source code.
The Code
Before we begin there's one thing I need to make clear. When I say "block", I mean the actual objects in our game. These are usually called "Tetrinos" but that name's probably copyrighted. When I say "square", I mean the individual squares that make up our Tetrin...er...blocks.
Defines.h
There are some changes and additions we need to make to "Defines.h". First, our window dimensions are way to big. We also need to change our window caption. Make the following changes to "Defines.h":
#define WINDOW_WIDTH 300
#define WINDOW_HEIGHT 400
#define WINDOW_CAPTION "Falling Blocks"
Now we need to add some new values to "Defines.h". If you run the executable that comes with the downloadable source code, you'll see that the game area does not take up the entire screen. We'll need to know the location and dimensions of our game area within our window, so let's add the following to "Defines.h":
#define GAME_AREA_LEFT 53
#define GAME_AREA_RIGHT 251
#define GAME_AREA_BOTTOM 298
We want there to be a finite amount of levels in our game, so we'll define that here. We'll keep all of the values that are dependent on the number of levels in our game in "Defines.h". This way we only have to change a couple of values in "Defines.h" to add more levels.
In Tetris, everytime you clear a line of squares you get points. If you reach a certain number of points, you advance to the next level. Every time you advance to the next level, the blocks start moving down faster. We'll define the speed of the blocks in moves/frames so if the block is moving down every 60 frames, and we're running our game at 30 frames per second, our blocks will move down every two seconds. Add the following to "Defines.h":
#define NUM_LEVELS 5 // number of levels in the game
#define POINTS_PER_LINE 525 // points player receives for completing a line
#define POINTS_PER_LEVEL 6300 // points player needs to advance a level
#define INITIAL_SPEED 60 // initial interval at which focus block moves down
#define SPEED_CHANGE 10 // the above interval is reduced by this much each level
When the current block (we'll call it the "focus block") reaches the bottom of the game area, the player should be given a brief moment in which to slide it left or right. This works great when the focus block hits another block and the player quickly slides it into a better place. We'll define the amount of time the player gets to slide the focus block here.
The main purpose of our game is to fill rows of squares in order to clear them and receive points. We'll need to define the number of squares that can fit in a row. We'll use this value later when we determine which rows are full of squares.
When we specify the locations of our squares within the game area, we'll be using our squares' centers. For this reason, we need to record the distance from the center of a square to its sides. This value will be used for getting the locations of the sides of our squares as well as for determining if two blocks are touching.
Add the following to "Defines.h":
#define SLIDE_TIME 15
#define SQUARES_PER_ROW 10 // number of squares that fit in a row
#define SQUARE_MEDIAN 10 // distance from the center of a square to its sides
The rest of our defines involve locations within our game area and our bitmap. We need to define the starting point for our blocks, and where to display the current score, level, required score, and next block in line. Add the following to "Defines.h":
// Starting position of the focus block
#define BLOCK_START_X 151
#define BLOCK_START_Y 59
// Location on game screen for displaying...
#define LEVEL_RECT_X 42 // current level
#define LEVEL_RECT_Y 320
#define SCORE_RECT_X 42 // current score
#define SCORE_RECT_Y 340
#define NEEDED_SCORE_RECT_X 42 // score needed for next level
#define NEEDED_SCORE_RECT_Y 360
#define NEXT_BLOCK_CIRCLE_X 214 // next block in line to be focus block
#define NEXT_BLOCK_CIRCLE_Y 347
In most games, each image is not actually stored in a separate file. Games generally cram as much information into as few files as possible. Because there isn't much to our game, we can easily store all of our images in a single file. To make this work, we need to define the locations within our bitmap of the game's background screens and squares. Add the following to "Defines.h":
// Locations within bitmap of background screens
#define LEVEL_ONE_X 0
#define LEVEL_ONE_Y 0
#define LEVEL_TWO_X 300
#define LEVEL_TWO_Y 0
#define LEVEL_THREE_X 300
#define LEVEL_THREE_Y 0
#define LEVEL_FOUR_X 0
#define LEVEL_FOUR_Y 396
#define LEVEL_FIVE_X 300
#define LEVEL_FIVE_Y 396
// Location within bitmap of colored squares
#define RED_SQUARE_X 600
#define RED_SQUARE_Y 400
#define PURPLE_SQUARE_X 620
#define PURPLE_SQUARE_Y 400
#define GREY_SQUARE_X 640
#define GREY_SQUARE_Y 400
#define BLUE_SQUARE_X 660
#define BLUE_SQUARE_Y 400
#define GREEN_SQUARE_X 680
#define GREEN_SQUARE_Y 400
#define BLACK_SQUARE_X 700
#define BLACK_SQUARE_Y 400
#define YELLOW_SQUARE_X 720
#define YELLOW_SQUARE_Y 400
Enums.h
A new file we'll be adding to our project is "Enums.h". This is a fairly simple file and could probably be fit into one of our other files but I like to keep things organized in separate files.
We only need two enumerations for this project. The first will be used for the types of blocks in our game. We enumerate our block types so we can generate random numbers to determine what block to add to our game next. The second will be used for directions. Blocks can be moved left, right, or down.
Add the following to "Enums.h":
#pragma once
enum BlockType
{
SQUARE_BLOCK,
T_BLOCK,
L_BLOCK,
BACKWARDS_L_BLOCK,
STRAIGHT_BLOCK,
S_BLOCK,
BACKWARDS_S_BLOCK
};
enum Direction
{
LEFT,
RIGHT,
DOWN
};
Falling Blocks Part 2: cSquare and cBlock