Tutorials

Step-by-step guides to help you master SSOEngine and build amazing games

🎓 Learning Path

Follow these tutorials in order to build a solid foundation with SSOEngine:

1

Getting Started

Setup and your first game

2

Player Movement

Input and character control

3

Camera System

Following and effects

4

UI & Menus

Building interfaces

Tutorial 1: Getting Started

🎯 Your First SSOEngine Game

In this tutorial, you'll create a simple game that displays a colored window and responds to basic input.

Step 1: Setup Your Project

Make sure you have SSOEngine installed and working:

  1. Navigate to SSOEngine/01_Core
  2. Run build.bat to test the build system
  3. Open game.h in your favorite editor
  4. Open main.cpp to see the entry point

Step 2: Understanding the Structure

The basic SSOEngine project has these key files:

main.cpp      // Game entry point and main loop
game.h        // Game logic and global variables
scripts/      // Game-specific code (player, enemies, etc.)
tools/        // Engine subsystems (camera, timer, UI, etc.)
assets/       // Your game assets (images, sounds, etc.)
build.bat     // Automated build script

Step 3: Basic Game Loop

Let's examine the main game loop in main.cpp:

#include "raylib.h"
#include "game.h"

int main(void) {
    // 1. Initialize window
    const int screenWidth = 1280;
    const int screenHeight = 720;
    
    InitWindow(screenWidth, screenHeight, "My First Game");
    SetTargetFPS(60);

    // 2. Start game
    Start();

    // 3. Main game loop
    while (!WindowShouldClose()) {
        float deltaTime = GetFrameTime();

        // Update phase
        Update(deltaTime);

        // Render phase
        BeginDrawing();
        ClearBackground(SKYBLUE);
        
        // Draw game objects here
        DrawGame();
        
        EndDrawing();
    }

    // 4. Cleanup
    CloseWindow();
    return 0;
}

Step 4: Game Logic in game.h

Now let's look at game.h and modify it:

#ifndef GAME_H
#define GAME_H

#include "raylib.h"

// Game state
struct Player {
    Vector2 position;
    float radius;
    Color color;
};

inline Player player = {{640, 360}, 20, BLUE};

// Game functions
inline void Start() {
    // Initialize game here
    player.position = {640, 360};
    player.radius = 20;
    player.color = BLUE;
}

inline void Update(float deltaTime) {
    // Update game logic here
    // Move player with arrow keys
    if (IsKeyDown(KEY_RIGHT)) player.position.x += 5;
    if (IsKeyDown(KEY_LEFT)) player.position.x -= 5;
    if (IsKeyDown(KEY_UP)) player.position.y -= 5;
    if (IsKeyDown(KEY_DOWN)) player.position.y += 5;
    
    // Keep player on screen
    player.position.x = Clamp(player.position.x, player.radius, 1280 - player.radius);
    player.position.y = Clamp(player.position.y, player.radius, 720 - player.radius);
}

inline void DrawGame() {
    // Draw game objects
    DrawCircleV(player.position, player.radius, player.color);
    
    // Draw some text
    DrawText("Use arrow keys to move!", 10, 10, 20, BLACK);
    DrawText(TextFormat("Position: %.0f, %.0f", player.position.x, player.position.y), 
             10, 40, 20, BLACK);
}

#endif

Step 5: Build and Run

Save your changes and run the build:

build.bat

Your game should now run with a blue circle that you can move with arrow keys!

✅ Congratulations!

You've created your first SSOEngine game! You've learned:

  • Basic project structure
  • Main game loop
  • Input handling
  • Basic rendering
  • Building and running

Tutorial 2: Advanced Player Movement

🎮 Smooth Character Control

In this tutorial, you'll implement smooth player movement with acceleration, deceleration, and delta-time independence.

Step 1: Enhanced Player Structure

Update your player structure in game.h:

struct Player {
    Vector2 position;
    Vector2 velocity;
    Vector2 acceleration;
    float maxSpeed;
    float accelerationRate;
    float friction;
    float radius;
    Color color;
};

