DEV Community

Cover image for From Zero to Pong: My First SFML Project
ルーカス ビークラ
ルーカス ビークラ

Posted on

From Zero to Pong: My First SFML Project

I’ve had the phrase 下学上達 (kagaku joutatsu) on my desktop background for years. Loosely translated, it means that to gain real understanding, you should start with the familiar—the most fundamental of fundamentals. Human learning begins with imitation: babies mimic their parents, musicians play classical pieces, and painters do master studies. They say imitation is the sincerest form of flattery, but it's also one of the most effective ways to learn something new.


Why Pong?

In the early 1970s, Allan Alcorn was given a training assignment by his boss, Nolan Bushnell: recreate a simple sports game for the then-popular Magnavox Odyssey. Alcorn’s implementation turned out so polished that Bushnell decided to release it commercially. That game—Pong—became a breakout hit, helping launch Atari as a household name and trailblazer in the fledgling video game industry.

In a sense, Pong was a clone from the start. It mimicked Magnavox’s “Table Tennis,” which was itself a digital take on the real-life sport. So cloning Pong doesn’t feel derivative; if anything, it feels like I’m paying homage.


Why Now?

This idea had been kicking around in my head since I first encountered CS50’s Introduction to Game Development. That course uses a case-study approach to rebuild classic games in Lua, giving students hands-on exposure to core game design principles. I liked the concept but didn’t want to pick up Lua on top of everything else I was learning.

Still, the idea stuck with me. I figured that programming these games in a different language would offer twofold benefits: a deeper understanding of both the game mechanics and the syntax of the language. Less passive consumption, more deliberate practice. Two birds, one stone.

That brings us to now. My experience with both C++ and SFML was, to put it generously, minimal. I’d skimmed a few chapters of LearnCPP.com and watched a YouTube video on linking SFML to Visual Studio. That was about it.

But I’m American—unearned confidence is practically the national pastime.


🔗 Project Link

Want to follow along or look at the code?

👉 GitHub Repo: BikuraStudios/Pong-Clone

Built with SFML 3.0 in Visual Studio 2022

~15 hours of work over a week of evenings


Development Breakdown


Window Setup

Getting the game window up and running was the first step. This was mostly copy-paste from tutorials, not an original coding challenge.

sf::RenderWindow window(sf::VideoMode(800, 600), "Pong");
window.setFramerateLimit(60);
Enter fullscreen mode Exit fullscreen mode

Paddles

Next up: paddles. I drew simple white rectangles and placed them on screen.

sf::RectangleShape paddle(sf::Vector2f(10.f, 100.f));
paddle.setPosition(50.f, 250.f);
paddle.setFillColor(sf::Color::White);
Enter fullscreen mode Exit fullscreen mode

(Yes, the magic numbers have already started creeping in.)


Movement

To handle input without tying it to frame rate, I introduced deltaTime and multiplied it by an arbitrary speed.

if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
    paddle.move(0.f, -paddleSpeed * deltaTime);

if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
    paddle.move(0.f, paddleSpeed * deltaTime);
Enter fullscreen mode Exit fullscreen mode

Ball Mechanics

The ball logic was similar to the paddles—define it, position it, assign velocity.

sf::CircleShape ball(10.f);
ball.setPosition(400.f, 300.f);
sf::Vector2f ballVelocity(200.f, 200.f);
ball.move(ballVelocity * deltaTime);
Enter fullscreen mode Exit fullscreen mode

In my code, ballVelocity is technically broken. I tried to randomize the starting direction but never seeded the random number generator. So the ball moves in the same direction every time—hardly "random."


Collision Detection

This is where things got messy.

I probably spent four hours going back and forth between YouTube videos and forums trying to write a collision function that didn't throw errors left and right. Even copilot was stuck, and I got into a fight with a hallucinating AI over whether a function existed or not. Eventually I came to realize that the code for intersects was updated for the SFML 3.0 release. Thus, what used to be typed out like this:

if (playerRect.getGlobalBounds().intersects(targetRect.getGlobalBounds()))
    Collision;
Enter fullscreen mode Exit fullscreen mode

Is now formatted more like this:

if (playerRect.getGlobalBounds().findIntersection(targetRect.getGlobalBounds()))
    Collision;
Enter fullscreen mode Exit fullscreen mode

I probably should have figured this out faster, but you live and learn.

