Overview of all game states: hosting, connecting, syncing, and gameplay.
Integrated Winsock-based networking into an existing C++ game framework with minimal code changes, working within strict constraints similar to a production codebase.
Designed a peer-to-peer connection based on lockstep game replication, the same type of networked system that powered games like Doom and Age of Empires 1.
Designed and implemented custom serialization and deserialization of player input data to synchronize game state using UDP data packets.
Utilized non-blocking input/output sockets to ensure gameplay code can still run during the period where networking functions are in a delay state.
Implemented connection, hosting, and error-handling logic to support peer discovery, session setup, and graceful fallback behavior.
Goals & Intent
This was a really interesting project, and a good excuse to get hands-on with low-level networking. The goal was to add hosting, connecting, and game replication to an existing engine that already handled gameplay and rendering. While it wasn’t my first time working inside someone else’s codebase, it was my first experience adding networking to an engine that didn’t already support it, which made for a fun and meaningful challenge.
What Went Well
The lockstep model turned out to be a simple but effective way to keep both peers fully synchronized, with collision resolution remaining deterministic. I initially struggled with how network calls should fit into the main engine loop, but switching the sockets to non-blocking IO made everything click. Instead of the game stalling while waiting on send/receive calls, it can now continue running and simply check for network activity each frame. That change was small in the context of this project, but in a real-time game it would have a huge impact on responsiveness!
What I Learned / What I’d Improve
This project deepened my understanding of low-level networking by giving me hands-on experience with game replication, custom UDP packets, and peer-to-peer connection logic. If I were to revisit it, I’d like to experiment with a different replication model, such as a server-authoritative client/server setup. It would be a fun opportunity to make it so players can move freely around instead of waiting on the other's input, and shift responsibilities like collision resolution to the server.
A* attempting to find an impossible path to the top right corner.
Implemented A* pathfinding algorithm in C++ for a custom professor-provided engine, as part of a game AI course project.
Integrated five different heuristic weight equations: Octile, Chebyshev, Inconsistent, Manhattan, and Euclidean.
Built custom node and grid data structures that fit within the cashe to support efficient pathfinding operations.
Added path smoothing and rubberbanding techniques for more natural movement in resulting paths.
Optimized performance by replacing the open list with a custom bucket queue, precomputing neighbors, and caching computed weights.
Goals & Intent
This project was extremely interesting and engaging, and my biggest takeaway was my newfound obsession with A*! It's a very versatile algorithm, and even outside of simple wall avoidance like in this demo, I have a lot of interesting ideas for how to implement it in future projects.
What Went Well
Implementation of the algorithm itself was surprisingly the easiest part of this entire project. It's basically just Dijkstra -- which I already understood pretty well -- with extra bells and whistles, so once I understood the differences between the two debugging went pretty quick. The hardest part was everything that came after: A* as a technique is an inch wide and a mile deep, and there's so much additional complexity and extra features to expand on the algorithm itself.
What I Learned / What I’d Improve
Optimization on this project was also a beast, and trying to do so probably took as much time as programming the actual features did. The most difficult part was programming my own bucket queue data structure, which is basically an array of priority queues arranged in "buckets" with different ranges of values.
Time lapse of a full generation. Zones are color coded, pre-built rooms & corridors are in grey.
Developed a procedurally generated level system in Unity, using a “drunkard’s walk” style algorithm to create unique grid-based dungeon layouts each playthrough.
Supported pre-designed rooms serialized through text files, combining both hand made and randomly generated areas to allow for both variety and intentional level design.
Implemented adjustable terrain generation settings that increases difficulty, changes the terrain, and adds stronger enemies each new area; keeping the player engaged and challenged as they progress.
Created player, enemy, and boss controllers with easily adjustable parameters, making it quick to tweak movement, combat behavior, and balance during testing.
Designed and playtested a scaling difficulty system where random power-ups placed in the level and dropped by enemies adapt to the player’s progress, helping maintain a steady level of challenge.
Goals & Intent
This project was my first experience working with procedural generation, and it served as a way to dip one toe into a massively deep and complex approach to level design. My goal was to start with a simple generation algorithm and adapt it to produce levels that felt cohesive and intentional. I also wanted the final project to have an engaging difficulty curve that felt hand-crafted, while still being procedurally generated.
What Went Well
The part of this project that I'm most proud of are the different zones in generation I implemented. Through iteration, adding more generation parameters, and repeated playtesting, I was able to introduce more structure and turn that chaos into a level that felt cohesive. I was also happy with the playtesting process itself, which helped me balance the variety of enemies in the game and give each one a distinct gameplay gimmick.
What I Learned / What I’d Improve
While I'm proud of the progress I made balancing the enemy difficulty and player power-up drops, most of my decisions were based purely on observational playtesting. If revisited this project it'd be nice to implement a telemetry system or automated testing to collect concrete data, like each enemy's damage per second or average time they spend in combat with the player. Also, A*!! It'd be really interesting to implement pathfinding to analyze ideal routes through the level and test how different generation parameters affect the distance the player has to travel.
Paddle Game running on the device, in debug mode.
Developed core game logic for paddles, ball physics, and game state management from scratch, transforming the standard Pong gameplay into a handheld experience, including analog controls using the Playdate's crank.
Utilized the Playdate SDK to create game objects with finite state machines and implemented custom graphics calls for rendering shapes directly on-screen.
Implemented a debug mode to draw information about enemy AI, frame rate, and other game info, expediting the process of fixing bugs whenever they came up.
Optimized code for hardware compatibility, ensuring a smooth transition from the Playdate simulator to the actual handheld device.
Designed a customizable enemy AI that predicts ball trajectory, with adjustable accuracy settings to dynamically scale difficulty.
Goals & Intent
I really like the Playdate! It's a very fun and quirky handheld, and the barrier to entry for making games is very low. The SDK is publicly available, and the documentation is easy to understand -- especially if, like me, you already have experience with Lua. This was a fun, low-stakes project that I still come back to every now and then to mess around with when I have the free time.
What Went Well
I'm proud of how my enemy AI turned out for this project. When I started I knew I wanted a decently smart enemy paddle that was challenging, but not unbeatable. I did some research, and implemented a few prototypes, but a big challenge was making it so that the AI would run on the device -- since the desktop simulator runs with more power, optimization was needed to port it onto the handheld. In the end, I decided to use a series of raycasts to predict where the ball would make contact with the other side of the screen. What I like about this implementation is that it's easily adjustable: I can choose to add minor offsets on the angle of the casts, and lengthen the time between casts, to mimic the actual imperfections of a human player.
What I Learned / What I’d Improve
Learning how to implement draw calls for the Playdate was the most challenging part of this project. Even though the graphics in this game are pretty simple rectangles and circles, it was still tricky to wrap my head around pushing and popping context to the handheld screen. Looking back, this project actually helped me as I learned how to implement custom OpenGL rendering, since the two use similar graphics architecture.
Whodunnit -- The Fighting Game! is a project I made for my DES 212: Systems Design class. The project is a one-dimensional fighting game simulation, made in Unity, where the player "interrogates" several enemies, in an attempt to "catch the killer". The goal of the project was to create a well-balanced fighting game, with a variety of attacks and enemies, and to use telemetry data and play testing to ensure the gameplay wasn't too easy or too hard.
I accomplished this by creating a "fast mode" - triggered by pressing the "F" key - which turns off all graphics and makes the simulation run at 6 times the usual speed, running through hundreds of battles with each enemy type, and testing each player AI mode. The AI modes were:
Randomly spamming attacks
Spamming one attack
Playing intelligently, conserving resources and retreating when necessary
Short animation of StarQuester showing off basic keyframed poses, rendered with EEVEE.
StarQuester is my largest Blender project to date. It's a personal project that began with the simple goal of reviving a forgotten animatronic character from Robot Scouts, a theme park attraction I loved as a kid. I was responsible for hard surface modeling, texturing, rigging, and animation. I designed this project specifically for high-quality rendered animation, prioritizing visual fidelity with higher triangle counts and shrinkwrap texturing over UVs, and generally less emphasis on game engine constraints.
Preparing references & scale - Using reference photos from the attraction, I planned the model's real world scale and relative proportions.
Blocking - Created simple shapes to define the body, arms, and head. The character is made of simple, blocky shapes with lots of little details, which were fun to try and replicate.
Modifier workflow - Added non-distructive modifiers like Boolean Union, Array, and Shrinkwrap to flexibly implement second pass details.
Final model polish - Finished development of model's shape and materials.
Rigging Setup - Created a set of linked bones, movement constraints, and vertex groups for every moving part - luckily with my iterative development and the robot's base design, it was easy to select and modify distinct areas.
Animation - Implemented animation sequences focusing on clear motion and timing, using keyframes and constraints to achieve behavior in line with the original animatronic, but with much more movement and personality.
Rendering & post-production - Rendered final outputs with basic lighting and post-processing to present the model's form and animation capabilities.
The Warhammer of Zillyhoo was one of my earlier projects in 3ds Max, back when I was still getting a grip on the software and 3D modeling best practices in general. It was an assignment for a 3D art class, with the stated goal of creating a model that could be ported into a game engine. I was responsible for hard surface modeling, UV unwrapping, texturing, and optimization.
Preparing references & scale - I stuck closely to the orthographic blueprint reference to determine the final model's scale.
Extrusion - Constructed most of the head & handle of the Warhammer through repeatedly extruding basic cube and cylinder shapes.
Final Polish - Exported the material through Paint.NET, and applied final touch up of the model's shape.
Optimization - The extrusion technique didn't make too much extra geometry, but I simplified any topology I could find to reduce the triangle count down to around 1,000.
UV unwrapping and texturing - Unwrapped the model by hand, manually splitting along seams on the diffuse material. The smile on the back was a minor pain to position correctly.
This goose model was another later project created for a 3d art class using 3ds Max. This one focused on organic modeling, utilizing sculpting to shape the goose's body. The final model still needed to be optimized for use in a game engine. I was responsible for organic modeling, UV unwrapping, texturing, and optimization.
Fun fact! The goose was sort of an in-joke during development of Goosthetic, so this model made it into the final release of that game. It's hidden in a few places around the level -- see if you can find them!
Preparing references & scale - References were taken directly from the in-game model and were placed orthographically behind the model, making it easy to create a matching sculpt.
Blocking & hard surface modeling - The goose's beak and feet were modeled first, and the body was blocked out with a combination of spheres and cylinders stitched together topologically.
Sculpting - Utilized push/pull, exaggerate, and smoothing tools to create the natural curves of the goose's body in line with the reference photos.
Optimization - Realigned the model's topology and reduced the poly count by hand down to around 2,000, while still keeping enough natural detail.
UV unwrapping and texturing - Unwrapped the model by hand. Luckily the goose has extremely simple colors; the hardest part was lining up the eyes so they remained circular.