Operation Null Mind 0.4.1
A 2D roguelike top down shooter game made with SDL2
 
Loading...
Searching...
No Matches
Technical Documentation

Overview

Operation Null Mind is a 2D top-down shooter with roguelike elements, developed in C utilizing the SDL2 framework.

Gameplay

This comprehensive documentation aims to provide developers with a detailed understanding of the codebase architecture, including:

  • Project structural organization
  • Application execution flow and lifecycle
  • Critical global variables and state management
  • Core algorithms, data structures, and implementation details

This documentation is designed to facilitate efficient navigation and comprehension of the codebase for both new and experienced contributors to the project.

Navigating This Documentation

This documentation is generated using Doxygen and offers several navigation methods:

  • Table of Contents: Navigate through this page through this table of contents
Table of Contents
  • Class List: View a list of all the structs in the project
Class List
  • File List: View a list of all the files in the project
File List
  • Search Function: Use the search box in the upper right corner to find specific elements
Search Box

Note: The "All" search option in Doxygen is not really good. You may have to adjust the search categories like this.

Search Issues
  • Index: View an alphabetical list of all the elements in the project
Index
  • Function Index: View an alphabetical list of all the functions in the project
Function Index
  • Breadcrumbs: Use the breadcrumbs at the bottom of the page to navigate back to the main page
Breadcrumbs

Project Structure

The project is organized into several directories, each with a specific purpose:

/Assets -> Contains images, sfx, music, ...
• All game resources are stored here
• Organized by resource type (images, sounds, fonts)
• Assets are loaded at runtime by the respective systems
/include -> Contains header (.h) files
/App -> Application headers for game loop management
/Core -> Core system headers (vectors, time, etc.)
/Game -> Game mechanics headers (player, gun, etc.)
/Utilities -> Helper utilities headers (animation, ...)
/src -> Contains source (.c) files
• Implements functionality declared in the header files
• Follows the same organizational structure as include/
/App -> Handles the main game loop
• Implements the application lifecycle (start, update, render, quit)
• Coordinates all other systems
/Core -> Core systems, like vectors, time, ...
• Implementation of fundamental systems like input, physics, etc.
• Used by virtually all other parts of the codebase
/Game -> Game mechanics, like player, gun, ...
• Implementation of gameplay features
• Depends on Core systems but is independent of App logic
/Utilities -> Helper utilities, like animation, ...
• Provides services to both Core and Game modules
main.c -> The entry point of the program
• Contains (kinda) the main() function
• Calls functions defined in App/ to run the game

This modular organization follows a clear separation of concerns, making the codebase maintainable and extensible. The structure allows for:

  1. Clear dependencies: Core systems don't depend on Game systems, avoiding circular dependencies
  2. Modularity: Systems can be modified or replaced without affecting unrelated components
  3. Scalability: New modules and features can be added by following the established patterns
  4. Readability: Developers can quickly locate code by understanding its category

Application Lifecycle

The application lifecycle is managed by the App module, which coordinates the game loop and system initialization. The lifecycle consists of the following stages:

Initialization Stage [Start]

During initialization, all systems are set up and resources are loaded:

  1. SDL Initialization: SDL libraries (SDL2, SDL_image, SDL_ttf, etc.) are initialized
  2. Resource Loading: Game assets (textures, sounds, fonts) are loaded into memory
  3. System Initialization: Core systems like input, time, camera, and collision are set up
  4. Game State Setup: Initial game state, player, maps, and other game objects are created

All initialization functions are called from App_Start() in src/App/app_start.c.

Game Loop

Once initialized, the game enters its main loop which runs continuously until exit:

Pre-Update [PreUpdate]

Before processing the main game logic, the pre-update stage:

  • Resets frame-specific states
  • Updates timing information
  • Prepares the renderer for the new frame

These operations are coordinated by App_PreUpdate() in src/App/app_preupdate.c.

Event Handling [Event_Handler]

Event handling processes all user inputs and system events:

  • Keyboard and mouse inputs are captured
  • Window events (resize, focus, etc.) are processed
  • Input state is updated for use in the main game logic

Event handling is managed by App_Event_Handler() in src/App/app_event_handler.c.

Post-Update [PostUpdate]

The post-update stage contains the main game logic:

  • Player movement and actions
  • Enemy AI and behavior
  • Physics and collision detection
  • Game state updates