inline Player player = {
    .position = {640, 360},
    .velocity = {0, 0},
    .acceleration = {0, 0},
    .maxSpeed = 300.0f,
    .accelerationRate = 1000.0f,
    .friction = 800.0f,
    .radius = 20,
    .color = BLUE
};

Step 2: Physics-Based Movement

Implement smooth movement in the Update function:

inline void Update(float deltaTime) {
    // Reset acceleration
    player.acceleration = {0, 0};
    
    // Apply input acceleration
    if (IsKeyDown(KEY_RIGHT)) player.acceleration.x += player.accelerationRate;
    if (IsKeyDown(KEY_LEFT)) player.acceleration.x -= player.accelerationRate;
    if (IsKeyDown(KEY_UP)) player.acceleration.y -= player.accelerationRate;
    if (IsKeyDown(KEY_DOWN)) player.acceleration.y += player.accelerationRate;
    
    // Apply friction (opposing force)
    if (player.velocity.x != 0) {
        player.acceleration.x -= (player.velocity.x / fabs(player.velocity.x)) * player.friction;
    }
    if (player.velocity.y != 0) {
        player.acceleration.y -= (player.velocity.y / fabs(player.velocity.y)) * player.friction;
    }
    
    // Update velocity (v = v0 + a*t)
    player.velocity.x += player.acceleration.x * deltaTime;
    player.velocity.y += player.acceleration.y * deltaTime;
    
    // Limit to max speed
    float speed = sqrt(player.velocity.x * player.velocity.x + player.velocity.y * player.velocity.y);
    if (speed > player.maxSpeed) {
        player.velocity.x = (player.velocity.x / speed) * player.maxSpeed;
        player.velocity.y = (player.velocity.y / speed) * player.maxSpeed;
    }
    
    // Update position (x = x0 + v*t)
    player.position.x += player.velocity.x * deltaTime;
    player.position.y += player.velocity.y * deltaTime;
    
    // Screen boundaries
    player.position.x = Clamp(player.position.x, player.radius, 1280 - player.radius);
    player.position.y = Clamp(player.position.y, player.radius, 720 - player.radius);
    
    // Stop at boundaries
    if (player.position.x <= player.radius || player.position.x >= 1280 - player.radius) {
        player.velocity.x = 0;
    }
    if (player.position.y <= player.radius || player.position.y >= 720 - player.radius) {
        player.velocity.y = 0;
    }
}

Step 3: Visual Feedback

Add visual indicators for movement:

inline void DrawGame() {
    // Draw trail effect
    static Vector2 trailPosition[10];
    static int trailIndex = 0;
    
    trailPosition[trailIndex] = player.position;
    trailIndex = (trailIndex + 1) % 10;
    
    // Draw trail
    for (int i = 0; i < 10; i++) {
        float alpha = (float)i / 10.0f;
        Color trailColor = Fade(player.color, alpha * 0.3f);
        DrawCircleV(trailPosition[i], player.radius * (0.5f + alpha * 0.5f), trailColor);
    }
    
    // Draw player
    DrawCircleV(player.position, player.radius, player.color);
    
    // Draw velocity indicator
    if (fabs(player.velocity.x) > 1 || fabs(player.velocity.y) > 1) {
        Vector2 endPos = {
            player.position.x + player.velocity.x * 0.1f,
            player.position.y + player.velocity.y * 0.1f
        };
        DrawLineV(player.position, endPos, RED);
    }
    
    // Draw UI
    DrawText("Smooth Movement Demo", 10, 10, 20, BLACK);
    DrawText(TextFormat("Speed: %.1f", 
             sqrt(player.velocity.x * player.velocity.x + player.velocity.y * player.velocity.y)), 
             10, 40, 20, BLACK);
    DrawText("Use arrow keys for smooth movement!", 10, 70, 20, BLACK);
}

