Game genres

How to make an FPS game in Roblox Studio

A shooter lives or dies on two things players feel within a minute: whether the guns feel good and whether deaths feel fair. Everything else (modes, unlocks, maps) only matters once those are right. This guide covers what makes an FPS work on Roblox, the systems you need, a build plan from a baseplate to a first match, server scripts for spawns that keep players away from enemies and for kill credit and match scoring, and how to monetize a competitive game without selling better guns.

Updated 26 October 20269 min readGenre guide

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
  1. What makes it work
  2. Design
  3. Systems
  4. Build plan
  5. Spawn script
  6. Kill script
  7. Monetization
  8. Retention
  9. With RoCode
  10. Questions
  11. Sources

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.

Gun roles and the trade each one makes
RoleStrengthWeakness
Assault rifleGood at most ranges, automaticNever the best at any range
SMGFast fire rate and movement, wins up closeDamage falls off at range
ShotgunKills in one or two shots at close rangeUseless past a short distance
SniperKills in one headshot at long rangeSlow fire rate, weak up close
PistolFast to draw, a backupLow 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

Systems, the Roblox pieces they use, and where to learn each one
SystemWhat it doesRoblox piecesGuide
GunsAim on the client, ray and damage on the server, ammo and reloadTool, RemoteEvent, workspace:RaycastGun script
TeamsTwo sides with colours, balanced team picksTeams, TeamTeam system
Safe spawnsSpawn away from enemies with a short shieldPivotTo, ForceFieldThis page
Kill credit and scoreWho killed whom, match end at a scoreHumanoid.Died, attributes, leaderstatsThis page
Match flowIntermission, map vote, match timer, resultsServer state, teleportsRound system
HUDAmmo, health, crosshair, kill feed, scoreboardScreenGuiHealth bar GUI
Sprint and movementSprint, slide, double jump optionsHumanoid.WalkSpeedSprint script
Loadouts and unlocksChoose guns before a match, saved unlocksDataStoreService, GUIInventory
LeaderboardKills, wins, levelOrdered data storeLeaderboard

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

  1. 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.
  2. A greybox map. Three lanes, cover, two spawn areas and a SpawnPoints folder of invisible parts spread around the map. Keep it small enough that eight players meet often.
  3. 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.
  4. Teams. Two teams from the team guide. Both scripts on this page already treat teammates as friends.
  5. 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.
  6. Second and third guns. Each with a clear role from the table above. Test each against the others at every range on your map.
  7. Match flow and HUD. Intermission, a match timer, a results screen, ammo and health on screen, a kill feed.
  8. 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
-- 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.
  • TakeDamage does nothing while a ForceField protects the character, so the shield works only if your guns use TakeDamage, 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
-- 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 matchOver flag stops kills counting during the results screen.
  • LoadCharacterAsync respawns 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.

What fits an FPS, and the trap in each
ItemRoblox featureAccepted?Watch out for
Weapon skins, kill effects, emotesPass or developer productYesNone. This is the safest thing to sell in a competitive game.
Battle pass or season passSubscription or passYesRoblox allows battle passes to be sold as a subscription. Fill it with cosmetics, not guns.
XP boostDeveloper productYesFaster unlocks are fine if every gun can still be earned by playing.
Paid gunsPassOnly as sidegradesA paid gun that beats free guns is pay to win and players notice in their first death to it.
Weapon cases with random skinsDeveloper product or paid currencyCommon, with rulesPaid random items: show every outcome's odds before purchase and respect ArePaidRandomItemsRestricted.
Private serverPrivate serversYesGroups 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.

Screenshot from a Roblox Studio playtest: a shop and HUD in the neon theme.
Studio screenshot. A shop and HUD from RoCode's UI kits (neon theme), in a Studio playtest. Kit screens RoCode can add, not part of a game built in this 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.

You type

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.

RoCode does
  • 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

Build this in Studio

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

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.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.