DEV Community

Cover image for Arkanoid: Building a Classic Arcade Game in C++ with SFML
Mr Punk da Silva
Mr Punk da Silva

Posted on • Edited on

Arkanoid: Building a Classic Arcade Game in C++ with SFML

Repository: Click here

This comprehensive tutorial demonstrates how to build the classic Arkanoid game from scratch using C++ and SFML. We'll progress from fundamental concepts to advanced implementations, with clear explanations at each stage.

Game Concept

Arkanoid is a breakout-style game where players control a paddle at the bottom of the screen, bouncing a ball to destroy colourful brick formations above. Think of it as single-player tennis with destructible targets:

  • A physics-driven ball ricochets around the play area
  • Player-controlled paddle moves horizontally
  • Balls must be kept in play
  • Brick destruction earns points
  • Objective: Clear all bricks without losing the ball

This project teaches essential game development concepts including collision physics, object-oriented programming, and state management.

Architectural Design

Game State Management

Arkanoid requires more sophisticated state handling than simpler games:

stateDiagram-v2
    [*] --> MENU
    MENU --> PLAYING: Space
    PLAYING --> PAUSED: P
    PAUSED --> PLAYING: P
    PLAYING --> GAME_OVER: No lives
    PLAYING --> VICTORY: All bricks
    GAME_OVER --> MENU: R
    VICTORY --> MENU: R
Enter fullscreen mode Exit fullscreen mode

Implementation using an enumeration:

enum GameState {
    MENU,        // Title screen
    PLAYING,     // Active gameplay
    PAUSED,      // Paused session
    GAME_OVER,   // Failed condition
    VICTORY      // Success condition
};
Enter fullscreen mode Exit fullscreen mode

Object-Oriented Structure

Key game elements are implemented as distinct classes:

Brick Class Implementation

class Brick {
public:
    sf::Sprite visual;      // Graphical representation
    bool destroyed;         // Destruction state
    int pointValue;         // Scoring worth

    Brick() : destroyed(false), pointValue(10) {} // Constructor

    void destroy() { destroyed = true; }      // Mark as destroyed
    sf::FloatRect getBounds() const;          // Collision area
    void render(sf::RenderWindow& window);    // Drawing method
};
Enter fullscreen mode Exit fullscreen mode

Design Rationale:

  • Encapsulates brick-specific properties
  • Enables efficient management of multiple bricks
  • Promotes code organisation and reusability

Ball Physics Implementation

class Ball {
public:
    sf::Sprite visual;          // Graphical representation
    sf::Vector2f velocity;      // Movement vector
    float baseSpeed;            // Movement scalar

    void update(float deltaTime, const sf::Vector2u& windowSize);  // Position update
    void invertX() { velocity.x = -velocity.x; }     // Horizontal bounce
    void invertY() { velocity.y = -velocity.y; }     // Vertical bounce
    bool isOOB(const sf::Vector2u& windowSize) const; // Boundary check
};
Enter fullscreen mode Exit fullscreen mode

Core Game Mechanics

Ball Physics System

The ball follows simplified physics with collision responses:

flowchart TD
    A[Frame Start] --> B[Position Update]
    B --> C[Boundary Check]
    C --> D{Collision?}
    D -->|Yes| E[Velocity Inversion]
    D -->|No| F[Paddle Check]
    F --> G{Collision?}
    G -->|Yes| H[Angled Rebound]
    G -->|No| I[Brick Check]
    I --> J{Collision?}
    J -->|Yes| K[Brick Destruction]
    J -->|No| A
Enter fullscreen mode Exit fullscreen mode

Movement Implementation

void Ball::update(float deltaTime, const sf::Vector2u& windowSize) {
    sf::Vector2f position = getPosition();
    position += velocity * deltaTime;

    // Boundary collision handling
    if (position.x <= 0 || position.x + getBounds().width >= windowSize.x) {
        invertX();
    }
    if (position.y <= 0) {
        invertY();
    }

    visual.setPosition(position);
}
Enter fullscreen mode Exit fullscreen mode

DeltaTime Explanation:

  • Represents time since last frame
  • Ensures consistent movement speed across hardware
  • At 60 FPS, deltaTime ≈ 0.0167 seconds

Intelligent Paddle Control

The paddle features responsive yet constrained movement:

void Paddle::update(float deltaTime, const sf::Vector2u& windowSize) {
    sf::Vector2f position = getPosition();

    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
        position.x -= speed * deltaTime;
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
        position.x += speed * deltaTime;
    }

    // Screen boundary enforcement
    position.x = std::clamp(position.x, 0.0f, 
                          windowSize.x - getBounds().width);
    visual.setPosition(position);
}
Enter fullscreen mode Exit fullscreen mode

Advanced Paddle Rebounds

The ball's rebound angle varies based on impact position:

if (ball.getBounds().intersects(paddle.getBounds())) {
    ball.invertY();

    float ballCenterX = ball.getPosition().x + ball.getBounds().width/2;
    float paddleCenterX = paddle.getPosition().x + paddle.getBounds().width/2;
    float offset = (ballCenterX - paddleCenterX) / (paddle.getBounds().width/2);

    ball.velocity.x = ball.baseSpeed * offset * 0.75f;
    ball.velocity.y = -std::abs(ball.velocity.y);
}
Enter fullscreen mode Exit fullscreen mode

World Building Systems

Procedural Brick Generation

The game creates organised brick formations:

void generateBricks() {
    bricks.clear();
    const int rows = 8, cols = 10;
    const float brickWidth = 60, brickHeight = 25;
    const float padding = 5;

    const float startX = (800 - (cols * brickWidth + (cols-1)*padding))/2;
    const float startY = 50;

    for (int row = 0; row < rows; row++) {
        for (int col = 0; col < cols; col++) {
            Brick brick;
            brick.setPosition(startX + col*(brickWidth+padding),
                            startY + row*(brickHeight+padding));
            brick.pointValue = (rows - row) * 10;
            brick.visual.setColor(rowColors[row % rowColors.size()]);
            bricks.push_back(brick);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Lives and Progression Systems

// Ball loss handling
if (ball.isOOB(window.getSize())) {
    lives--;
    if (lives <= 0) gameState = GAME_OVER;
    else ball.reset(400, 300);
}

// Level completion
if (std::all_of(bricks.begin(), bricks.end(), 
               [](const Brick& b){ return b.destroyed; })) {
    level++;
    score += 1000 * level;
    if (level <= 3) {
        generateBricks();
        ball.increaseSpeed(50);
    } else {
        gameState = VICTORY;
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Development Insights

This project demonstrates:

  1. OOP Principles: Encapsulation, modular design
  2. Game Physics: Collision detection, movement systems
  3. State Management: Complex game flow control
  4. Procedural Generation: Dynamic content creation
  5. Performance Optimization: Time-based movement
  6. UX Design: Player feedback systems

The complete implementation showcases how classic arcade games combine simple concepts into engaging experiences while teaching fundamental programming techniques.

Top comments (0)