Building a 2D Game from Scratch in C++
Exploring 2D Game Development in C++ is a great way to enhance your programming skills. Our comprehensive guide not only helps you create captivating games but also equips you with the knowledge to help with your C++ assignment. Dive into the world of game development and strengthen your expertise. Whether you're a student seeking to excel in your coursework or an enthusiast looking to turn your passion into a practical skill, this resource is your gateway to building exciting games and mastering C++ programming for academic excellence.
Block 1: Header Inclusions
```cpp
#include < glfw\glfw3.h >
#include
< stdlib.h >
#include
< stdio.h >
#include
< conio.h >
#include
< iostream >
#include
< vector >
#include
< time.h >
```
These lines include the necessary header files for the program. They provide functionality for graphics (using GLFW), standard input/output operations, console input (conio.h), and containers (vector) to store game objects.
Block 2: Constants and Helper Functions
```cpp
const float DEG2RAD = 3.14159f / 180;
float clampFloat(float f, float minValue, float maxValue) {
// Constrain a value between a minimum and maximum values
if (f < minValue)
return minValue;
if (f > maxValue)
return maxValue;
return f;
}
float randomFloat(float minValue, float maxValue) {
// Generate a random value between 0.0 and 1.0
float r = (rand() % 1000) * 0.001f;
// Scale and shift the value to lie between a minimum and maximum values
return minValue + (maxValue - minValue) * r;
}
```
Here, some constants are defined, such as `DEG2RAD`, which is used to convert degrees to radians. Two helper functions, `clampFloat` and `randomFloat`, are also declared. `clampFloat` ensures a value is within a specified range, and `randomFloat` generates a random float value within a given range.
Block 3: `Brick` Class Definition
```cpp
class Brick {
public:
float red, green, blue;
float x, y, width, height;
BRICKTYPE type;
BRICKSTATE state;
vector
crack; // (x,y) positions of crack points
Brick(BRICKTYPE bt, float xx, float yy, float ww, float hh, float rr, float gg, float bb)
{
type = bt; x = xx; y = yy, width = ww; height = hh; red = rr, green = gg, blue = bb;
state = ON;
};
bool inside(float px, float py)
{
// check if a point (px, py) is inside the brick
return px > x - width / 2 && px < x + width / 2 && py > y - height / 2 && py < y + height / 2;
}
void draw()
{
if (state == OFF)
return;
float halfWidth = width / 2;
float halfHeight = height / 2;
// draw the brick as a rectangle
glColor3d(red, green, blue);
glBegin(GL_POLYGON);
glVertex2f(x + halfWidth, y + halfHeight);
glVertex2f(x + halfWidth, y - halfHeight);
glVertex2f(x - halfWidth, y - halfHeight);
glVertex2f(x - halfWidth, y + halfHeight);
glEnd();
// draw the crack as a strip of lines
glLineWidth(5);
glColor3d(0, 0, 0);
glBegin(GL_LINE_STRIP);
for (int i = 0; i < (int)crack.size() / 2; i++)
glVertex2f(crack[i * 2], crack[i * 2 + 1]);
glEnd();
}
void hit(float px, float py)
{
if (type != DESTRUCTABLE)
return;
if (state == ON)
{
// when hit, the intact brick becomes cracked
state = CRACKED;
createCrack(px, py);
}
else if (state == CRACKED)
{
// when hit, the cracked brick becomes hidden
state = OFF;
}
}
void createCrack(float px, float py)
{
// start cracking from the collision point
crack.push_back(px);
crack.push_back(py);
// angle in the direction to the brick center
float angle = atan2f(y - py, x - px) + 0.5f;
// create a sequence of crack points
for (int i = 0; ; i++)
{
// make a step
float step = randomFloat(0.1f, 0.2f);
px += width * step * cosf(angle);
py += height * step * sinf(angle);
// stop if outside the brick
if (!inside(px, py))
break;
// append another point of the crack
crack.push_back(px);
crack.push_back(py);
// update the angle in a zigzag pattern
float turn = randomFloat(0.5f, 1.0f);
if (i % 2)
angle += turn;
else
angle -= turn;
}
}
};
```
This class represents a brick in the game. It contains properties like position, color, type (reflective or destructible), and state (on, cracked, off). The class also has methods for drawing, checking if a point is inside the brick, and handling collisions with the ball.
Block 4: `Paddle` Class Definition (Inheritance)
```cpp
class Paddle : public Brick {
public:
Paddle(float xx, float yy, float ww, float hh, float rr, float gg, float bb) :
Brick(REFLECTIVE, xx, yy, ww, hh, rr, gg, bb)
{
};
};
```
This class inherits from the `Brick` class and represents the paddle in the game. It adds no new properties but defines a constructor.
Block 5: `Circle` Class Definition
```cpp
class Circle {
public:
float red, green, blue;
float radius;
float x;
float y;
float xspeed;
float yspeed;
Circle(float xx, float yy, float xxspeed, float yyspeed, float rad, float r, float g, float b)
{
x = xx;
y = yy;
xspeed = xxspeed;
yspeed = yyspeed;
radius = rad;
red = r;
green = g;
blue = b;
}
bool checkCollision(Circle* crc)
{
// Squared distance to another circle
float dx = crc->x - x;
float dy = crc->y - y;
float dd = dx * dx + dy * dy;
// Distance test
float minDist = radius + crc->radius;
return dd < minDist * minDist;
}
void checkCollision(Brick* brk)
{
// Ignore hidden bricks
if (brk->state == OFF)
return;
// Closest point to the circle within the brick
float bx = clampFloat(x, brk->x - brk->width / 2, brk->x + brk->width / 2);
float by = clampFloat(y, brk->y - brk->height / 2, brk->y + brk->height / 2);
// Squared distance from the closest point to the circle center
float dx = x - bx;
float dy = y - by;
float dd = dx * dx + dy * dy;
// Distance test
if (dd > radius * radius || dd == 0)
return;
// Project the circle's speed vector on the direction vector
float sd = xspeed * dx + yspeed * dy;
if (sd >= 0)
return;
// For a reflective or uncracked brick, bounce off the closest point
if (brk->type == REFLECTIVE || brk->state == ON)
{
xspeed -= 2 * sd * dx / dd;
yspeed -= 2 * sd * dy / dd;
}
// Crack or hide the brick
brk->hit(bx, by);
}
void moveOneStep()
{
// Update position
x += xspeed * timeStep;
y += yspeed * timeStep;
// Collide with walls
if (x < -1 + radius && xspeed < 0) // Left wall
xspeed *= -1;
if (x > +1 - radius && xspeed > 0) // Right wall
xspeed *= -1;
if (y < -1 + radius && yspeed < 0) // Bottom wall
yspeed *= -1;
if (y > +1 - radius && yspeed > 0) // Top wall
yspeed *= -1;
}
void draw()
{
glColor3f(red, green, blue);
glBegin(GL_POLYGON);
for (int i = 0; i < 360; i++) {
float degInRad = i * DEG2RAD;
glVertex2f((cos(degInRad) * radius) + x, (sin(degInRad) * radius) + y);
}
glEnd();
}
};
```
The `Circle` class represents a circular game object (likely a ball). It contains properties like position, color, radius, and speed. It has methods for checking collisions with other circles or bricks, updating its position, and drawing itself.
Block 6: Global Variables
```cpp
Paddle paddle(0, -0.9f, 0.5f, 0.1f, 1, 1, 1);
vector
bricks;
vector
circles;
```
Here, the global game objects are declared and initialized. The `paddle` is an instance of the `Paddle` class, and there are vectors to store `bricks` and `circles`.
Block 7: `main` Function
```cpp
int main(void)
{
srand((unsigned int)time(NULL));
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
GLFWwindow* window = glfwCreateWindow(480, 480, "Random World of Circles", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
// Create bricks
bricks.push_back(Brick(DESTRUCTABLE, 0.5f, -0.33f, 0.20f, 0.20f, 1, 1, 0));
bricks.push_back(Brick(DESTRUCTABLE, -0.5f, 0.33f, 0.25f, 0.25f, 0, 1, 0));
bricks.push_back(Brick(DESTRUCTABLE, -0.5f, -0.33f, 0.30f, 0.30f, 0, 1, 1));
bricks.push_back(Brick(REFLECTIVE, 0.5f, 0.33f, 0.35f, 0.35f, 1, 0.5f, 0.5f));
while (!glfwWindowShouldClose(window))
{
// Manage time
float t = (float)glfwGetTime();
timeStep = t - currentTime;
currentTime = t;
// Setup view
int width, height;
glfwGetFramebufferSize(window, &width, &height);
float ratio = width / (float)height;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
processInput(window);
// Alter the state of the circles upon collision
vector
uncollidedCircles;
for (int i = 0; i < (int)circles.size(); i++)
{
// Collide with other circles
bool hit = false;
for (int j = 0; j < (int)circles.size(); j++)
if (i != j && circles[i].checkCollision(&circles[j]))
hit = true;
// Store uncollided circles
if (!hit)
uncollidedCircles.push_back(circles[i]);
}
circles = uncollidedCircles;
// Update and draw the circles
for (int i = 0; i < (int)circles.size(); i++)
{
// Collide with the bricks/paddle
for (int j = 0; j < (int)bricks.size(); j++)
circles[i].checkCollision(&bricks[j]);
circles[i].checkCollision(&paddle);
circles[i].moveOneStep();
circles[i].draw();
}
// Draw the bricks/paddle
for (int i = 0; i < (int)bricks.size(); i++)
bricks[i].draw();
paddle.draw();
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
```
The `main` function is the entry point of the program. It initializes the game, creates the window, manages time, processes user input, and runs the game loop, handling the collision, update, and rendering of objects.
Block 8: `processInput` Function
```cpp
void processInput(GLFWwindow *window)
{
// Close the window
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
// Add circles
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
{
float x = randomFloat(-0.9f, 0.9f);
float y = randomFloat(0.8f, 0.9f);
float angle = randomFloat(0, 360 * DEG2RAD);
float xspeed = cosf(angle);
float yspeed = sinf(angle);
float r = randomFloat(0.5f, 1.0f);
float g = randomFloat(0.5f, 1.0f);
float b = randomFloat(0.5f, 1.0f);
Circle B(x, y, xspeed, yspeed, 0.05f, r, g, b);
circles.push_back(B);
}
// Control the paddle
const float paddleSpeed = 2.0f;
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) // Move left
paddle.x -= paddleSpeed * timeStep;
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_PRESS) // Move right
paddle.x += paddleSpeed * timeStep;
if (paddle.x < -1 + paddle.width / 2) // Left constraint
paddle.x = -1 + paddle.width / 2;
if (paddle.x > 1 - paddle.width / 2) // Right constraint
paddle.x = 1 - paddle.width / 2;
}
```
The `processInput` function is responsible for handling user input, such as closing the window, adding circles, and controlling the paddle's movement.
Block 9: Game Loop
```cpp
while (!glfwWindowShouldClose(window))
{
// Manage time
float t = (float)glfwGetTime();
timeStep = t - currentTime;
currentTime = t;
// Setup view
int width, height;
glfwGetFramebufferSize(window, &width, &height);
float ratio = width / (float)height;
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
processInput(window);
// Alter the state of the circles upon collision
vector
uncollidedCircles;
for (int i = 0; i < (int)circles.size(); i++)
{
// Collide with other circles
bool hit = false;
for (int j = 0; j < (int)circles.size(); j++)
if (i != j && circles[i].checkCollision(&circles[j]))
hit = true;
// Store uncollided circles
if (!hit)
uncollidedCircles.push_back(circles[i]);
}
circles = uncollidedCircles;
// Update and draw the circles
for (int i = 0; i < (int)circles.size(); i++)
{
// Collide with the bricks/paddle
for (int j = 0; j < (int)bricks.size(); j++)
circles[i].checkCollision(&bricks[j]);
circles[i].checkCollision(&paddle);
circles[i].moveOneStep();
circles[i].draw();
}
// Draw the bricks/paddle
for (int i = 0; i < (int)bricks.size(); i++)
bricks[i].draw();
paddle.draw();
glfwSwapBuffers(window);
glfwPollEvents();
}
```
This is the core of the game loop. It manages time, handles collisions between circles and bricks, updates positions, and draws the game objects. The loop continues until the user closes the window.
Block 10: Cleanup and Termination
```cpp
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
```
After the game loop ends, these lines clean up resources and terminate the program.
Conclusion
In conclusion, this guide has taken you on a comprehensive journey into the fascinating world of 2D game development using C++. From understanding the code to unraveling the complexities of collision detection, physics, and graphics, you've gained essential knowledge. Whether you're a novice game developer or an aspiring coder, this guide equips you with the fundamentals to create engaging 2D games from scratch. By the end of this guide, you're not just familiar with the intricacies; you're well-prepared to bring your own game ideas to life, complete with dynamic interactions and captivating visuals. As you continue on your game development journey, remember that creativity and persistence are your greatest assets in shaping the digital worlds of the future. So, dive in, experiment, and turn your gaming dreams into reality with the power of C++.