DEV Community

Cover image for Building Doodle Jump in C++
Mr Punk da Silva
Mr Punk da Silva

Posted on • Edited on

Building Doodle Jump in C++

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]
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Play

make run_doodle_jump
Enter fullscreen mode Exit fullscreen mode

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)