Sir Rad

Custom C++ Engine • SDL2 & WebAssembly

About the Project

Play as Sir Rad, a knight who abandons his post defending the castle to perform sick stunts instead. The core technical objective of this university project was to architect a custom 2D engine from scratch in C++ and tie custom audio tracks (including my own vocals) directly to the gameplay mechanics.

Engine Architecture Highlights

  • O(1) Object Pooling: Engineered custom stack-based free-lists to recycle entities, bypassing the OS memory allocator to eliminate heap fragmentation and dynamic allocation overhead.
  • Deterministic 60Hz Physics: Built a fixed-timestep accumulator loop that caps delta-time, preventing the "spiral of death" and ensuring stable collision responses on variable hardware.
  • Memory Safety & Alignment: Enforced 64-bit data alignment to prevent compiler padding waste, and utilized Copy Contructors and Assignment Operators (Rule of Three) to prevent potential double free crashes in future development.

Tech Stack

  • Language: C++
  • Framework: SDL2
  • Deployment: Emscripten (WebAssembly)
  • Features: Audio-driven mechanics & custom physics

                                    void Collision::UpdateGrid()
                                    {
                                        for (size_t i = 0; i < spatialGrid.size(); i++) {
                                            spatialGrid[i].clear();
                                        }

                                        for (size_t i = 0; i < engine->allcharacters.size(); i++) {
                                            Character* c = engine->allcharacters[i];
                                            if (!c->GetSpawned()) continue;

                                            CalculateHitZone(c);
                                            ///flattening a 2d grid to a 1d array index for cache locality
                                            int bucketIndex = c->collisionZone[0] + (c->collisionZone[1] * hitZoneDepth);

                                            spatialGrid[bucketIndex].push_back(c);
                                        }
                                    }

                                    void Collision::CheckCollision(Character* thisChar)
                                    {
                                        int bucketIndex = thisChar->collisionZone[0] + (thisChar->collisionZone[1] * hitZoneDepth);

                                        std::vector& localZone = spatialGrid[bucketIndex];

                                        int thisLX = thisChar->GetPosX();
                                        int thisRX = thisChar->GetPosX() + thisChar->GetSizeW();
                                        int thisTY = thisChar->GetPosY();
                                        int thisBY = thisChar->GetPosY() + thisChar->GetSizeH();

                                        for (size_t i = 0; i < localZone.size(); i++)
                                        {
                                            Character* other = localZone[i];

                                            if (other == thisChar) continue;
                                            int listLX = other->GetPosX();
                                            int listRX = other->GetPosX() + other->GetSizeW();
                                            int listTY = other->GetPosY();
                                            int listBY = other->GetPosY() + other->GetSizeH();

                                            if (thisRX > listLX && thisLX < listRX && thisTY < listBY && thisBY > listTY)
                                            {
                                                thisChar->Collide(other);
                                            }
                                        }
                                    }
                                    
O(n) Spatial Hash Grid: Custom 2D collision system in C++. Implemented a spatial hash grid data structure to map entities into discrete memory buckets via O(1) coordinate hashing. This eliminates O(n²) brute-force iteration by restricting AABB intersection math to localized grid cells, vastly optimizing the engine's broad-phase physics loop.

                                    void GameEngine::Step()
                                    {
                                        static Uint32 lastTime = SDL_GetTicks();
                                        static float timeAccumulator = 0.0f;
                                        const float TIME_STEP = 16.667f; // Fixed 60Hz update step

                                        Uint32 currentTime = SDL_GetTicks();
                                        float frameTime = (float)(currentTime - lastTime);
                                        lastTime = currentTime;

                                        // Spiral of Death preventer (Capped at 100ms)
                                        if (frameTime > 100.0f) {
                                            frameTime = 100.0f;
                                        }

                                        timeAccumulator += frameTime;
                                        Input();

                                        // Fixed timestep physics loop
                                        while (timeAccumulator >= TIME_STEP) {
                                            Update();
                                            totalTime += TIME_STEP;
                                            timeAccumulator -= TIME_STEP;
                                        }

                                        Render();

                                    #ifndef __EMSCRIPTEN__
                                        // Native Desktop frame-limiting delay
                                        if (TIME_STEP - aTimer.getTicks() >= 0) {
                                            SDL_Delay(TIME_STEP - aTimer.getTicks());
                                        }
                                        aTimer.resetTicksTimer();
                                    #endif
                                    }
                                    
                                    //inside the main.cpp (SirRad.cpp)
                                    #ifdef __EMSCRIPTEN__
                                    #include 

                                    GameEngine* globalGame = nullptr;

                                    void MainLoop()
                                    {
                                        if (globalGame) {
                                            globalGame->Step();
                                        }
                                    }
                                    #endif

                                    //inside the main to start the emscripten loop or basic loop, I have a splash screen
                                    #ifdef __EMSCRIPTEN__
                                        globalGame = new GameEngine(window);
                                        emscripten_set_main_loop(MainLoop, 60, 1);
                                    #else
                                        GameEngine theGame(window);
                                        ///game of life splash screen not included in emscripten version
                                        theGame.Splash();
                                        theGame.GameLoop();
                                        SDL_Quit();
                                    #endif
                                    
Cross-Platform Deterministic Game Loop: Developed a fixed-timestep accumulator (16.667ms) to decouple rendering from the core physics simulation, ensuring stable, deterministic behavior across varying hardware. Implemented C++ preprocessor directives to handle diverging core-loop logic between Native Desktop execution and Emscripten (WebAssembly) cross-compilation.

Play the Game