https://github.com/mrpunkdasilva/16Games-in-Cpp/tree/main/02%20%20Doodle%20Jump
This tutorial teaches you how to create the game Doodle Jump from scratch using C++ and SFML. We will start with basic concepts and build knowledge step by step, explaining each part clearly and in detail.
What is Doodle Jump
Imagine a game where you control a small character who needs to jump from one platform to another, trying to climb as high as possible. It's like jumping from step to step on an infinite staircase, but with some special rules:
- The character always falls due to gravity (as in real life)
- He can only move left and right
- When he touches a platform while falling, he automatically jumps up
- If he falls too far down, the game ends
- The goal is to reach the highest possible height
This game allows us to learn several important concepts of game programming in a simple and fun way.
How to Organise a Game
Game States - Different Screens
Before we start programming, we need to think about how to organise our game. Every game has different ‘screens’ or ‘states’. For example:
- Menu: The initial screen where the player decides whether to play
- Playing: When the game is actually happening
- Game Over: When the player loses and sees their score
We call these ‘game states.’ It's like having different rooms in a house — you can only be in one room at a time, but you can move between them.
graph LR
A[MENU] -->|Space| B[PLAYING]
B -->|Fall| C[GAME_OVER]
C -->|R| B
C -->|M| A
A -->|ESC| D[EXIT]
This diagram shows how the player navigates between screens:
- From the Menu, pressing Space takes you to the game
- During the Game, if the player falls, it goes to Game Over
- In Game Over, you can press R to play again or M to return to the menu
To implement this in code, we use something called an ‘enum’ - which is a way of giving names to numbers:
enum GameState {
MENU, // Value 0 - Game start screen
PLAYING, // Value 1 - When we are playing
GAME_OVER // Value 2 - When the game ends
};
Storing Information - Variables and Structures
In any game, we need to store information. For example, where is the player? Where are the platforms? What is the current score?
Platform Position
For each platform, we need to know its position on the screen. A position has two coordinates: X (horizontal) and Y (vertical). We create a structure for this:
struct point {
int x, y; // x = horizontal position, y = vertical position
};
Think of it as an address: ‘The platform is at position X=100, Y=200’.
Player Information
For the player, we need to store several important pieces of information:
int x = 100, y = 100; // Where the player is on the screen
int h = 200; // A special height (we will explain later)
float dx = 0, dy = 0; // Player speed
int score = 0; // Points scored by the player
int height = 0; // Highest height reached by the player
Let's understand each one:
- x, y: The player's position on the screen (like coordinates on a map)
- dx, dy: The player's speed (dx = horizontal speed, dy = vertical speed)
- h: A special reference height that we use for the camera system
- score: The points the player has earned
- height: The highest height the player has ever reached in the game
The Main Game Mechanics
How Gravity Works
In real life, when you jump, gravity pulls you down. In our game, we need to simulate this gravity in a simple way.
Imagine gravity as a force that is always pulling the player down. At every moment of the game (every ‘frame’), gravity makes the player fall a little faster.
graph TD
A[Every frame of the game] --> B[Increase downward speed: dy += 0.2]
B --> C[Move player: y += dy]
C --> D[Check if platform touched]
D --> E{Hit platform?}
E -->|Yes| F[Jump up: dy = -10]
E -->|No| G[Continue falling]
F --> A
G --> A
Let's understand this step by step:
dy += 0.2; // With each frame, the player falls a little faster
y += dy; // Move the player based on current speed
How it works:
- dy is the player's vertical speed
- When dy is negative (example: -10), the player moves upwards
- When dy is positive (example: +5), the player moves down
- Gravity always adds +0.2 to dy, making the player fall faster
- When the player touches a platform, we set dy = -10, making them jump up
Horizontal Movement - Left and Right
The player can move left and right using the arrow keys. But there is a special trick: when the player leaves one side of the screen, he appears on the other side.
Imagine that the screen is like a cylinder - if you walk to the right and leave the screen, you appear on the left side. This creates the feeling of an infinite world.
if (Keyboard::isKeyPressed(Keyboard::Right)) {
x += 3; // Move 3 pixels to the right
if (x > 400) x = -50; // If you left the right side, you appear on the left
}
if (Keyboard::isKeyPressed(Keyboard::Left)) {
x -= 3; // Move 3 pixels to the left
if (x < -50) x = 400; // If you left the left side, you appear on the right
}
Why -50 and 400?
- The screen is 400 pixels wide (from 0 to 400)
- We use -50 and 400 to create a smooth transition
- The player gradually disappears from one side before appearing on the other
The Camera Trick - The Smartest Part
This is the most interesting part of the game. Instead of making the player move up the screen when they jump high, we do the opposite: we keep the player in the same place and move the whole world down!
Imagine you are on a conveyor belt that moves downwards. You are always in the same position on the belt, but the world around you is moving.
sequenceDiagram
participant Player
participant Camera
participant Platforms
Player->>Camera: Jumped too high!
Camera->>Platforms: Move all down
Platforms->>Platforms: Recreate platforms that have left the screen
Camera->>Player: Keep at the same visual height
How this works in code:
if (y < h) { // If the player has climbed above the reference height
int heightGain = h - y; // Calculate how much they have climbed
height += heightGain; // Count for statistics
score += heightGain / 5; // Give points for climbing
// Move ALL platforms down
for (int i = 0; i < 10; i++) {
plat[i].y = plat[i].y - dy;
// If a platform has left the screen at the bottom, create a new one at the top
if (plat[i].y > 533) {
plat[i].y = -50; // New position at the top
plat[i].x = rand() % 332; // Random horizontal position
}
}
y = h; // Put the player back at the reference height
}
Why do it this way?
- The player is always visible on the screen
- We can create platforms infinitely
- It is easier to program
- The game never ‘ends’ - there are always more platforms appearing
How to Detect if the Player Touched a Platform
To know if the player touched a platform, we need to check if they are ‘overlapping’ on the screen. It's like checking if two rectangles are touching each other.
But there is a special rule: we only detect the collision when the player is falling (not when they are climbing). This allows the player to pass through the platforms when climbing, but ‘land’ on them when descending.
for (int i = 0; i < 10; i++) { // Check all 10 platforms
if ((x + 25 > plat[i].x) && // Player is not too far to the left
(x + 25 < plat[i].x + 68) && // Player is not too far to the right
(y + 70 > plat[i].y) && // Player is not too high
(y + 70 < plat[i].y + 14) && // Player is not too low
(dy > 0)) { // Player is falling (not climbing)
dy = -10; // Make the player jump up
score += 10; // Give points for jumping
}
}
Understanding the conditions:
- x + 25: We use x + 25 because we want to check the centre of the player
- plat[i].x + 68: 68 is the width of the platform
- y + 70: 70 is approximately the height of the player
- plat[i].y + 14: 14 is the height of the platform
- dy > 0: Only detects collision when the player is falling
Why only when falling?
- If the player is climbing, they should pass through the platform
- If the player is descending, they should ‘land’ on the platform
- This prevents the player from getting ‘stuck’ on the platform
The Mathematics Behind the Game
Calculating Jump Height
Let's discover some interesting things about our game using simple mathematics.
In our game:
- Gravity adds 0.2 to the speed every frame
- When the player jumps, their initial speed is -10
What is the maximum height the player can reach?
We can calculate this! When the player jumps, they start with a speed of -10, and gravity decreases this speed until it reaches 0 (when they stop rising).
Maximum height = (initial speed)² ÷ (2 × gravity)
Maximum height = 10² ÷ (2 × 0.2) = 100 ÷ 0.4 = 250 pixels
How long does the player stay in the air?
Time to climb = initial speed ÷ gravity = 10 ÷ 0.2 = 50 frames
Total time in the air = 2 × time to climb = 100 frames
If the game runs at 60 fps, this means that each jump lasts about 1.67 seconds.
Why are the platforms 80 pixels apart?
The platforms are spaced 80 pixels apart vertically. Since the player can jump up to 250 pixels high, they can always reach the next platforms. This keeps the game challenging but always playable.
graph TB
A[Current Platform] -->|80px| B[Next Platform]
B -->|80px| C[Next Platform]
D[Maximum Height: 250px] -.->|Reachable| B
D -.->|Reachable| C
How to Create Infinite Platforms
One of the coolest parts of the game is that the platforms never end. This is called ‘procedural generation’ — the computer automatically creates new content as you play.
Creating the First Platforms
When the game starts, we create 10 platforms:
for (int i = 0; i < 10; i++) {
plat[i].x = rand() % 332; // Random horizontal position
plat[i].y = i * 80 + 100; // Vertical spacing of 80 pixels
}
Why use 332?
- Our screen is 400 pixels wide
- Each platform is 68 pixels wide
- For the platform to fit completely on the screen: 400 - 68 = 332
- So we can place the platform in any position from 0 to 332
Recycling Platforms
When a platform leaves the bottom of the screen, we don't throw it away. Instead, we ‘recycle’ it by creating a new platform at the top:
if (plat[i].y > 533) { // If the platform has left the screen
plat[i].y = -50; // Place at the top of the screen
plat[i].x = rand() % 332; // New random horizontal position
}
This means that we always have exactly 10 platforms on the screen, but they are always changing position.
How Scoring Works
The game has three ways to earn points:
1. Points for Climbing
score += heightGain / 5; // 1 point for every 5 pixels climbed
As you climb in the game, you automatically earn points. The higher you climb, the more points you earn!
2. Points for Jumping on Platforms
score += 10; // 10 points each time you touch a platform
Each time you manage to jump on a platform, you earn 10 extra points.
3. Special Bonuses
if (height % 1000 == 0 && height > 0) {
score += 500; // 500 points for every 1000 units of height
}
For every 1000 units of height, you earn a special bonus of 500 points!
Improving Text Appearance
Text with Outline
To ensure that text is always visible (regardless of the background colour), we have created a special function that draws an outline around the letters:
void drawTextWithOutline(RenderWindow& window, Text& text, Color outlineColor) {
Vector2f originalPos = text.getPosition();
Color originalColor = text.getFillColor();
// Draw a ‘shadow’ of the text in 8 positions around it
text.setFillColor(outlineColor);
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx != 0 || dy != 0) {
text.setPosition(originalPos.x + dx, originalPos.y + dy);
window.draw(text);
}
}
}
// Draw the main text on top
text.setFillColour(originalColour);
text.setPosition(originalPos);
window.draw(text);
}
This function draws the text in 8 slightly different positions around the original position, creating an outline effect that makes the text always legible.
Why the Game Works Well
Using Only 10 Platforms
The game only needs 10 active platforms at a time. This is smart because:
- It saves computer memory
- The game always runs at the same speed
- It is easier to program and debug
Smart Checks
We only check for collisions when the player is falling, not when they are climbing. This makes the game faster and avoids problems.
How the Game Works - The Main Loop
Every game has a ‘main loop’ - a cycle that repeats many times per second. With each repetition (called a ‘frame’), the game:
flowchart TD
A[Start frame] --> B[See what the player pressed]
B --> C{What screen are we on?}
C -->|MENU| D[Show menu]
C -->|PLAYING| E[Play the game]
C -->|GAME_OVER| F[Show game over]
D --> G[Draw everything on the screen]
E --> H[Move player and platforms]
F --> G
H --> I[Move the camera if necessary]
I --> G
G --> J[Show the frame on the screen]
J --> K{Continue playing?}
K -->|Yes| A
K -->|No| L[Close the game]
What Each State Does
- MENU: Shows the game title and waits for the player to press Space to start
- PLAYING: Runs all game logic (physics, collisions, scoring)
- GAME_OVER: Shows the final score and allows you to restart or return to the menu
How to Run the Game
Compile
cd build
make doodle_jump
Play
make run_doodle_jump
Required Files
The game needs these files to run:
-
images/background.png
: The background image -
images/platform.png
: The platform image -
images/doodle.png
: The character image -
fonts/Carlito-Regular.ttf
: The font for the text
Ideas for Improving the Game
New Mechanics
- Power-ups such as double jump or jetpack
- Special platforms (that move, break, or give super jumps)
- Enemies to avoid
- Sound effects and music
Technical Improvements
- More platforms on the screen
- Better graphics
- Smooth animations
- Save system to remember the highest score
Complete Game Code
Conclusion
Congratulations! You have learned how a complete game works. Doodle Jump may seem simple, but it teaches very important concepts:
- Game states: How to organize different screens
- Basic physics: How to simulate gravity and movement
- Collision detection: How to know when objects touch each other
- Procedural generation: How to create infinite content
- Camera system: How to make the world move instead of the player
These concepts are used in much more complex games. Now that you understand how it works, you can try modifying the values in the code to see what happens, or even create your own mechanics!
Top comments (0)