Step 4: Add Diagonal Movement Fix

Prevent faster diagonal movement:

// In Update function, after applying input acceleration:
// Normalize diagonal input
if (player.acceleration.x != 0 && player.acceleration.y != 0) {
    float length = sqrt(player.acceleration.x * player.acceleration.x + 
                       player.acceleration.y * player.acceleration.y);
    player.acceleration.x = (player.acceleration.x / length) * player.accelerationRate;
    player.acceleration.y = (player.acceleration.y / length) * player.accelerationRate;
}

🎯 What You've Learned

  • Physics-based movement with acceleration and friction
  • Delta-time independent game logic
  • Speed limiting and boundary collision
  • Visual feedback with trail effects
  • Diagonal movement normalization

Tutorial 3: Camera System

📹 Dynamic Camera Following

Learn to use SSOEngine's camera system for smooth following, zoom, and screen effects.

Step 1: Include Camera System

Add the camera header to game.h:

#include "raylib.h"
#include "tools/sso_camera.h"
#include "tools/sso_timer.h"

// Camera setup
SSO::Camera mainCam({0, 0}, 1280, 720);

// Game world (larger than screen)
inline Rectangle worldBounds = {-1000, -1000, 3000, 3000};

Step 2: Initialize Camera

Set up the camera in the Start function:

inline void Start() {
    // Initialize player
    player.position = {0, 0};  // Center of world
    
    // Initialize camera
    mainCam = SSO::Camera({0, 0}, 1280, 720);
    mainCam.SetBounds(worldBounds);
    mainCam.SetZoom(1.0f);
    
    // Camera smoothing (0.0 = instant, 1.0 = very smooth)
    mainCam.SetSmoothing(0.1f);
}

Step 3: Update Camera

Update camera to follow the player:

inline void Update(float deltaTime) {
    // Update player movement (from previous tutorial)
    UpdatePlayer(deltaTime);
    
    // Camera controls
    // Follow player
    mainCam.Follow(player.position);
    
    // Zoom controls
    if (IsKeyPressed(KEY_EQUAL)) {  // + key
        float currentZoom = mainCam.GetZoom();
        mainCam.SetZoom(currentZoom + 0.1f);
    }
    if (IsKeyPressed(KEY_MINUS)) {
        float currentZoom = mainCam.GetZoom();
        mainCam.SetZoom(currentZoom - 0.1f);
    }
    
    // Screen shake on spacebar
    if (IsKeyPressed(KEY_SPACE)) {
        mainCam.Shake(10.0f, 0.3f);
    }
    
    // Update camera state
    mainCam.Update(deltaTime);
}

// Separate player update function
inline void UpdatePlayer(float deltaTime) {
    // (Same movement code from previous tutorial)
    // ... player movement logic ...
}

Step 4: Render with Camera

Use camera for game rendering:

inline void DrawGame() {
    // Begin camera mode
    BeginMode2D(mainCam.GetCamera2D());
    
    // Draw world boundaries
    DrawRectangleLines(worldBounds.x, worldBounds.y, worldBounds.width, worldBounds.height, GREEN);
    
    // Draw grid for reference
    for (int x = -1000; x <= 2000; x += 100) {
        DrawLine(x, -1000, x, 2000, Fade(GRAY, 0.3f));
    }
    for (int y = -1000; y <= 2000; y += 100) {
        DrawLine(-1000, y, 2000, y, Fade(GRAY, 0.3f));
    }
    
    // Draw player
    DrawCircleV(player.position, player.radius, player.color);
    
    // Draw some world objects
    DrawRectangle(-200, -100, 100, 200, RED);
    DrawRectangle(300, 200, 150, 150, YELLOW);
    DrawCircle(500, -300, 80, PURPLE);
    
    // End camera mode
    EndMode2D();
    
    // Draw UI (not affected by camera)
    DrawText("Camera Demo", 10, 10, 20, BLACK);
    DrawText(TextFormat("Position: %.0f, %.0f", player.position.x, player.position.y), 10, 40, 20, BLACK);
    DrawText(TextFormat("Zoom: %.1f", mainCam.GetZoom()), 10, 70, 20, BLACK);
    DrawText("Controls: Arrow Keys = Move, +/- = Zoom, Space = Shake", 10, 100, 20, BLACK);
}