Anyway, here's the dvd screensaver I made when I figured out what I was actually supposed to be doing:

A white ball moves across the screen and bounces off the edges of the window

Eventually, I settled on basic AABB (Axis-Aligned Bounding Box) collision. It’s functional, though not perfect. Sometimes the ball clips into paddles and accelerates wildly before rocketing off.

sf::FloatRect leftPaddleBounds = paddle.getGlobalBounds();
sf::FloatRect rightPaddleBounds = paddle2.getGlobalBounds();
sf::FloatRect ballBounds = ball.getGlobalBounds();

if (const std::optional intersection = ballBounds.findIntersection(leftPaddleBounds)) {
    sound_leftPaddle.play();
    ballVelocity.x *= -1.1f;
}

if (const std::optional intersection = ballBounds.findIntersection(rightPaddleBounds)) {
    sound_rightPaddle.play();
    ballVelocity.x *= -1.1f;
}
Enter fullscreen mode Exit fullscreen mode

We’ll revisit this when I’m more comfortable with collision handling.


Scoring System

If the ball goes off-screen, award a point and reset:

if (ball.getPosition().x < 0.f) {
    player2Score += 1;
    resetBall();
}

if (ball.getPosition().x > window.getSize().x) {
    player1Score += 1;
    resetBall();
}
Enter fullscreen mode Exit fullscreen mode

Then update the displayed text:

player2ScoreString = std::to_string(player2Score);
scoreTextLeft.setString(player2ScoreString);

player1ScoreString = std::to_string(player1Score);
scoreTextRight.setString(player1ScoreString);
Enter fullscreen mode Exit fullscreen mode

Pause Functionality

A proper pause system needs a state machine, but I used a simple boolean:

bool isPaused = false;

if (!isPaused) {
    // game loop
}

if (sf::Keyboard::isKeyPressed(sf::Keyboard::P)) {
    isPaused = !isPaused;
}
Enter fullscreen mode Exit fullscreen mode

It’s not elegant, but it works.


Game Reset

I allowed resets only after a player hits 10 points. When startNewGame is true, everything resets:

if (startNewGame) {
    player1Score = 0;
    player2Score = 0;
    // Update score text

    // Reset positions
    ball.setPosition({400.f, 300.f});
    paddle.setPosition({50.f, 250.f});
    paddle2.setPosition({725.f, 250.f});

    // Reassign velocity
    directionX = randomDirection();
    directionY = randomDirection();
    ballVelocity = {200.f * directionX, 200.f * directionY};

    startNewGame = false;
}
Enter fullscreen mode Exit fullscreen mode

In the future, I'd like a reset feature that works at any time.


Buggier Than an Entomologist’s Sketchbook

A non-exhaustive bug list:

  • Outdated documentation for window creation and input

  • Paddles scrolling off-screen

  • Ball flying offscreen at terminal velocity

  • Multi-line if statements without {}

  • Function definitions inside main()

  • Confusion with sf::Vector2f arguments

  • Bounding boxes conceptually clear but poorly implemented

  • Difficulty loading font files

  • Failing to update score text

  • Overcomplicated state toggling

Here's a particularly bad example of logic from early on:

if (keyPressed->scancode == sf::Keyboard::Scancode::Space) {
    if (Pause == true)
        Pause = false;
    if (Pause == false)
        Pause = true;
}
Enter fullscreen mode Exit fullscreen mode

Eventually simplified to:

if (keyPressed->scancode == sf::Keyboard::Scancode::Space)
    Pause = !Pause;
Enter fullscreen mode Exit fullscreen mode

Code Review Summary

I don't have any programmers in my immediate circle, so I threw my code at an AI and told it to give me a review. Here are the results:

Major Issues:

  1. main() was 600+ lines with no decomposition

  2. Game logic, input, and rendering were all in main()

  3. Repetitive code (WET)

  4. Inefficient event handling

Minor Issues:

  1. Excessive commented-out code

  2. Overuse of magic numbers

  3. No error handling for assets

  4. Hard-to-debug logic due to lack of toggles


So what did we learn?

All in all, this was a solid first project. I learned a ton and came away with something playable. More importantly, I proved to myself that I could start and finish a game. That confidence boost alone made the whole process worth it.

That’s all for this week, folks; see you next week when we look at Flappy Bird.

Sayonara.

Top comments (0)