🚀 Overview
Untitled Magic/Dungeon Game is a Roblox game heavily inspired by Noita. Designed with a focus on deep technical craftsmanship, the project features an emergent spell-building engine, high-speed binary network replication, an interactive 3D card deck UI for wand customization, and a decoupled Model-View-Controller (MVC) architecture.
In addition to the systems engineering, I also created some of the pixel art myself. Using Aseprite, I manually drew the icons for the Bolt, Explosive Projectile, Bounce, Explosive Bounce, and Slithering Path spells (the remaining spell art is temporarily AI-generated while in development).
🎬 Gameplay & Media Gallery
[!TIP] Adding Your Videos & Screenshots: Drop your gameplay recordings (
.mp4,.webm) or screenshots (.png,.gif) intoc:\Users\rabbi\Documents\portfolio\static\images\and reference them directly below!
🃏 3D Wand Deck Customization
Grabbing 3D cards and slotting them on a physical deck to customize wands.
🔮 Spell Casting & Replication
Dynamic spell multicasts, triggers, and projectile physics replication.
💻 The 5 Most Impressive Technical Highlights
1️⃣ 1. Recreated Noita Spell System (With Own Twists)
The core gameplay centers on a dynamic spell composition system inspired by Noita. Spells are built using Continuation-Passing Style (CPS) where modifiers yield functions that dynamically compose parameters for downstream spells:
- Dynamic Multicasts & Modifiers: Supports
Hexagon Form(6-way spread),Double Form,Trigger Bolts(triggering sub-spells on impact),Slithering Path, andExplosive Bounces. - Higher-Order Wrapper Composition: Modifiers wrap property updaters via
compose(f, g), allowing infinite stacking of spell behaviors. - Nondestructive Virtual UI Tree Preview (
GetSpellCastTree): Evaluates the full wand cycle non-destructively in a virtual pass to construct a hierarchical tree for HUD rendering before resetting wand pointers.
-- Continuation-Passing Style Wrapper Composition
local function compose(f, g)
return function(val)
return g(f(val))
end
end
-- Example Spell Modifier: Explosive Trigger Bolt
{
Name = "boltWithTrigger",
Type = SpellType.Projectile,
Cast = function(wand, spell, castState)
return 1, function(nextSpells)
local tracker = wand:CastProjectile("Bolt", castState, spell)
local nextSpell = nextSpells[1]
if nextSpell then
tracker.onHit = function(hitProjectile)
nextSpell.cast({
cf = CFrame.lookAt(hitProjectile.position, hitProjectile.position + hitProjectile.velocity),
bounceFromNormal = hitProjectile.normal
})
end
end
return { projectiles = {tracker} }
end
end
}
2️⃣ 2. Performant Binary Networking System
To replicate hundreds of fast-moving projectiles and enemies simultaneously without network lag:
- Declarative Serialization (
ByteBuffer.luau): Custom schema DSL mapping entity states to bit-aligned byte layouts (Id,Position vec2,Health u32). - Dynamic Byte-Width Scaling: Dynamically selects byte writers (
u8,u16,u32) based on active entity counts to save every bit of network bandwidth. - Delta Replicators (
ServerCorrectionReplicator/ClientCorrectionReceiver): Streams byte-packed updates with strict assertion checking (offset == totalBytes), reducing remote event payload size by over 70%.
-- Server Binary State Replicator Flush
function ServerCorrectionReplicator:Flush()
local totalBytes = CORRECTION_COUNT_BYTES + count * (self.idSize + self.stateSize)
local output = buffer.create(totalBytes)
buffer.writeu16(output, 0, count)
for _, entity in self.entities do
self.writeId(output, offset, self.getId(entity))
buffer.copy(output, offset + self.idSize, self.getStateBuffer(entity), 0, self.stateSize)
offset += self.idSize + self.stateSize
end
assert(offset == totalBytes, `Packet mismatch: wrote {offset} bytes, expected {totalBytes}`)
self.remote:FireAllClients(output)
end
3️⃣ 3. Immersive 3D UI (Wand Customization Deck)
Instead of traditional flat 2D menus, players interact with a tactile 3D UI:
- Physical Card Grabbing: Players physically grab 3D spell cards in world space and place them onto a 3D deck to assemble custom wands.
- Spatial Feedback: Directly connects physical card ordering on the deck to the underlying
GetSpellCastTreevirtual pass, giving real-time visual feedback on how spells will fire.
4️⃣ 4. Clean Code Architecture & Model-View-Controller (MVC)
The codebase is built with strict software engineering standards for performance and maintainability:
- Strict Model-View-Controller (MVC):
- Model Core (
ProjectileEngine/Wand): Pure vector math, sub-stepping (1/30), linear drag decay, normal raycast reflection, and homing vectors. Zero GUI or instance coupling. - Reactive View (
ProjectileRenderer): Visual subscriber listening asynchronously to engine signals (ProjectileAdded,ProjectileHit,ProjectileRemoved) to handle 3D meshes, lighting tweens, and sound SFX.
- Model Core (
- Single-Responsibility Hierarchy: Clean folder structure dividing code into
Buffers/,Classes/,Data/,Effects/,Enums/,GameStates/,Gui/,Singletons/, andUtils/. - Memory Pooling (
RotaryIdMap): Circular ID buffer pool (MAX_PROJECTILES) that recycles entity IDs to eliminate runtime garbage collection stutters.
5️⃣ 5. Custom Vector-Math Steering & A* Pathfinding
Rather than relying on Roblox’s bloated Humanoid instances, the game utilizes a pure mathematical vector engine combined with custom hitboxes for massive AI swarms:
- String-Pulling Path Smoothing: The custom A* implementation (
NavGraph.luau) firesworkspace:Spherecastscaled to the agent’s radius to prune unnecessary intermediate path nodes when a clear line of sight exists. - Boids-like Steering Behaviors: Enemies dynamically calculate steering forces (
EnemyEngine.luau). A separation force checks neighboring hitboxes and applies an inverted vector to prevent clipping, while a repulsion force prevents overshooting the target—creating fluid, flocking AI swarms.