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)