In short
A Roblox FPS is a gun whose shots the server checks, a first-person camera, a map with lanes, cover and clear sightlines, spawns that put players away from enemies, kill credit and a match that ends at a score or a time. Build one gun, one small map and a team deathmatch to 25 kills first, playtest it with eight people, then add guns with different roles, modes, unlocks and cosmetics.
- The server decides every hit: the client sends where it aimed, never who it hit or how much damage.
- Spawn killing ends shooters faster than bad guns. Pick spawns by distance from enemies, with a short shield.
- Sell looks and progress speed, not damage: paid guns that beat free ones empty a competitive game.
On this page
What makes an FPS game work
The loop in one line: spawn, find an enemy, shoot first, die or score, respawn within seconds. Roblox's Shooter genre has three subgenres: Battle Royale (many players, last one or team standing wins), Deathmatch Shooter (eliminate the other players or team) and PvE Shooter (fight computer-controlled enemies). A first FPS is usually a deathmatch; it needs the fewest systems.
- Every second: gun feel. Recoil, a muzzle flash, a hit marker, a sound for hits and another for kills. Players forgive a lot in a game where shooting feels good.
- Every 10 seconds: an encounter. Maps should bring players together often. Long walks with nobody to shoot are where players quit.
- Every life: a fair chance. Spawning safely, seeing who shot you, and losing to aim rather than to lag or a paid gun.
- Every match: a result. A scoreboard, a winner, an MVP, then straight into the next match.
- Every session: progress. Levels, unlocked guns and attachments, challenge rewards. Players stay for the next unlock.
Shooters that empty out usually share the same faults: spawns next to enemies, one gun that beats all others, hits that do not count when they looked clean, maps so large players rarely meet, or a store that sells stronger weapons. Each is a fairness problem. Fix fairness before you add content.
Guns, time to kill and maps
Time to kill (how many hits a kill takes, and how quickly a gun can land them) sets the game's whole feel. Low time to kill rewards whoever sees the other first and makes positioning everything. High time to kill rewards tracking aim and lets players react and escape. Pick one deliberately and balance every gun around it.
| Role | Strength | Weakness |
|---|---|---|
| Assault rifle | Good at most ranges, automatic | Never the best at any range |
| SMG | Fast fire rate and movement, wins up close | Damage falls off at range |
| Shotgun | Kills in one or two shots at close range | Useless past a short distance |
| Sniper | Kills in one headshot at long range | Slow fire rate, weak up close |
| Pistol | Fast to draw, a backup | Low damage |
Every gun should lose to another in some situation. If one gun wins at every range, the game has one gun.
Maps. A common layout for small deathmatch maps is three lanes (left, middle, right) with connections between them, so players can flank. Give every long sightline a way around it, put cover every few steps on open ground, and keep spawn areas out of sight of the middle of the map. Build the map in grey parts first and play it before decorating; changing a wall is cheap until it has detail on it.
Camera. For a true first-person shooter, set StarterPlayer.CameraMode to LockFirstPerson in the Properties window. Some Roblox shooters use third person or an over-the-shoulder camera instead; that is a design choice with different aiming and peeking rules, so decide before you build maps around it.
The systems an FPS needs
| System | What it does | Roblox pieces | Guide |
|---|---|---|---|
| Guns | Aim on the client, ray and damage on the server, ammo and reload | Tool, RemoteEvent, workspace:Raycast | Gun script |
| Teams | Two sides with colours, balanced team picks | Teams, Team | Team system |
| Safe spawns | Spawn away from enemies with a short shield | PivotTo, ForceField | This page |
| Kill credit and score | Who killed whom, match end at a score | Humanoid.Died, attributes, leaderstats | This page |
| Match flow | Intermission, map vote, match timer, results | Server state, teleports | Round system |
| HUD | Ammo, health, crosshair, kill feed, scoreboard | ScreenGui | Health bar GUI |
| Sprint and movement | Sprint, slide, double jump options | Humanoid.WalkSpeed | Sprint script |
| Loadouts and unlocks | Choose guns before a match, saved unlocks | DataStoreService, GUI | Inventory |
| Leaderboard | Kills, wins, level | Ordered data store | Leaderboard |
The gun guide is the foundation: the client sends only an aim point, the server casts its own ray from the gun, checks fire rate and ammo, and deals damage. That split is the genre's answer to exploiters: they control their own device, so a client that reports hits or damage can be made to lie. Everything on this page builds on top of that gun.
Build plan: from a baseplate to launch
- One gun. Build the blaster from the gun guide and shoot a dummy until the numbers feel right. Add a hit marker and a hit sound.
- A greybox map. Three lanes, cover, two spawn areas and a
SpawnPointsfolder of invisible parts spread around the map. Keep it small enough that eight players meet often. - Spawns and kill credit. Add the SafeSpawns and KillScore scripts, and one line to the gun's server script that tags each hit with the shooter.
- Teams. Two teams from the team guide. Both scripts on this page already treat teammates as friends.
- First playable: team deathmatch to 25. Play with as many people as you can gather. Watch for spawn deaths, the lane nobody uses and the spot everyone camps.
- Second and third guns. Each with a clear role from the table above. Test each against the others at every range on your map.
- Match flow and HUD. Intermission, a match timer, a results screen, ammo and health on screen, a kill feed.
- Launch features. A second map, levels and unlocks, a cosmetic store, and the Maturity & Compliance questionnaire, where shooting is violence and constant shooting counts as repeated.
Spawns that stop spawn killing
Fixed spawn pads let players learn where enemies appear and wait there. This script puts each player, every time they spawn, at the spawn point whose nearest enemy is furthest away, with a little randomness so the choice is not predictable, and a two-second ForceField.
-- Script in ServerScriptService named SafeSpawns.
-- Puts each player, every time they spawn, at the spawn point farthest from
-- their enemies, with a short ForceField so nobody is shot on arrival.
local Players = game:GetService("Players")
local spawnPoints = workspace:WaitForChild("SpawnPoints") :: Folder -- plain parts
local SPAWN_SHIELD = 2 -- seconds of ForceField after each spawn
local function isEnemy(a: Player, b: Player): boolean
return a ~= b and (not a.Team or a.Team ~= b.Team)
end
local function safestSpawn(player: Player): BasePart?
local best: BasePart?, bestScore = nil, -math.huge
for _, point in spawnPoints:GetChildren() do
if point:IsA("BasePart") then
local nearestEnemy = 500 -- counts as "far" when nobody is around
for _, other in Players:GetPlayers() do
local root = other.Character and other.Character:FindFirstChild("HumanoidRootPart")
if isEnemy(player, other) and root and root:IsA("BasePart") then
nearestEnemy = math.min(nearestEnemy, (root.Position - point.Position).Magnitude)
end
end
local score = nearestEnemy + math.random() * 15 -- a little noise so spawns are not predictable
if score > bestScore then
best, bestScore = point, score
end
end
end
return best
end
local function onCharacter(player: Player, character: Model)
character:WaitForChild("HumanoidRootPart")
local point = safestSpawn(player)
if point then
character:PivotTo(point.CFrame + Vector3.new(0, 3, 0))
end
local shield = Instance.new("ForceField") -- TakeDamage does nothing while it is there
shield.Parent = character
task.delay(SPAWN_SHIELD, shield.Destroy, shield)
end
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
onCharacter(player, character)
end)
end)
- Spread 10 or more spawn points around the map, out of the main sightlines. With only two, "furthest from enemies" has nothing to choose from.
TakeDamagedoes nothing while a ForceField protects the character, so the shield works only if your guns useTakeDamage, as the gun guide does, rather than setting Health directly.- Players with no team count everyone as an enemy, which is free-for-all. With teams, only the other team counts.
- If you also use SpawnLocations, this script moves the character after Roblox spawns it there. Use one system or the other to keep spawns predictable for you.
Kill credit and match scoring
When a Humanoid dies, the server needs to know who killed it. The simplest reliable way: the gun's server script writes the shooter's UserId on the target's Humanoid every time it deals damage, and the score script reads it on death. In the gun guide's server script, add this line right before target:TakeDamage(damage): target:SetAttribute("LastHitBy", player.UserId).
-- Script in ServerScriptService named KillScore.
-- Credits each kill to the last enemy who hit the victim and ends the match
-- when someone reaches the score. Your weapon script must tag hits:
-- target:SetAttribute("LastHitBy", player.UserId) right before TakeDamage.
local Players = game:GetService("Players")
local SCORE_TO_WIN = 25
local matchOver = false
local function getKills(player: Player): IntValue?
local leaderstats = player:FindFirstChild("leaderstats")
local kills = leaderstats and leaderstats:FindFirstChild("Kills")
return if kills and kills:IsA("IntValue") then kills else nil
end
local function endMatch(winner: Player)
matchOver = true -- kills stop counting until the next match
workspace:SetAttribute("LastWinner", winner.Name)
task.wait(8) -- time to show the result screen
for _, player in Players:GetPlayers() do
local kills = getKills(player)
if kills then
kills.Value = 0
end
task.spawn(player.LoadCharacterAsync, player)
end
matchOver = false
end
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local kills = Instance.new("IntValue")
kills.Name = "Kills"
kills.Parent = leaderstats
player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid") :: Humanoid
humanoid.Died:Once(function()
local killerId = humanoid:GetAttribute("LastHitBy")
local killer = if typeof(killerId) == "number" then Players:GetPlayerByUserId(killerId) else nil
local killerKills = killer and getKills(killer)
local enemies = killer and killer ~= player and (not killer.Team or killer.Team ~= player.Team)
if killer and killerKills and enemies and not matchOver then
killerKills.Value += 1
if killerKills.Value >= SCORE_TO_WIN then
endMatch(killer)
end
end
end)
end)
end)
- Kills by teammates or by yourself (falling off the map) do not count. The
matchOverflag stops kills counting during the results screen. LoadCharacterAsyncrespawns everyone for the new match. Roblox's docs note it also clears the Backpack and PlayerGui, so test that every player's guns and HUD come back after the reset, and have your loadout script give guns again if they do not.- For a kill feed, fire a RemoteEvent to all clients from the same place the kill is counted, with the killer's and victim's names.
- For assists, store a small table of everyone who damaged the target in the last few seconds instead of only the last one.
Monetization that fits a shooter
Shooter players are quick to call a game pay to win, and they leave when a purchase decides fights. Sell what players see and how fast they progress, not what changes the damage numbers.
| Item | Roblox feature | Accepted? | Watch out for |
|---|---|---|---|
| Weapon skins, kill effects, emotes | Pass or developer product | Yes | None. This is the safest thing to sell in a competitive game. |
| Battle pass or season pass | Subscription or pass | Yes | Roblox allows battle passes to be sold as a subscription. Fill it with cosmetics, not guns. |
| XP boost | Developer product | Yes | Faster unlocks are fine if every gun can still be earned by playing. |
| Paid guns | Pass | Only as sidegrades | A paid gun that beats free guns is pay to win and players notice in their first death to it. |
| Weapon cases with random skins | Developer product or paid currency | Common, with rules | Paid random items: show every outcome's odds before purchase and respect ArePaidRandomItemsRestricted. |
| Private server | Private servers | Yes | Groups use them for practice matches and games among friends. |
A pass is a one-time purchase; a developer product can be bought again. Permanent skins and gun unlocks are passes; XP boosts are developer products. Check ownership on the server before giving anything, as in the game pass guide.
What brings shooter players back
- Unlock progression. Levels that unlock guns, attachments and cosmetics give every match a little progress, win or lose.
- Challenges. Daily and weekly goals ("10 kills with a shotgun") push players into guns and modes they would skip.
- Seasons. A new map, a gun and a battle pass every few weeks give lapsed players a reason to return.
- Modes. Free-for-all, team deathmatch, capture the flag, a one-life mode. The same map plays differently in each.
- Squads. Friends who join as a group and stay on one team play longer. Keep groups together when you balance teams.
For a game where the enemies are computer-controlled, the zombie survival guide covers waves and hordes; for melee fighting with abilities, see the battlegrounds guide.
Build it with RoCode
RoCode is an independent AI agent for Roblox Studio, not made by Roblox. You describe the game in a web chat and its Studio plugin writes the Luau and builds parts, scripts and UI in the place you have open. For a shooter it can write the gun, spawn, team and scoring scripts with server checks, build a greybox map, and add a shooter HUD, a leaderboard and a store from its UI kits. It cannot make gun meshes or animations, and it cannot tell you whether your time to kill feels right.
Make the first playable version of a first-person shooter in my place: a small three-lane greybox map with cover, two teams, a raycast rifle whose shots, damage and ammo the server checks, 12 spawn points where players spawn far from enemies with a 2 second shield, kills counted in leaderstats, and a match that ends at 25 kills.
- Writes the gun, spawn, team and kill scripts, with every hit decided on the server
- Builds the greybox map, then adds guns, modes and a match timer in rounds
- Can add UI kit screens such as a shooter HUD, a leaderboard, a game pass store and a battle pass
- Can run a test robot that equips tools, clicks and presses keys, and reports PASS or FAIL
RoCode does not publish the game, create passes or spend Robux. The free plan gives free credits every day, no card needed. How RoCode builds a game.
Questions
How do I make an FPS game on Roblox?
Start with a gun whose shots the server checks (the gun guide), set the camera to first person, build a small three-lane map, add safe spawns and kill credit from this page, and run team deathmatch to a score. Then add guns, modes and unlocks.
How do I make Roblox first person only?
Select StarterPlayer in the Explorer and set CameraMode to LockFirstPerson. Every player's camera then stays in first person.
How do I stop spawn killing?
Use many spawn points and pick, on the server, the one furthest from enemies each time a player spawns, then give a short ForceField. The SafeSpawns script does both.
How do I stop exploiters in my shooter?
Decide everything on the server: the client sends only where it aimed, and the server casts its own ray, checks fire rate and ammo, and deals damage. An exploiter controls their own device, so never accept hits or damage from it.
Should my shooter be first or third person?
Either works on Roblox. First person makes aiming and corners feel like a classic FPS; third person lets players see around cover, which changes map design. Decide before you build maps.
Sources and further reading
- Roblox Creator Docs: Genres. the Shooter genre and its subgenres
- Roblox Engine API: StarterPlayer. CameraMode and LockFirstPerson
- Roblox Engine API: Humanoid:TakeDamage. no damage while a ForceField protects the target
- Roblox Engine API: Player:LoadCharacterAsync. clears Backpack and PlayerGui
- Roblox Creator Docs: Subscriptions. battle passes as subscriptions
- Roblox Creator Docs: Paid random items policy. odds for weapon cases
- Roblox Creator Docs: Content maturity and compliance. violence intensity and frequency
- RoCode guide: Gun script. server-checked shots, damage and ammo
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 26 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.