Step 5: Advanced Camera Features

Add more sophisticated camera behavior:

// Add to Update function for dynamic zoom based on speed
float playerSpeed = sqrt(player.velocity.x * player.velocity.x + player.velocity.y * player.velocity.y);
float targetZoom = 1.0f + (playerSpeed / player.maxSpeed) * 0.3f;  // Zoom out when moving fast
float currentZoom = mainCam.GetZoom();
mainCam.SetZoom(Lerp(currentZoom, targetZoom, 0.05f));

// Add look-ahead feature (camera looks where player is moving)
Vector2 lookAhead = {
    player.velocity.x * 0.1f,
    player.velocity.y * 0.1f
};
Vector2 targetPos = {player.position.x + lookAhead.x, player.position.y + lookAhead.y};
mainCam.Follow(targetPos);

🎯 Camera Features Learned

  • Camera following with smooth interpolation
  • Dynamic zoom control
  • Screen shake effects
  • Camera boundaries and constraints
  • Look-ahead and dynamic zoom based on movement
  • Separating world rendering from UI rendering

Tutorial 4: UI and Menu Systems

🎨 Building User Interfaces

Create interactive menus, HUD elements, and user interface components.

Step 1: Include UI System

Add UI headers to game.h:

#include "raylib.h"
#include "tools/sso_camera.h"
#include "tools/sso_ui.h"
#include "tools/sso_timer.h"

// Game states
enum GameState {
    MENU,
    PLAYING,
    PAUSED,
    GAME_OVER
};

inline GameState currentState = MENU;
inline SSO::Timer gameTimer;

Step 2: Create Menu Buttons

Define menu elements:

// Menu buttons
SSO::Button startButton({540, 300, 200, 50}, "Start Game");
SSO::Button optionsButton({540, 370, 200, 50}, "Options");
SSO::Button quitButton({540, 440, 200, 50}, "Quit");

// Pause menu buttons
SSO::Button resumeButton({540, 300, 200, 50}, "Resume");
SSO::Button restartButton({540, 370, 200, 50}, "Restart");
SSO::Button mainMenuButton({540, 440, 200, 50}, "Main Menu");

// Game over button
SSO::Button playAgainButton({540, 350, 200, 50}, "Play Again");

Step 3: Update Game States

Handle different game states:

inline void Update(float deltaTime) {
    switch (currentState) {
        case MENU:
            UpdateMenu();
            break;
        case PLAYING:
            UpdateGame(deltaTime);
            break;
        case PAUSED:
            UpdatePauseMenu();
            break;
        case GAME_OVER:
            UpdateGameOver();
            break;
    }
}

inline void UpdateMenu() {
    if (startButton.IsClicked()) {
        currentState = PLAYING;
        StartGame();
    }
    if (optionsButton.IsClicked()) {
        // Open options (for now, just print message)
        TraceLog(LOG_INFO, "Options clicked");
    }
    if (quitButton.IsClicked()) {
        // Close the game
        WindowShouldClose() = true;
    }
}

inline void UpdateGame(float deltaTime) {
    // Check for pause
    if (IsKeyPressed(KEY_ESCAPE)) {
        currentState = PAUSED;
        return;
    }
    
    // Update player
    UpdatePlayer(deltaTime);
    mainCam.Update(deltaTime);
    
    // Update game timer
    gameTimer.Update(deltaTime);
    
    // Check game over condition
    if (gameTimer.IsFinished()) {
        currentState = GAME_OVER;
    }
}

