§ How to · with Picoo
How to make a Roblox FPS (with AI)
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A Roblox FPS needs five wired-up systems: server-authoritative hit detection, ammo + reload, weapon switching, recoil pattern, and aim down sights. Describe your shooter and Picoo writes the scripts for it; the example below shows one way those systems split into files, including the FOV punch on fire and the ADS tween.
Example build: 14 files · 420 lines, three weapons (rifle, shotgun, pistol). The files and numbers you get follow your prompt.
Server-authoritative hits
In the example the client casts the ray and sends the hit position, and the server re-casts from the player's Head before it applies any damage. The client never sends a damage number.
Ammo + reload
Per-weapon magazine size, reserve ammo and reload time. AmmoService keeps ammo on the server, so a fire request with an empty magazine is rejected.
Weapon switching
1/2/3 keys swap weapons. Each weapon is a Tool with attributes (damage, mag, recoil) and a matching WeaponConfig entry.
Recoil pattern
Per-weapon recoil curve (vertical kick + horizontal randomness) applied to the camera, with an FOV punch on each shot for game feel.
ADS (aim down sights)
Right mouse aims: the FOV tweens in, the crosshair narrows and recoil drops.
HUD and hit markers
WeaponHUD shows ammo and reserve, Crosshair narrows on ADS, and HitMarker pops when the server confirms a hit.
Example output from a build like this
14 files in this example. Picoo writes this code for your prompt, so file names and sizes vary.
ServerScriptService/WeaponServer.lua
Hit re-cast + damage application
ServerScriptService/AmmoService.lua
Per-player ammo state, reload state machine
ReplicatedStorage/Modules/WeaponConfig.lua
Per-weapon damage, mag, recoil tables
ReplicatedStorage/Remotes/FireRemote
Fire request RemoteEvent
ReplicatedStorage/Remotes/ReloadRemote
Reload request RemoteEvent
StarterPlayer/StarterPlayerScripts/WeaponClient.lua
Input → ray cast → fire remote
StarterPlayer/StarterPlayerScripts/RecoilController.lua
Recoil curve + FOV punch
StarterPlayer/StarterPlayerScripts/ADSController.lua
FOV tween on right-mouse
StarterPack/Rifle (Tool)
Rifle Tool with attached attributes
StarterPack/Shotgun (Tool)
Shotgun Tool
StarterPack/Pistol (Tool)
Pistol Tool
StarterGui/WeaponHUD.lua
Ammo + reserve display, weapon icon
StarterGui/Crosshair.lua
Dynamic crosshair, narrows on ADS
StarterGui/HitMarker.lua
Server-fired hit marker pop-up
Sample output: ServerScriptService/WeaponServer.lua
--!strict
-- ServerScriptService/WeaponServer.lua (example)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local FireRemote = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("FireRemote")
local Config = require(ReplicatedStorage.Modules.WeaponConfig)
local lastFire: { [number]: number } = {}
FireRemote.OnServerEvent:Connect(function(player: Player, weaponName: string, hitPos: Vector3)
local weapon = Config[weaponName]
if not weapon or typeof(hitPos) ~= "Vector3" then return end
local now = os.clock()
if lastFire[player.UserId] and now - lastFire[player.UserId] < weapon.fireRate then return end
lastFire[player.UserId] = now
-- server re-cast from the shooter's own Head, never from a client origin
local character = player.Character
local head = character and character:FindFirstChild("Head")
if not head or not head:IsA("BasePart") then return end
local offset = hitPos - head.Position
if offset.Magnitude < 0.01 then return end
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
local result = workspace:Raycast(head.Position, offset.Unit * weapon.range, params)
local model = result and result.Instance:FindFirstAncestorOfClass("Model")
local humanoid = model and model:FindFirstChildOfClass("Humanoid")
if humanoid then humanoid:TakeDamage(weapon.damage) end
end)Building a Roblox FPS
FPS is the genre with the most server-load and the most exploit pressure on Roblox. The two patterns that determine whether it survives launch: server-authoritative hit detection, and rate-limited fire events. The example below has both.
The hit detection pattern is hybrid: client casts the ray for instant visual feedback, server re-casts from the player's Head CFrame to validate damage. The client never gets to write a damage number — only a hint position the server can sanity-check. Standard hit-modifier exploits (set damage to 1000, fire through walls) do not work because the server re-casts from authoritative state.
The ammo system is server-side state. Per-player ammo + mag count lives in AmmoService — exploiters cannot empty-fire (server rejects the fire if mag is 0) or skip reload (server tracks reload duration). Reload is a state machine: idle → reloading (1.5 s in this example) → idle. During reload, fire requests are rejected.
Weapon switching is a Tool-based system. Each weapon is a Tool in StarterPack with attached attributes (damage, mag, fireRate, recoil). Switching is a Roblox-native equip/unequip — no custom switch logic needed. To add a weapon: drop a Tool in StarterPack, add an entry to WeaponConfig.lua with the same name. Picoo can also regenerate WeaponConfig with more entries via a follow-up prompt.
Recoil is a per-weapon curve. RecoilController applies vertical kick + horizontal randomness to the camera CFrame on every fire. FOV punch (5° here, recovering in 0.2 s) gives the fire feel without affecting hit detection. ADS multiplies recoil by 0.6 in this example — tunable per weapon.
See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.
Frequently asked
Is hit detection client-side or server-side?+
Server-side in this example. The client casts the ray for instant visual feedback (sparks, hit marker), and the server re-casts from the player's Head with workspace:Raycast to decide damage. No client-trusted hits.
Can I add more weapons?+
Yes. Add an entry to WeaponConfig.lua with damage, mag, fireRate, recoil — and drop a new Tool in StarterPack. Picoo can also regenerate the WeaponConfig with more entries.
How is recoil handled?+
Per-weapon recoil curve in WeaponConfig (vertical kick + horizontal random). RecoilController applies it on the camera CFrame. FOV punch is a tween on FieldOfView.
Does ADS reduce recoil?+
In this example, yes. ADS tweens FOV down (zoom in), narrows the crosshair, and applies a recoil multiplier of 0.6, set per weapon.
What about netcode for high-ping players?+
In this example the server uses the client's reported hit position only as an aim direction and re-casts from the player's Head on the server, so the damage decision never rests on a number the client sends. If your game needs lag compensation, ask for it in a follow-up prompt.
Related Picoo prompts
Roblox combat system
3 scripts · 1,181 lines · one recipe call. Press Play and fight with M1, E, F, Q, R and Shift.
How to prevent exploits in Roblox
Example output: 4 files · 160 lines of guards. The model writes this code for your game, so the files and line counts vary.
Roblox currency system
Example build: 4 files · 165 lines. Cash, Gems and a premium currency.