arrow_backBACK TO PROJECTS
2026
Game Dev

Merge Weapons (Roblox)

ROBLOX

Scope note: This write-up covers the server and client code architecture only. The project does not include custom UI assets, VFX, 3D models, or SFX — all of which would be the next layer of production work. The systems described here were designed to make that layer straightforward to plug in.

A multiplayer idle-combat game on Roblox in the Vampire Survivors genre. Players accumulate weapons with individually randomized stats, merge duplicates to upgrade rarities, and progress through increasingly difficult stages — all on a fully server-authoritative architecture.


Architecture Overview

All game logic follows a strict server-authority model. The client handles only input, visuals, sound, and UI rendering. It never resolves damage, drops, inventory changes, or currency changes. Every outcome is computed server-side against server-owned state.

Systems are registry-driven throughout. There are no central branching dispatchers for weapons, abilities, enemies, tasks, or gate behaviors. Extending any of these systems means adding a new data entry or module — not modifying shared logic.


Performance

Performance is treated as a first-class constraint. The game is designed to sustain smooth gameplay across a shared multiplayer server with many concurrent enemies and players.

  • Enemies use no humanoid instances: Rather than Roblox's built-in character model, enemies are lightweight custom instances with only the attributes needed for combat. This eliminates the physics, animation state machine, and joint overhead that humanoids carry, and lets the server sustain a significantly higher enemy count.
  • No pathfinding: Enemies move directly toward their current target. The server steps movement at a fixed rate while the client interpolates to render smooth continuous motion — no pathfinding queries, no navmesh overhead.
  • Spawn/despawn lifecycle: Enemies spawn near the player and despawn automatically when the player moves too far away or after a hard time limit. This caps active enemy count per player regardless of session length.
  • Idle stage culling: Stages with no players present run no AI at all. Server time is not spent simulating content nobody is in.
  • Batched network replication: Inventory updates are coalesced per frame — if multiple items drop simultaneously, a single network packet carries the final state rather than one packet per item.
  • Differential UI rendering: The inventory grid only creates or destroys elements for items that actually changed between updates, rather than rebuilding the full grid every time. Updates are also deferred while the panel is closed and applied lazily on open.
  • Client-local rendering: Damage numbers, health bars, and weapon visuals are all computed and rendered entirely on the client. The server signals that something happened; the visual cost is distributed across clients rather than centralized on the server.

Systems

Data & Persistence

Player data is persisted using a session-locking datastore library that prevents cross-server data corruption. Each player's profile holds their currency balance, stage progress, task progress, and weapon inventory.

Each weapon in the inventory is tracked as an individual instance with its own unique ID, allowing a player to own multiple copies of the same weapon type with different rolled stats.


Currency

Currency changes are always initiated server-side. The client receives updates reactively and renders the balance — it never writes or reads currency from local state. Other server systems can listen to currency change events without being directly coupled to the currency module.


Stage & Task System

Stages gate progression behind a set of completion tasks. Task definitions — goals, display names, trigger conditions — are shared between server and client from a single authoritative source.

The task system and the stage system are fully decoupled. The task tracker only knows about tasks and their goals; it does not know what stage a player is on or when stages advance. The stage system listens for task completion events and independently decides when to advance the player. Neither system reaches into the other.

Stage gates in the world are data-driven. Parts tagged as gates carry an attribute that names their behavior type. The client loads the corresponding behavior module from a registry and calls it on unlock or lock — adding a new gate type (teleporter, animated door, etc.) requires only a new behavior module, no changes to the gate system itself.


Enemy System

Enemy definitions live in a shared registry that specifies stats, loot tables, and display properties. Adding a new enemy type is an additive operation — a new entry in the registry and a zone assignment.

  • Standard enemies are player-owned: each spawns near a specific player, targets that player, and despawns when the player leaves or moves away. Loot goes entirely to the owning player.
  • Global enemies (e.g., shared boss arenas) operate on a shared model: the zone maintains a target population regardless of which players are present. Damage contributions per player are tracked, and loot on death is distributed proportionally to players above a minimum participation threshold. When a global zone empties of players, the enemy fully resets.

Client-side, enemies render health bars and receive floating damage number popups. Both are local-only with no server involvement.


Weapon Inventory & Merging

Weapons are individually-owned instances with randomized base stats rolled at acquisition time from per-rarity ranges. A player can own multiple copies of the same weapon type, each with distinct stats. Rarities are fully open — the registry supports any custom rarity tier, each with its own stat ranges and drop weights.

Each weapon type can optionally define its own floating damage number style (color, prefix), animation set, and merge outcome odds.

The merge system allows combining nine weapons of the same type and rarity into a single weapon of higher rarity. The resulting rarity is sampled from configurable outcome weights defined per weapon type, so different weapon types can have different upgrade curves.

Equipment changes are atomic — duplicate or rapid-fire requests from the client are safely rejected. All other players' equipped loadouts are also replicated so weapons can be rendered on remote players.


Combat System

CombatManager runs an automatic server-side loop that fires weapon abilities on cooldown for all equipped weapons for every player. The game is intentionally idle-friendly — players can go AFK and still progress. Each weapon can have multiple independent abilities, each on its own cooldown timer.

Phased Ability System

The core challenge of the combat system: VFX must be synchronized with damage application, but the server must retain full control over who gets hit. Letting the client report its own targets is a security hole.

The solution is a phased execution model that separates timing from resolution. When an ability fires, the server signals the client to begin the VFX sequence. The client plays its animation or projectile tween, and at the exact moment of visual impact it signals the server. The server — entirely independently, using only its own position data — re-runs the targeting logic and applies damage.

Critically, both sides use the same targeting module to guarantee they arrive at the same targets. The client controls when damage applies (for visual sync); the server controls who gets hit and how much. Phase signals that arrive out of order, too fast, or past an expiry window are silently rejected. The rejection system also accounts for player's latency.

This replaced an earlier system where the client reported its own hit targets, which was both architecturally fragile and exploitable.


Orbital Weapon Rendering

Equipped weapons are rendered as animated 3D models that accompany the player, driven entirely on the client at render framerate. The server has no involvement in weapon visual positioning.

Weapon models are purpose-built to support animation layered on top of positional following. When a weapon is added to the loadout, the rendering system notifies the weapon's visual module, giving it a hook to start idle animations or other persistent effects. On removal, the same module is notified to clean up.


Status Effect System

A standalone module for applying timed buffs and debuffs to any target — players or enemies. Effects carry a type, a duration, and an optional data payload. The module has no dependency on combat; any server system can apply a status effect to any target.

Applied effects are replicated to all clients so the visual layer can respond accordingly.


Inventory UI

The inventory panel uses differential rendering — on each update, the grid is patched rather than rebuilt. Only elements for newly added or removed items are created or destroyed. While the panel is closed, incoming updates are cached silently and applied the moment it reopens.

linkLinks

infoThe live demo reflects the current state of the project — code-only. No custom UI, VFX, models, or SFX have been implemented.