Stacking Success: Introduction to Game Development Using Tetris
You can find this code's C++ implementation in my GitHub:
Fueled by my passion for C++ and the urge to learn more, I've stepped into the vibrant world of video game development. What intrigues me the most is the diversity of problems we can tackle due to the virtual nature of video games. This field truly offers a playground for anyone looking to deepen their understanding of software development, since various approaches at different levels stand out more here than in other domains.
With a surge of enthusiasm, I took on my first game project, which, as you can discern, is Tetris!
And my aim is to bring you along on my journey 😀 and convey the approachability I experienced during this introduction.
Tetris
Coming soon.
Lets get started
In this tutorial, I'll be walking you through the process of creating an interactive application using Visual Studio in object-oriented programming. To facilitate the development, we'll use the Simple DirectMedia Layer (SDL), a library to give developers intimate, low-level access to elements such as audio, keyboard, mouse, joystick, and graphics. But what's development without robust testing? Hence, we'll adopt a debugging-oriented approach throughout. Dive in and let's begin this exciting journey!
I won't be talking about how to install SLD, but here is the video where I learned!
Implementation design
Designing our game is the foundational step. It sets the stage because all the programming will revolve around it. Given that Tetris is a widely-analyzed challenge, many solutions have been proposed over the years. One of the most straightforward methods to tackle it is by writing the code in-line, leading to a single file for our entire project.
We won't be taking that route. Instead, we'll prepare for an industry that's predominantly Object-Oriented. It's a complex programming subject; however, I believe this application does a good job of keeping it simple.
And, once you get the hang of it, you'll find there's a comprehensive methodology underlying Object-Oriented-Programming (OOP) that can truly make our life easier as programmers.
Steps
The following list outlines some of the most basic tasks a game can entail. Organized sequentially, it ensures that our progression through each task remains independent of subsequent ones. This structure guarantees that even if we pause at any stage, we'll have a somewhat complete exercise to reflect upon and showcase 🙂
- Set up your development
- Game Initialization.
- Basic Gameboard
- Tetromino implementation
- Game logic
- User input
- Game flow
There are also some steps that could lead to a more polished result, but I haven't explored them myself. Some examples:
- Polishing
- User interface
- Testing iteration
Implementation
1. Set up your environment.
After we install SDL as seen here, we can confirm with the following snippet that we can use it.
////////////////// Main.cpp /////////////////////
#include <SDL.h>
int main(int argc, char* argv[]) {
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return 1;
}
return 0;
}
2. Game Initialization
SDL Usage
At this point, SDL functions should be completely accesible 🙂, but as SDL usage depends on other components, first we need to initialize it. This way, we make sure every subsystem (video, audio, events, etc.) is operationg properly. If anything turns out, you can handle errors gracefully instead of encountering unpredictable behavior later. Also, we set up the necessary ressources for the library to work properly. It's a crucial step in using the library correctly and efficiently.
Please note, we are also creating and initializing the window where we will see tangible proof of our advancements.
////////////////// Main.cpp /////////////////////
bool initializeSDL(SDL_Window* &window, SDL_Renderer* &renderer) {
// Initialize SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return false;
}
// Create window
window = SDL_CreateWindow("Tetris",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH, SCREEN_HEIGHT,
SDL_WINDOW_SHOWN);
if (!window) {
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
// Initialize renderer
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
//For opacity
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
return true;
}
To mirror this, cleaning up after using SDL is equally crucial.
////////////////// Main.cpp /////////////////////
bool cleanupSDL(SDL_Window*& window, SDL_Renderer*& renderer) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return true;
}
Game Class
Here, we introduce the concept of the "main loop" or "game loop." Essentially, it's a continuous loop responsible for calling the core mechanics of our Tetris game. Some comon mechanics are:
- Event handling: This method is responsible for handling user input or other external events
- Update: Updates the game state. This can include moving characters, checking for collisions, updating timers, etc.
- Render phases: This method is responsible for drawing the game's content on the screen.
As I mentioned before, we will introduce this mechanichs as characteristics of an object "Game".
////////////////// Game.h /////////////////////
class Game {
public:
Game(SDL_Renderer* sdlRenderer); // This makes the renderer accesible to the game mechanics
const bool isRunning() const; // The main loop continues as long as the game is "running".
void handleEvents();
void update();
void render();
private:
SDL_Renderer* mSdlRenderer;
bool mIsRunning;
void renderWindow(); //specialized rendering function
};
After defining our "Game"" class, we've implemented it's member functions and initialization. The code is quite extensive, so I encourage you to review the "Game.cpp" file. Please focus on the functions introduced so far and disregard the rest for now.
Then we can call our new code to manage correctly our game setup.
////////////////// Main.cpp /////////////////////
#include <SDL.h>
#include "Game.h"
int main(int argc, char* argv[]) {
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
if (!initializeSDL(window, renderer)) {
return 1;
}
//Initialize game renderer
Game game(renderer);
//Game loop
while (game.isRunning()) {
game.handleEvents();
game.update();
game.render();
}
//clean up and free resources.
cleanupSDL(window, renderer);
return 0;
}
Now, we have some logic going on. So far, your window should look like this: Picture coming soon! TODO: Check if this code would actually not end uo in an infinite loop
3. Basic Gameboard
Coming soon!
4. Tetromino Implementation
Coming soon!
5. Game Logic
Coming soon!
6. User Input
Coming soon!
7. Game Flow
Coming soon!
Next Steps
Coming soon!
Aknowledgements
Coming soon!