C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
175: Building a Small Game with a Game Loop
You've probably already written plenty of programs that wait for user input, process it, and then stop. But games don't work like that. A game is essentially a high-speed loop that keeps running whether the player is pressing a key or not. The world keeps turning, the enemies keep moving, and the timer keeps ticking.
When I first started writing game loops, I made a mistake that almost every beginner makes. I thought "once per frame" was a reliable unit of measurement. Take a look at this snippet from a project I worked on years ago:
while (gameRunning) {
handleInput();
// Move the player to the right
player.x += 5;
updateWorld();
render();
}
The Teleporting Player Problem
On my old laptop, this code worked fine. The player moved at a reasonable pace. But when I ran it on a newer machine with a faster CPU, the player didn't just move—they teleported. They flew off the screen in a fraction of a second. Conversely, if the computer lagged, the player slowed down to a crawl.
The problem is that player.x += 5 is tied to the frame rate. If your computer can push 500 frames per second, you're moving 5 pixels 500 times. If it can only push 30, you're moving 150 pixels per second. Your game's physics are currently held hostage by the hardware's speed, which is a nightmare for gameplay balance.
Scaling Movement by Delta Time
To fix this, we need to stop thinking in "pixels per frame" and start thinking in "pixels per second." We do this using something called Delta Time (dt)—the amount of time that actually elapsed since the last frame was processed.
Here is how we fix the logic using the <chrono> library to track real-world time:
#include <chrono>
auto lastTime = std::chrono::high_resolution_clock::now();
float playerSpeed = 300.0f; // 300 pixels per second
while (gameRunning) {
auto currentTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> elapsed = currentTime - lastTime;
float dt = elapsed.count();
lastTime = currentTime;
handleInput();
// Now movement is independent of frame rate
player.x += playerSpeed * dt;
updateWorld(dt);
render();
}
By multiplying the speed by dt, we've normalized the movement. If the frame takes 0.016 seconds (roughly 60 FPS), the player moves a small amount. If the frame takes 0.1 seconds (a massive lag spike), the player moves a larger chunk to "catch up." The result? The player crosses the screen in exactly one second, regardless of whether the computer is a toaster or a supercomputer.
Structuring the Heartbeat of Your Game
Now that we have timing sorted, let's look at the overall architecture. A professional game loop is usually split into three distinct phases. I find that if you mix these, your code becomes a spaghetti mess very quickly.
- Process Input: Capture keyboard, mouse, or controller states. Do not put game logic here; just record what the player wants to do.
- Update: This is where the "simulation" happens. Move characters, check for collisions, and update timers. This phase must accept
dtas a parameter so everything stays synchronized. - Render: Draw everything to the screen. This phase should be "read-only"—it shouldn't change the player's position or health; it just displays the current state.
Handling Input Without Blocking
One last thing: if you use std::cin, your game loop will freeze every time it waits for the user to press Enter. That's a deal-breaker for a game. In a real-world scenario, you'd use a library like SDL, SFML, or GLFW to poll the keyboard state. For our purposes in C++, you need to ensure your input method is non-blocking, meaning the code checks "is this key down right now?" and immediately moves on if the answer is no.
📋 Practical Task
Exercise: Implementing a Frame-Independent Enemy Patrol
You are building a simple survival game. You have an enemy that needs to patrol back and forth between two points on the X-axis (leftBound = 100 and rightBound = 500).
Currently, the enemy movement is broken because it's tied to the frame rate. Your task is to rewrite the update logic to use Delta Time.
Requirements:
- Create a
float enemyX = 100.0f;and afloat enemySpeed = 200.0f;(pixels per second). - Create a
bool movingRight = true;to track direction. - Implement a loop simulation where you calculate
dtusingstd::chrono. - Update
enemyXbased onenemySpeedanddt. - Add logic to flip
movingRightwhen the enemy hits either 100 or 500. - Print the
enemyXposition to the console each frame to verify it's moving smoothly.
There are no comments for now.