inline void UpdatePauseMenu() {
    if (resumeButton.IsClicked()) {
        currentState = PLAYING;
    }
    if (restartButton.IsClicked()) {
        StartGame();
        currentState = PLAYING;
    }
    if (mainMenuButton.IsClicked()) {
        currentState = MENU;
    }
}

inline void UpdateGameOver() {
    if (playAgainButton.IsClicked()) {
        StartGame();
        currentState = PLAYING;
    }
}

Step 4: Render All States

Draw different UI based on game state:

inline void DrawGame() {
    ClearBackground(RAYWHITE);
    
    switch (currentState) {
        case MENU:
            DrawMenu();
            break;
        case PLAYING:
            DrawPlayingGame();
            break;
        case PAUSED:
            DrawPlayingGame();
            DrawPauseOverlay();
            break;
        case GAME_OVER:
            DrawPlayingGame();
            DrawGameOverOverlay();
            break;
    }
}

inline void DrawMenu() {
    // Draw title
    DrawText("SSOEngine Demo", 400, 150, 60, BLACK);
    DrawText("Advanced Movement & Camera Demo", 320, 220, 20, DARKGRAY);
    
    // Draw buttons
    startButton.Draw();
    optionsButton.Draw();
    quitButton.Draw();
    
    // Draw instructions
    DrawText("Use mouse to navigate menus", 10, 680, 20, GRAY);
}

inline void DrawPlayingGame() {
    // Draw game world with camera
    BeginMode2D(mainCam.GetCamera2D());
    
    // Draw world (same as camera tutorial)
    DrawWorld();
    DrawPlayer();
    
    EndMode2D();
    
    // Draw HUD
    DrawHUD();
}

inline void DrawHUD() {
    // Timer display
    float remaining = gameTimer.GetRemaining();
    DrawText(TextFormat("Time: %.1f", remaining), 10, 10, 30, BLACK);
    
    // Controls hint
    DrawText("ESC = Pause", 10, 50, 20, DARKGRAY);
    DrawText("Arrow Keys = Move", 10, 75, 20, DARKGRAY);
    DrawText("+/- = Zoom", 10, 100, 20, DARKGRAY);
    DrawText("Space = Screen Shake", 10, 125, 20, DARKGRAY);
}

inline void DrawPauseOverlay() {
    // Darken the screen
    DrawRectangle(0, 0, 1280, 720, Fade(BLACK, 0.5f));
    
    // Draw pause menu
    DrawText("PAUSED", 550, 200, 50, WHITE);
    
    resumeButton.Draw();
    restartButton.Draw();
    mainMenuButton.Draw();
}

inline void DrawGameOverOverlay() {
    // Darken the screen
    DrawRectangle(0, 0, 1280, 720, Fade(BLACK, 0.7f));
    
    // Draw game over text
    DrawText("GAME OVER", 500, 200, 60, WHITE);
    DrawText("Time's up!", 580, 280, 30, WHITE);
    
    playAgainButton.Draw();
}

Step 5: Game Initialization

Set up the game when starting:

inline void StartGame() {
    // Reset player
    player.position = {0, 0};
    player.velocity = {0, 0};
    
    // Reset camera
    mainCam.Follow({0, 0});
    mainCam.SetZoom(1.0f);
    
    // Reset timer
    gameTimer.SetDuration(30.0f);  // 30 second game
    gameTimer.Reset();
}

🎯 UI Features Learned

  • Game state management system
  • Interactive button creation
  • Menu systems and navigation
  • HUD elements and overlays
  • Pause/resume functionality
  • Game over screens
  • Separating game rendering from UI rendering

🚀 Next Steps

Continue Your Journey

You've completed the basic tutorials! Here's what to explore next:

📚 Advanced Topics

  • • Enemy AI and behavior
  • Collision detection system
  • Particle effects and animations
  • Audio and sound management
  • Save/load game systems

🎮 Game Ideas

  • • Platformer with physics
  • Top-down shooter
  • Puzzle game with timers
  • Racing game with camera
  • RPG with inventory system