Big day with a mix of visual polish and infrastructure work.
Screen transitions and countdown
Added a screen transition system — smooth fade-to-black wipes for moving between game states. Also built a countdown timer overlay (“3… 2… 1… GO!”) for round starts. These are small details but they make the difference between “programmer test” and “feels like a game.”
The transitions use a simple full-screen Frame with BackgroundTransparency animated from 1 to 0 and back. We considered fancier wipes (iris, slide, pixelate) but decided to keep it simple. A clean fade does the job without calling attention to itself.
Isometric camera
Built a configurable camera rig that overrides Roblox’s default third-person camera. The campfire game works better with a pulled-back, angled view. The rig takes an angle, distance, and follow target, handles smooth interpolation, and locks the camera to prevent player rotation.
The Roblox-specific challenge here is fighting the default CameraScript. You can’t just set Camera.CFrame every frame — the default script will overwrite it. You have to set Camera.CameraType = Enum.CameraType.Scriptable to take full control. But then you’re responsible for everything: follow behavior, collision avoidance, and smoothing.
Reusable asset templates
Made the framework’s asset templates truly reusable. Previously, a template like “Marshmallow” had its name hardcoded internally. Clone it, rename the clone to “GoldenMarshmallow,” and internal references would break because they were looking for FindFirstChild("Marshmallow"). Now templates discover their own name at runtime via script.Parent.Name, so cloning and renaming just works.
Client module loading
Added deterministic client module loading with dependency resolution. Client-side modules declare their dependencies, and the loader topologically sorts them to determine initialization order. This is the client-side equivalent of the Stage-Wait boot system (Day 5), but for individual modules rather than lifecycle stages.
ArrayPlacer and Dropper
New assets for dynamically spawning objects — ArrayPlacer for radial patterns (place marshmallows in a circle around the campfire), Dropper for randomized positions with physics. Both are data-driven: you configure spawn counts, radii, and timing, and the asset handles the rest.
The invisible NPC bug
Then the bug that ate two hours: Walking NPCs were invisible. They existed in the workspace. You could collide with them. The Explorer showed them with all their parts. But they weren’t rendering.
After checking materials, transparency, render distance, and LOD settings, we found the cause: dynamically spawned Humanoid characters were being positioned with their HumanoidRootPart several studs above the ground. Roblox has a rendering optimization where characters that aren’t grounded (no raycast hit below their feet within a threshold) don’t render their appearance. It’s an LOD optimization for characters in freefall or far from terrain.
The fix was one line — set the Y position so the HumanoidRootPart is on the floor:
rootPart.CFrame = CFrame.new(x, floorY + hipHeight, z)
One line. Two hours. If you’ve ever spawned Humanoid NPCs and they’re invisible, check their ground contact.