These operations are orchestrated by App_PostUpdate() in src/App/app_postupdate.c.

Rendering [Render]

After all game logic is processed, the rendering stage:

  • Clears the screen
  • Renders the game world (map, entities, effects)
  • Draws the UI
  • Presents the final frame to the display

Rendering is managed by App_Render() in src/App/app_render.c.

Shutdown Stage [Quit]

When the game exits, cleanup operations free resources and shut down systems:

  • Unload assets from memory
  • Close file handles
  • Free allocated memory
  • Shut down SDL and other libraries

Cleanup is handled by App_Quit() in src/App/app_quit.c.

Key Global Variables

Operation Null Mind uses several global variables to maintain state across the application. These variables are declared as extern in header files and defined in their respective source files.

AppData

The central application data structure that holds the game's current state:

typedef struct {
AppResources resources; // Contains window, renderer, textures, fonts
AppState state; // Contains running flag, fps, current scene
AppConfig config; // Contains window settings and debug flags
extern AppData app; // Global app instance
AppData app
A global variable containing the app's resources, state, and configurations.
A struct containing the app's configurations like window size, title, and fullscreen mode.
Definition app.h:89
A struct containing the app's resources, state, and configurations.
Definition app.h:107
A struct containing the app's resources like the window, renderer, and fonts.
Definition app.h:66
A struct containing the app's states like fps, current scene, and a flag for running.
Definition app.h:79

Located in include/App/app.h and defined in src/App/app.c. This is the central structure that manages the application state and resources.

Usage Examples:

// Change the game state
// Check if the application is still running
if (!app.state.running) {
// Handle exit condition
}
// Access the renderer for drawing
SDL_RenderClear(app.resources.renderer);
// Use the configured screen dimensions
int width = app.config.screen_width;
int height = app.config.screen_height;
@ SCENE_PAUSE
Pause menu scene.
Definition app.h:56
int screen_height
The screen height.
Definition app.h:95
int screen_width
The screen width.
Definition app.h:94
AppState state
The app's state.
Definition app.h:109
AppConfig config
The app's configurations.
Definition app.h:110
AppResources resources
The app's resources.
Definition app.h:108
SDL_Renderer * renderer
The SDL renderer.
Definition app.h:68
AppScene currentScene
The current scene.
Definition app.h:83
bool running
Flag indicating if the app is running.
Definition app.h:80

InputEvent

Tracks all input device states for handling player interactions:

typedef struct {
MouseState mouse; // Contains position, motion, button states
KeyboardState keyboard; // Contains states for all keyboard keys
extern const InputEvent * const Input; // Global input instance (read-only)
const InputEvent *const Input
Definition input.h:44
Definition input.h:40
Definition input.h:30

Located in include/Core/input.h and defined in src/Core/input.c. The Input system provides an interface for checking if keys/buttons are pressed, held, or released.

Usage Examples:

// Check if a key was just pressed this frame
if (Input->keyboard.keys[SDL_SCANCODE_SPACE].pressed) {
Player_Jump();
}
// Check if a key is being held down
if (Input->keyboard.keys[SDL_SCANCODE_W].held) {
Player_MoveForward();
}
// Check if a key was just released
if (Input->keyboard.keys[SDL_SCANCODE_ESCAPE].released) {
TogglePauseMenu();
}
// Get current mouse position
Vec2 mousePos = Input->mouse.position;
// Check if left mouse button was just clicked
}
void Player_Shoot()
[Utility] Makes the player shoot their current weapon
Definition player_combat.c:24
bool pressed
Definition input.h:25
bool released
Definition input.h:26
bool held
Definition input.h:27
KeyboardState keyboard
Definition input.h:46
MouseState mouse
Definition input.h:45
ButtonState keys[SDL_NUM_SCANCODES]
Definition input.h:41
ButtonState leftButton
Definition input.h:33
Vec2 position
Definition input.h:31
A 2D vector structure.
Definition vec2.h:81

TimeSystem

Manages timing information for frame-rate independence:

typedef struct {
float deltaTimeSeconds; // Time between frames in seconds
float timeScale; // Scale factor for time
float scaledDeltaTimeSeconds; // Delta time multiplied by time scale
double programElapsedTimeSeconds; // Total time since program start
double previousTick; // Previous frame tick value
extern const TimeSystem * const Time; // Global time instance (read-only)
Structure to handle time-related functionality.
Definition time_system.h:36
const TimeSystem *const Time
Global read-only time system pointer for other modules.
Definition time_system.c:29

Located in include/Core/time_system.h and defined in src/Core/time_system.c. The Time system is crucial for frame-rate independent movement and animations.

Usage Examples:

// Move an object with frame-rate independence
object.position.x += object.velocity.x * Time->deltaTimeSeconds;
object.position.y += object.velocity.y * Time->deltaTimeSeconds;
// Update animation timer
animation.timer += Time->deltaTimeSeconds;
if (animation.timer >= animation.frameDuration) {
animation.currentFrame = (animation.currentFrame + 1) % animation.frameCount;
animation.timer = 0;
}
// Create a countdown timer
float countdownRemaining = 10.0f - Time->programElapsedTimeSeconds;
if (countdownRemaining <= 0) {
// Time's up!
}
// Use time scaling for slow-motion effect
Time_SetTimeScale(0.5f); // Half speed
// Movement and animations will now use scaledDeltaTimeSeconds automatically
double programElapsedTimeSeconds
Total time elapsed since program start.
Definition time_system.h:40
float deltaTimeSeconds
Time between current frame and previous frame in seconds.
Definition time_system.h:37
void Time_SetTimeScale(float scale)
[Utility] Sets the time scale.
Definition time_system.c:81

CameraSystem

Controls the game's view into the world:

typedef struct {
Vec2 position; // Camera position in world coordinates
extern CameraSystem camera; // Global camera instance
CameraSystem camera
Definition camera.c:17
Definition camera.h:28

Located in include/Core/camera.h and defined in src/Core/camera.c. The Camera system provides coordinate conversion between world and screen space.

Usage Examples:

// Update camera to follow player
// Convert world position to screen position for rendering
Vec2 worldPos = enemy.position;
Vec2 screenPos = Camera_WorldVecToScreen(worldPos);
SDL_Rect destRect = {screenPos.x, screenPos.y, enemy.width, enemy.height};
SDL_RenderCopy(app.resources.renderer, enemy.texture, NULL, &destRect);
// Convert screen position (e.g., mouse) to world position
Vec2 mouseScreenPos = Input->mouse.position;
Vec2 mouseWorldPos = Camera_ScreenVecToWorld(mouseScreenPos);
// Check if an object is visible on screen
SDL_Rect objectRect = {worldPos.x, worldPos.y, object.width, object.height};
if (Camera_RectIsOnScreen(objectRect)) {
// Only render if visible
RenderObject(object);
}
Vec2 Camera_WorldVecToScreen(Vec2 worldPosition)
[Utility] Converts world coordinates to screen coordinates
Definition camera.c:57
Vec2 Camera_ScreenVecToWorld(Vec2 screenPosition)
[Utility] Converts screen coordinates to world coordinates
Definition camera.c:69
PlayerData player
[Data] The player's initial data and configuration
Definition player_data.c:22
Vec2 position
Position of the camera in the world.
Definition camera.h:29
PlayerState state
Current player state.
Definition player.h:144
Vec2 position
Current player position.
Definition player.h:89
float y
The y-coordinate of the vector.
Definition vec2.h:83
float x
The x-coordinate of the vector.
Definition vec2.h:82

PlayerData

Contains all information about the player character:

typedef struct {
PlayerState state; // Position, direction, speed, equipped gun, etc.
PlayerStat stats; // Walk speed, dash speed, dash duration, etc.
PlayerResources resources; // Particle emitters, timers, animations, etc.
AnimationData animData; // Animation data for the player
extern PlayerData player; // Global player instance
Configuration data for creating an animation.
Definition animation.h:102
Main player data structure.
Definition player.h:143
Contains the player's resources and timers.
Definition player.h:114
Contains the player's stats and attributes.
Definition player.h:128
Contains the player's current state information.
Definition player.h:88

Located in include/Game/player.h and defined in src/Game/player.c. The Player system manages the player character's state, movement, animations, and interactions.

Key Algorithms and Data Structures

Vector System

The vector system provides 2D vector operations essential for game physics and positioning:

typedef struct {
float x;
float y;
} Vec2;

Key algorithms include:

  • Vector Addition/Subtraction: Combines vectors component-wise. Used extensively for physics calculations such as applying velocity to position.
    Vec2 result = { a.x + b.x, a.y + b.y };
    return result;
    }
    Vec2 Vec2_Add(Vec2 a, Vec2 b)
    [Utility] Adds two vectors.
    Definition vec2.c:62
  • Normalization: Converts a vector to a unit vector (length of 1) while preserving direction. Critical for directional calculations like aiming and movement.
    float magnitude = Vec2_Magnitude(v);
    if (magnitude > 0) {
    Vec2 result = { v.x / magnitude, v.y / magnitude };
    return result;
    }
    return v; // Avoid division by zero
    }
    Vec2 Vec2_Normalize(Vec2 v)
    [Utility] Returns the normalized vector of the input vector.
    Definition vec2.c:49
    float Vec2_Magnitude(Vec2 v)
    [Utility] Returns the magnitude of a vector.
    Definition vec2.c:27
  • Dot Product: Calculates the scalar product of two vectors, used for angle calculations and projection.
    float Vec2_Dot(Vec2 a, Vec2 b) {
    return a.x * b.x + a.y * b.y;
    }
    float Vec2_Dot(Vec2 a, Vec2 b)
    [Utility] Returns the dot product of two vectors.
    Definition vec2.c:132
  • Distance Calculation: Measures the Euclidean distance between two points. Used for proximity checks and collision detection.
    float Vec2_Distance(Vec2 a, Vec2 b) {
    float dx = b.x - a.x;
    float dy = b.y - a.y;
    return sqrtf(dx*dx + dy*dy);
    }
    float Vec2_Distance(Vec2 a, Vec2 b)
    [Utility] Returns the magnitude of a vector.
    Definition vec2.c:38
  • Linear Interpolation (Lerp): Creates smooth transitions between two vectors. Used for camera movement, animations, and particle effects.
    Vec2 Vec2_Lerp(Vec2 a, Vec2 b, float t) {
    // Clamp t between 0 and 1
    t = t < 0 ? 0 : (t > 1 ? 1 : t);
    Vec2 result = {
    a.x + (b.x - a.x) * t,
    a.y + (b.y - a.y) * t
    };
    return result;
    }
    Vec2 Vec2_Lerp(Vec2 a, Vec2 b, float t)
    [Utility] Linearly interpolates between vectors a and b.
    Definition vec2.c:144

Located in include/Core/vec2.h and implemented in src/Core/vec2.c.

Collision System

The collision system detects interactions between game objects using a layer-based filtering approach:

typedef struct {
SDL_Rect hitbox; // The hitbox of the collider
CollisionLayer layer; // What layer this object belongs to
CollisionLayer collidesWith; // Bitmask of layers this can collide with
void* owner; // Pointer back to the entity
bool active; // Is this collidable active?
typedef struct {
Collider* objects[MAX_COLLISIONS_PER_CHECK]; // An array of detected colliders
int count; // Number of collisions detected
#define MAX_COLLISIONS_PER_CHECK
Definition colliders.h:75
CollisionLayer
Definition colliders.h:79
Definition colliders.h:113
This struct represents a collider.
Definition colliders.h:96

Key algorithms include:

Located in include/Core/colliders.h and implemented in src/Core/colliders.c.

Particle System

The particle system creates visual effects like explosions, smoke, and bullet trails:

typedef struct {
Vec2 position;
Vec2 velocity;
float lifetime;
float currentLife;
SDL_Color color;
float size;
float rotation;
float angularVelocity;
float drag;
SDL_Texture* texture;
typedef struct {
Particle* particles;
int maxParticles;
int activeParticles;
Vec2 position;
Vec2 emissionDirection;
float emissionAngle;
float emissionRate;
float emissionTimer;
ParticleMovementPattern movementPattern;
ParticleEmitterPreset preset;
bool isActive;
A struct that represents the configurations of a particle emitter.
Definition particles.h:105
A struct that represents a particle.
Definition particles.h:62

Key algorithms include:

  • Particle Emission: Controls how particles are spawned, including patterns like burst, continuous streams, or shaped emissions.
    void Particle_Emit(ParticleEmitter* emitter, int count, Vec2 position) {
    for (int i = 0; i < count; i++) {
    if (emitter->activeParticles >= emitter->maxParticles) {
    break;
    }
    // Find an inactive particle slot
    int index = -1;
    for (int j = 0; j < emitter->maxParticles; j++) {
    if (emitter->particles[j].currentLife <= 0) {
    index = j;
    break;
    }
    }
    if (index == -1) continue;
    // Initialize particle with random variations based on emitter properties
    Particle* p = &emitter->particles[index];
    p->position = position;
    // Calculate emission angle with randomness
    float angle = emitter->emissionDirection +
    (Random_Float(-emitter->emissionAngle, emitter->emissionAngle) * 0.5f);
    float speed = Random_Float(emitter->minSpeed, emitter->maxSpeed);
    p->velocity.x = cosf(angle) * speed;
    p->velocity.y = sinf(angle) * speed;
    // Set other particle properties with randomness
    p->lifetime = Random_Float(emitter->minLifetime, emitter->maxLifetime);
    p->currentLife = p->lifetime;
    // ...more initialization...
    emitter->activeParticles++;
    }
    }
    int maxParticles
    Maximum particles this emitter can have.
    Definition particles.h:112
    Particle * particles
    Array of particles.
    Definition particles.h:142
    Vec2 position
    Current position.
    Definition particles.h:63
    Vec2 velocity
    Current velocity vector.
    Definition particles.h:77
  • Particle Movement Patterns: Implements different movement behaviors like linear, spiral, gravitational, or targeted movement.
    void Particle_UpdateMovement(Particle* particle, ParticleMovementPattern pattern) {
    switch (pattern) {
    case PATTERN_LINEAR:
    // Simple linear movement with drag
    particle->velocity.x *= (1.0f - particle->drag * Time->deltaTimeSeconds);
    particle->velocity.y *= (1.0f - particle->drag * Time->deltaTimeSeconds);
    particle->position = Vec2_Add(particle->position,
    Vec2_Scale(particle->velocity, Time->deltaTimeSeconds));
    break;
    case PATTERN_SPIRAL:
    // Spiral movement pattern
    float angle = atan2f(particle->velocity.y, particle->velocity.x);
    angle += particle->angularVelocity * Time->deltaTimeSeconds;
    float speed = Vec2_Magnitude(particle->velocity);
    particle->velocity.x = cosf(angle) * speed;
    particle->velocity.y = sinf(angle) * speed;
    particle->position = Vec2_Add(particle->position,
    Vec2_Scale(particle->velocity, Time->deltaTimeSeconds));
    break;
    // ...other patterns...
    }
    }
    float drag
    Individual drag factor.
    Definition particles.h:79
  • Particle Lifecycle Management: Handles particle creation, updating, and removal based on lifetime.
    void Particle_Update(ParticleEmitter* emitter) {
    // Emit new particles based on emission rate
    if (emitter->isActive && emitter->emissionTimer >= 1.0f / emitter->emissionRate) {
    emitter->emissionTimer = 0;
    Particle_Emit(emitter, 1, emitter->position);
    }
    // Update existing particles
    int activeCount = 0;
    for (int i = 0; i < emitter->maxParticles; i++) {
    Particle* p = &emitter->particles[i];
    if (p->currentLife <= 0) continue;
    // Decrease lifetime
    p->currentLife -= Time->deltaTimeSeconds;
    if (p->currentLife <= 0) {
    continue; // Particle is now inactive
    }
    // Update movement
    Particle_UpdateMovement(p, emitter->movementPattern);
    // Update other properties like color, size, rotation
    float lifeRatio = p->currentLife / p->lifetime;
    p->size = Lerp(p->endSize, p->startSize, lifeRatio);
    p->rotation += p->angularVelocity * Time->deltaTimeSeconds;
    // Color interpolation from start to end color
    p->color.r = Lerp(p->endColor.r, p->startColor.r, lifeRatio);
    p->color.g = Lerp(p->endColor.g, p->startColor.g, lifeRatio);
    p->color.b = Lerp(p->endColor.b, p->startColor.b, lifeRatio);
    p->color.a = Lerp(p->endColor.a, p->startColor.a, lifeRatio);
    activeCount++;
    }
    emitter->activeParticles = activeCount;
    }
    Timer * emissionTimer
    Timer for emission control.
    Definition particles.h:141
    float emissionRate
    Time between emissions in seconds.
    Definition particles.h:110
    Vec2 position
    Position of the emitter.
    Definition particles.h:107
    Vec2 size
    Current size (can change over lifetime)
    Definition particles.h:72
    SDL_Color color
    Current color (interpolated between start/end)
    Definition particles.h:71
    float rotation
    Current rotation angle (Not implemented)
    Definition particles.h:73
  • Particle Rendering: Efficiently renders particles with various blending modes and visual effects.
    void Particle_Render(ParticleEmitter* emitter) {
    for (int i = 0; i < emitter->maxParticles; i++) {
    Particle* p = &emitter->particles[i];
    if (p->currentLife <= 0) continue;
    // Convert world coordinates to screen coordinates
    // Set color with alpha blending
    SDL_SetTextureColorMod(p->texture, p->color.r, p->color.g, p->color.b);
    SDL_SetTextureAlphaMod(p->texture, p->color.a);
    // Create destination rectangle
    SDL_Rect destRect = {
    (int)(screenPos.x - p->size / 2),
    (int)(screenPos.y - p->size / 2),
    (int)p->size,
    (int)p->size
    };
    // Render with rotation if needed
    if (p->rotation != 0) {
    SDL_RenderCopyEx(
    p->texture,
    NULL,
    &destRect,
    p->rotation * (180.0f / M_PI), // Convert radians to degrees
    NULL,
    SDL_FLIP_NONE
    );
    } else {
    SDL_RenderCopy(app.resources.renderer, p->texture, NULL, &destRect);
    }
    }
    }
    #define M_PI
    Definition vec2.c:17

Located in include/Utilities/Particles/particle.h and implemented in src/Utilities/Particles/particle.c.

Map Generation

The map system manages the game environment using a three-tiered hierarchical structure: maps, chunks, and tiles.

Map-Chunk-Tile Hierarchy

The map is divided into chunks, and each chunk is further divided into tiles. This hierarchical structure allows for efficient management and rendering of the game environment.

  • Map: The entire game world, consisting of multiple chunks.
  • Chunk: A section of the map, containing a grid of tiles.
  • Tile: The smallest unit of the map, representing a single piece of the environment.
typedef struct {
int type;
bool collidable;
SDL_Texture* texture;
int variation;
bool explored;
} Tile;
typedef struct {
Tile** tiles;
int width;
int height;
Vec2 position;
bool isRoom;
RoomType roomType;
int colliderCount;
Collider** colliders;
} MapChunk;
typedef struct {
MapChunk** chunks;
int chunkWidth;
int chunkHeight;
int chunkSize;
Vec2 startPos;
Vec2 endPos;
Vec2* mainPath;
int mainPathLength;
int currentChunkX;
int currentChunkY;
RoomType
Possible types of rooms.
Definition chunks.h:93
Represents a game environment map composed of chunks.
Definition maps.h:26
EnvironmentTileData tiles
Global tile data instance.
Definition tile_data.c:14

hm to find the path from the start node to the end node.

  1. Reconstruct Path: Reconstructs the path fro#### Procedural Map Generation The map generation process creates randomized but cohesive game levels using a combination of techniques.
  1. Reset Existing Map Data: Clears any existing map data to start fresh.
  2. Place Starter Room: Places the starter room in a fixed location, typically the center of the map.
  3. Create Main Path: Uses a directed random walk to create the main path from the start room to the end room.
  4. Place End Room: Places the end room at the furthest point on the main path.
  5. Fill in Side Paths and Additional Rooms: Adds side paths and additional rooms to create a more complex map.
  6. Add Detail to Each Chunk: Adds details to each chunk based on its type.
  7. Place Colliders: Places colliders for walls and obstacles.

Conclusion

This documentation provides a high-level overview of Operation Null Mind's architecture and key components. For detailed information about specific functions and structures, please refer to the appropriate sections of this documentation or the inline comments in the source code.

For developers new to the project, we recommend starting with the Project Structure section to understand the codebase organization, then exploring the Application Lifecycle to understand the flow of execution. The Key Algorithms and Data Structures section provides deeper insights into the technical implementation details.

If you're interested in contributing to the project, please refer to the GitHub repository for contribution guidelines and development roadmap.