Docs
README
Graphics Programming in C++
Table of Contents
- •Graphics Libraries Overview
- •The Game Loop
- •Coordinate Systems
- •SFML Basics
- •Drawing Shapes
- •Textures and Sprites
- •Animation
- •Input Handling
- •Text Rendering
Graphics Libraries Overview
Popular Options
| Library | Purpose | Level | Best For |
|---|---|---|---|
| SFML | 2D games/graphics | Beginner | 2D games, multimedia apps |
| SDL2 | 2D games/graphics | Beginner | Cross-platform, game engines |
| Raylib | 2D/3D games | Beginner | Learning, prototyping |
| OpenGL | 2D/3D graphics | Advanced | Custom rendering, 3D |
| Vulkan | High-perf graphics | Expert | AAA games, performance |
Installation (SFML on Ubuntu/Debian)
# Install SFML development libraries
sudo apt install libsfml-dev
# Compile with SFML
g++ -std=c++17 main.cpp -lsfml-graphics -lsfml-window -lsfml-system
The Game Loop
Every graphical application runs a continuous loop that processes input, updates state, and renders graphics.
The Classic Game Loop
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE GAME LOOP │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ INITIALIZATION │ │
│ │ - Create window│ │
│ │ - Load assets │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ MAIN LOOP │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ 1. PROCESS │ Poll keyboard, mouse, window events │ │ │
│ │ │ │ INPUT │ Handle user actions │ │ │
│ │ │ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ 2. UPDATE │ Move objects, apply physics │ │ │
│ │ │ │ STATE │ Check collisions, game logic │ │ │
│ │ │ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ 3. RENDER │ Clear screen │ │ │
│ │ │ │ GRAPHICS │ Draw all objects │ │ │
│ │ │ │ │ Display to screen │ │ │
│ │ │ └──────┬───────┘ │ │ │
│ │ │ │ │ │ │
│ │ └──────────┴────── Loop continues ─────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────── Until window closed ──────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ CLEANUP │ │
│ │ - Free memory │ │
│ │ - Close window │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Frame Rate and Delta Time
┌─────────────────────────────────────────────────────────────────────────────┐
│ DELTA TIME EXPLAINED │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Problem: Different computers run at different speeds! │
│ │
│ Fast Computer (120 FPS): Slow Computer (30 FPS): │
│ ───────────────────────── ───────────────────────── │
│ Frame 1: move 1 pixel Frame 1: move 1 pixel │
│ Frame 2: move 1 pixel Frame 2: move 1 pixel │
│ ... ... │
│ 120 frames = 120 pixels 30 frames = 30 pixels │
│ │
│ SAME TIME, DIFFERENT DISTANCE! ❌ │
│ │
│ ───────────────────────────────────────────────────────────────────────── │
│ │
│ Solution: Use DELTA TIME (dt) = time since last frame │
│ │
│ movement = velocity * dt │
│ │
│ Fast Computer (dt = 0.0083s): Slow Computer (dt = 0.033s): │
│ ───────────────────────────── ───────────────────────── │
│ Frame 1: 100 * 0.0083 = 0.83px Frame 1: 100 * 0.033 = 3.3px │
│ Frame 2: 100 * 0.0083 = 0.83px Frame 2: 100 * 0.033 = 3.3px │
│ ... ... │
│ 120 frames = ~100 pixels 30 frames = ~100 pixels │
│ │
│ SAME TIME, SAME DISTANCE! ✅ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Coordinate Systems
Screen Coordinate System
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCREEN COORDINATE SYSTEM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ (0,0) ────────────────────────────────────────────────► X (width) │
│ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │
│ │ │ WINDOW │ │
│ │ │ │ │
│ │ │ (100, 50) ● │ │
│ │ │ │ │
│ │ │ (400, 300) ● │ │
│ │ │ │ │
│ │ │ (700, 500) ● │ │
│ │ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │
│ ▼ │
│ Y (height) │
│ │
│ ⚠️ NOTE: Y increases DOWNWARD (opposite of math coordinates!) │
│ │
│ Window Size: 800 x 600 │
│ • Top-left corner: (0, 0) │
│ • Top-right corner: (799, 0) │
│ • Bottom-left corner: (0, 599) │
│ • Bottom-right corner: (799, 599) │
│ • Center: (400, 300) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Shape Positioning and Origin
┌─────────────────────────────────────────────────────────────────────────────┐
│ SHAPE ORIGIN AND POSITIONING │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ By Default: Origin is at TOP-LEFT of shape │
│ │
│ setPosition(100, 50) │
│ │ │
│ ▼ │
│ (100,50) ●─────────────┐ │
│ │ ████████████ │ Shape drawn to the RIGHT and DOWN │
│ │ ████████████ │ from position │
│ │ ████████████ │ │
│ └──────────────┘ │
│ │
│ ───────────────────────────────────────────────────────────────────────── │
│ │
│ With setOrigin(): Origin can be anywhere │
│ │
│ setOrigin(width/2, height/2) // Center origin │
│ setPosition(100, 50) │
│ │
│ ┌──────────────┐ │
│ │ ████████████ │ │
│ │ ████●████████ │ ← Position (100,50) is now at CENTER │
│ │ ████████████ │ │
│ └──────────────┘ │
│ │
│ Centering is useful for: │
│ • Rotation (rotates around origin) │
│ • Scaling (scales from origin) │
│ • Positioning objects by center │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
SFML Basics
Window Creation and Game Loop
#include <SFML/Graphics.hpp>
int main() {
// Create a window (width, height, title)
sf::RenderWindow window(sf::VideoMode(800, 600), "My Game");
// Optional: Set frame rate limit
window.setFramerateLimit(60);
// Clock for delta time
sf::Clock clock;
// Main game loop
while (window.isOpen()) {
// 1. PROCESS INPUT (events)
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
if (event.type == sf::Event::KeyPressed) {
if (event.key.code == sf::Keyboard::Escape)
window.close();
}
}
// 2. UPDATE (game logic)
float dt = clock.restart().asSeconds();
// Update game objects here using dt
// 3. RENDER (drawing)
window.clear(sf::Color::Black); // Clear with color
// Draw all objects here
window.display(); // Show on screen
}
return 0;
}
The Rendering Pipeline
┌─────────────────────────────────────────────────────────────────────────────┐
│ SFML RENDERING PIPELINE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ window.clear() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BACK BUFFER │ │
│ │ (invisible, being drawn to) │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ Cleared to black (or specified color) │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ window.draw(shape1) │
│ window.draw(shape2) ← Drawing happens HERE (to back buffer) │
│ window.draw(sprite) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ BACK BUFFER │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ ● ▲ │ │ │
│ │ │ shape1 sprite │ │ │
│ │ │ ■ shape2 │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ window.display() ← SWAP buffers (back → front) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ FRONT BUFFER │ │
│ │ (visible on screen!) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ This double-buffering prevents screen tearing and flickering! │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Color
// Predefined colors
sf::Color red = sf::Color::Red;
sf::Color green = sf::Color::Green;
sf::Color blue = sf::Color::Blue;
sf::Color white = sf::Color::White;
sf::Color black = sf::Color::Black;
sf::Color yellow = sf::Color::Yellow;
sf::Color cyan = sf::Color::Cyan;
sf::Color magenta = sf::Color::Magenta;
sf::Color transparent = sf::Color::Transparent;
// Custom RGB color
sf::Color purple(128, 0, 255); // RGB
// Custom RGBA color (with transparency)
sf::Color semiTransparent(255, 0, 0, 128); // 50% transparent red
// R G B Alpha
// 0=invisible, 255=opaque
Drawing Shapes
Rectangle
sf::RectangleShape rect(sf::Vector2f(100, 50)); // width, height
rect.setPosition(200, 150);
rect.setFillColor(sf::Color::Blue);
rect.setOutlineColor(sf::Color::White);
rect.setOutlineThickness(2);
window.draw(rect);
Circle
sf::CircleShape circle(50); // radius
circle.setPosition(300, 200);
circle.setFillColor(sf::Color::Green);
circle.setPointCount(100); // smoothness
window.draw(circle);
Convex Shapes
sf::ConvexShape triangle;
triangle.setPointCount(3);
triangle.setPoint(0, sf::Vector2f(0, 0));
triangle.setPoint(1, sf::Vector2f(100, 0));
triangle.setPoint(2, sf::Vector2f(50, 100));
triangle.setFillColor(sf::Color::Yellow);
window.draw(triangle);
Lines
sf::Vertex line[] = {
sf::Vertex(sf::Vector2f(10, 10), sf::Color::Red),
sf::Vertex(sf::Vector2f(200, 200), sf::Color::Blue)
};
window.draw(line, 2, sf::Lines);
Textures and Sprites
Loading Textures
sf::Texture texture;
if (!texture.loadFromFile("player.png")) {
// Handle error
return -1;
}
sf::Sprite sprite;
sprite.setTexture(texture);
sprite.setPosition(100, 100);
sprite.setScale(2.0f, 2.0f); // 2x size
window.draw(sprite);
Texture Regions
// Use part of texture (sprite sheet)
sprite.setTextureRect(sf::IntRect(0, 0, 32, 32)); // x, y, w, h
Transform
sprite.setOrigin(16, 16); // Center of rotation
sprite.setRotation(45); // Degrees
sprite.setScale(1.5f, 1.5f);
sprite.move(10, 0); // Relative movement
Animation
Frame-based Animation
class Animation {
sf::Sprite& sprite;
sf::IntRect frameRect;
int currentFrame = 0;
int frameCount;
float frameTime;
float elapsedTime = 0;
public:
Animation(sf::Sprite& s, int frames, float duration,
int frameWidth, int frameHeight)
: sprite(s), frameCount(frames),
frameTime(duration / frames),
frameRect(0, 0, frameWidth, frameHeight) {}
void update(float dt) {
elapsedTime += dt;
if (elapsedTime >= frameTime) {
elapsedTime = 0;
currentFrame = (currentFrame + 1) % frameCount;
frameRect.left = currentFrame * frameRect.width;
sprite.setTextureRect(frameRect);
}
}
};
Delta Time
sf::Clock clock;
while (window.isOpen()) {
float dt = clock.restart().asSeconds();
// Update with delta time
position += velocity * dt;
}
Input Handling
Keyboard
// Real-time (continuous)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
player.move(-speed * dt, 0);
}
// Event-based (single press)
if (event.type == sf::Event::KeyPressed) {
if (event.key.code == sf::Keyboard::Space) {
jump();
}
}
Mouse
// Position
sf::Vector2i mousePos = sf::Mouse::getPosition(window);
// Buttons
if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
shoot();
}
// Events
if (event.type == sf::Event::MouseButtonPressed) {
if (event.mouseButton.button == sf::Mouse::Left) {
click(event.mouseButton.x, event.mouseButton.y);
}
}
Game Loop
Fixed Timestep
int main() {
sf::RenderWindow window(sf::VideoMode(800, 600), "Game");
const float TIMESTEP = 1.0f / 60.0f;
float accumulator = 0;
sf::Clock clock;
while (window.isOpen()) {
float dt = clock.restart().asSeconds();
accumulator += dt;
// Handle events
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
// Fixed update
while (accumulator >= TIMESTEP) {
update(TIMESTEP);
accumulator -= TIMESTEP;
}
// Render
window.clear();
render(window);
window.display();
}
return 0;
}
Simple Player Class
class Player {
sf::Sprite sprite;
sf::Texture texture;
float speed = 200.0f;
public:
bool init(const string& file) {
if (!texture.loadFromFile(file)) return false;
sprite.setTexture(texture);
return true;
}
void update(float dt) {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
sprite.move(-speed * dt, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
sprite.move(speed * dt, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
sprite.move(0, -speed * dt);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
sprite.move(0, speed * dt);
}
void draw(sf::RenderWindow& window) {
window.draw(sprite);
}
};
Text Rendering
sf::Font font;
if (!font.loadFromFile("arial.ttf")) {
// Handle error
}
sf::Text text;
text.setFont(font);
text.setString("Hello World!");
text.setCharacterSize(24);
text.setFillColor(sf::Color::White);
text.setPosition(100, 100);
window.draw(text);
Quick Reference
// Window
sf::RenderWindow window(sf::VideoMode(w, h), "Title");
window.clear();
window.draw(shape);
window.display();
// Shapes
sf::RectangleShape rect(sf::Vector2f(w, h));
sf::CircleShape circle(radius);
shape.setPosition(x, y);
shape.setFillColor(color);
// Sprites
sf::Texture tex; tex.loadFromFile("img.png");
sf::Sprite sprite; sprite.setTexture(tex);
// Input
sf::Keyboard::isKeyPressed(sf::Keyboard::Space);
sf::Mouse::getPosition(window);
// Time
sf::Clock clock;
float dt = clock.restart().asSeconds();
Compile & Run
# Install SFML first: sudo apt install libsfml-dev
g++ -std=c++17 -Wall examples.cpp -lsfml-graphics -lsfml-window -lsfml-system -o examples
./examples