Welcome to the first devlog for Space Dance Melee 2026. This is where we'll be sharing regular, in-depth looks at how the game is being built — the decisions we made, the problems we solved, and the things that surprised us along the way.
This first entry covers the core architecture decisions we made early in development: how we structured player stats, why we built a powerup registry system, and how the shop network came together into something that actually feels great to play with.
Starting With Stats
The first thing we built wasn't a combat system or an enemy — it was a stat architecture. Before anything fun could happen, we needed to decide how player attributes would be stored, modified, and communicated to other systems.
We went with a component-based approach using Unity's MonoBehaviour system. HealthStats, MoveStats, and DamageStats each live as separate components on the relevant GameObjects. This keeps things clean and composable — enemies and players can share the same health system because it doesn't know or care what it's attached to.
For values that need both a base and a modifiable runtime version — like movement speed and max HP — we use a pattern where the SO or FloatReference stores the base value, and the component maintains a separate runtime float. This prevents "drift" when repeatedly applying multipliers.
The Critical Hit System
One of the more interesting design problems early on was how to handle critical hits. We didn't want a simple boolean — we wanted tiers of crits, and we wanted crit chance to stack naturally past 100%.
The solution: a CritTier integer (0 = normal, 1 = crit, 2 = triple crit) and a CritChance float that rolls iteratively. If you have 150% crit chance, the first roll is guaranteed (100%), and the remaining 50% rolls for the second tier. Push past 200% and triple crits become guaranteed. This creates a satisfying progression curve for the Pirate's Spyglass powerup.
The Powerup System
Powerups are the heart of the game. We wanted them to be stackable, composable, and easy to add without touching core systems. The solution we landed on:
- A PowerupsSO ScriptableObject acts as the runtime registry — it tracks how many of each powerup type has been collected
- A PowerupCollectedEventSO fires whenever any powerup is picked up, broadcasting the type to all listeners
- Each powerup behavior subscribes to this event and recalculates its effects from the current count — no cumulative state to get out of sync
The "recalculate from count" approach was a key decision. Instead of adding effects incrementally (+1 each time), each powerup reads the total stack count and calculates the final value fresh. This means there's no way for state to drift, and adding a "respec" or powerup removal feature later becomes trivial.
The Five Powerups
The first five powerups we built each focus on a different pillar of gameplay:
- Crystal Spork — spread shot, more bullets per stack, slight damage trade-off
- Overclock Clock — proc-based temporary speed and fire rate doubling
- Pirate's Spyglass — range extension and stacking crit chance with tier cascade
- Gelatin Rabbit — passive HP regen and movement speed from base
- Metal Heart — max HP growth and flat armor damage reduction
Each one has at least one interesting interaction with the others. Crystal Spork + Overclock Clock means a burst of 9+ bullets at double fire rate. Pirate's Spyglass + Gelatin Rabbit creates a fast, high-crit kiter build. Metal Heart + Gelatin Rabbit is pure survivability.
The Shop Network
The shop system went through several iterations before it felt right. The final design: multiple "shop tubes" placed around the arena, each selling a random powerup. When you buy from one, they all restock together.
This creates interesting decisions. Do you wait near a tube hoping for a better restock? Do you rush across the arena when you spot a good roll? The network behavior turns what could have been a simple vending machine into a live actor in the run.
Implementation-wise, each ShopTube self-registers with a ShopNetwork ScriptableObject at runtime. When a purchase triggers a restock, the tube calls RestockAll() on the network, which propagates to every registered tube. No manual wiring required in the Inspector.
What's Next
The foundation is solid. Next up: enemy AI iteration, wave spawning, and the first real pass at game feel — juice, screen shake, hit effects, and the kind of polish that makes every interaction feel satisfying.
We'll have more to share soon. Follow us on social or subscribe to our newsletter to get notified when the next devlog drops.