Games Programmer
Specializing in complex multiplayer systems, persistence frameworks, and combat mechanics, demonstrated within constrained runtimes such as VRChat's Udon
Note: This site showcases both personal projects and assessed university work. The VRChat-based projects are personal and extracurricular, developed outside of coursework. The Quackworks Engine is an assessed university group project β the systems described here reflect my individual contributions within that team. While several personal systems are implemented within VRChat, the focus is on multiplayer systems design, persistence, networking constraints, and reusable gameplay frameworks rather than the platform itself.
I focus on building robust, reusable systems in Unity (C#), with an emphasis on multiplayer architecture, persistence, and framework-level design. Many of these systems are demonstrated within VRChatβs Udon runtime, which acts as a constrained execution environment with strict data limits, client-authoritative networking, and unpredictable runtime behavior.
These projects represent several years of iteration, refactors, and live usage with real players. VRChat is treated as a hostile runtime for validating system robustness, rather than as the focus of the work itself.
UdonCombatSystem is a reusable combat framework for VRChat, providing consistent, extensible weapon handling, damage logic, and physically interactive reloading in Udon. Originally a lightweight hitscan/projectile system, it has since been rewritten from the ground up to support H3VR/Boneworks-style manual manipulation, including physical magazine loading, charging handles, and body-anchored holsters, while remaining fully playable for desktop users.
It has also become something of a reference point in the space: searches for combat, weapon, and related VRChat/Udon systems frequently surface this framework, and other creators routinely encounter it while looking for systems to build their own projects on.
π View on GitHub
UdonCombatSystem Main Showcase
VRChat has no native socket concept, so both the magwell and pockets system are effectively custom socket implementations.
Two-handed interaction was the hardest part of the rewrite because VRChat has no native two-grip pickup model.
The system is intentionally minimal and composable, designed to be dropped into worlds without dragging in unrelated gameplay logic.
VRCRP is a large-scale roleplay framework inspired by DarkRP-style systems, adapted for VRChat's instance-based multiplayer and Udon's technical constraints. This is my most complex and ambitious project, developed over multiple iterations and used in real communities.
VRCRP Overview Demo
VRCRP has been adapted for a VRChat milsim community (jobs, equipment, controlled access) and forms the conceptual backbone of multiple RP-style experiments. It actively informs academic work on VR multiplayer systems.
Note: VRCRP is not fully public due to its size, ongoing iteration, and community-specific adaptations, but represents my strongest example of large-scale systems design under hostile constraints.
π Visit AfterDark Plaza in VRChat
AfterDark Plaza is a VRChat world developed in collaboration with a game/level designer. My role focused on implementing gameplay systems based on concrete design requests rather than self-directed framework work.
Session-Based Economy & Inventory
Arcade Machine Integration
Shop System
Shooting Range
Portable Inventory UI
AfterDark Plaza serves as a contrast to my larger framework projects, showing my ability to integrate into a collaborative pipeline and deliver feature-complete gameplay systems within an existing creative vision.
Quackworks is a C++ cross-platform game engine built as an assessed university group project, targeting Windows and PS5. The engine powers a Duck Game-inspired 2D platformer demo. The systems below are my individual contributions within the team.
Quackworks Engine Demo
A type-safe publish/subscribe system for decoupled inter-system communication.
std::function<void(const void*)> with a typed wrapper at the subscribe site, keeping storage uniform across all event typesPublish fires synchronously; Enqueue/Flush defers events to a controlled point in the game loop so input lands before gameplay logic runsA three-layer input architecture designed so gameplay code never sees raw key codes or platform SDK types.
InputState struct each frame; all normalisation and SDK-specific knowledge stops hereIsKeyPressed/IsKeyReleased derived from current/previous diff rather than hardware edge detection, making edge detection consistent across all backends and testable without real hardwareWindows backend (PCInputSystem) β unifies three controller SDKs into the common InputState:
LoadLibraryW across three DLL candidates β no hard link-time dependency, engine still launches on machines without XInputSetGamepadLedColor API"Gameplay" action set, covering Switch Pro Controllers, Steam Deck, and generic pads; conditionally compiled under QUACK_ENABLE_STEAM_INPUTuint64_t device ID encoding both the source SDK and native handle in a single value, so reconnected controllers return to their original slotPS5 backend β same IInputBackend interface without the PC complexity:
SceUserServiceUserId constants maps directly to gamepad slots β no dynamic discovery neededControllerConnectedEvent/ControllerDisconnectedEvent publishes explicitly added after they were found missing on PS5 hardware during lobby testingDrives a SpriteSheetRendererComponent by selecting which cell of a sprite sheet to display each frame.
while loop so large frame deltas advance multiple frames rather than causing the animation to fall permanently behind_sprite.AddAnimation("run", 1f, true, 1, 2, 3, 4, 5, 6)), with the coordinate system and offset compositing developed independentlyApplication-layer systems for the Duck Game demo's local multiplayer lobby and character control.
ControllerConnectedEvent/ControllerDisconnectedEvent rather than polling; controller disconnect mid-game immediately unregisters the player and publishes PlayerLeftEventuint64_t device ID rather than slot index, so reconnects return to the same player slotUpdate rather than at construction to avoid ordering constraints