Hey everyone,
I’m working on a hitscan pistol in Roblox and have an issue: currently, my bullets don’t always hit exactly where my crosshair is aimed, especially when targets move sideways. It feels like there’s aim prediction or leading applied, which makes shooting feel unnatural.
- When I shoot, the bullet should hit exactly what’s under my crosshair at the moment of firing.
- No aiming ahead of moving targets (no prediction or aim assist).
- The bullet is hitscan, so it should be instant and precise.
Here’s my server script with slight modifications :
Minimalized Server Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local shotEvent = ReplicatedStorage:WaitForChild("Guns"):WaitForChild("Weapons"):WaitForChild("Pistol"):WaitForChild("ShotClient")
local Pistol = require(ReplicatedStorage.Guns.Weapons.Pistol.PistolModule)
local GoldenPistols = {}
local MAX_RANGE = 300
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(character)
local pistolTool = character:WaitForChild("Pistol", 10)
if not pistolTool then
warn("Player "..player.Name.." missing pistol")
return
end
local pistol = Pistol.new(character)
GoldenPistols[player.UserId] = pistol
local humanoid = character:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
GoldenPistols[player.UserId] = nil
end)
end)
end)
Players.PlayerRemoving:Connect(function(player)
GoldenPistols[player.UserId] = nil
end)
shotEvent.OnServerEvent:Connect(function(player, origin, targetPos)
if typeof(origin) ~= "Vector3" or typeof(targetPos) ~= "Vector3" then
warn("Invalid shot data from "..player.Name)
return
end
local pistol = GoldenPistols[player.UserId]
if not pistol then
warn("No pistol instance for "..player.Name)
return
end
local direction = (targetPos - origin)
local distance = math.min(direction.Magnitude, MAX_RANGE)
direction = direction.Unit
local params = RaycastParams.new()
params.FilterDescendantsInstances = {player.Character, pistol.Handle.Parent}
params.FilterType = Enum.RaycastFilterType.Exclude
params.IgnoreWater = true
local raycastResult = workspace:Raycast(origin, direction * distance, params)
if raycastResult then
pistol:ProcessHit(player, raycastResult)
else
-- No hit, but still process firing if needed
pistol:ProcessHit(player, nil)
end
end)
Minimalized Client Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera
local shotEvent = ReplicatedStorage:WaitForChild("Guns"):WaitForChild("Weapons"):WaitForChild("Pistol"):WaitForChild("ShotClient")
local mouse = player:GetMouse()
-- Example firing function, called when player clicks or shoots
local function fire()
if not mouse.Hit then return end
local origin = camera.CFrame.Position
local targetPos = mouse.Hit.p
shotEvent:FireServer(origin, targetPos)
end
-- Bind to left click or your input method
mouse.Button1Down:Connect(fire)
On the server, I raycast from the origin towards the targetPos, clamping the distance and filtering out the shooter’s character. The problem is that even though I’m sending exactly where the player’s crosshair is pointing, shots often don’t register exactly where I’m aiming, especially when players are moving sideways or strafing.
The bullets are hitscan, and I’m not doing any prediction or leading targets. Still, the shot detection feels off, and I want the bullets to hit precisely when the enemy is under the crosshair — no needing to “lead” or guess their movement.
What am I missing here?
- Is it a server/client latency issue?
- Is my raycast origin or direction off by a little due to camera FOV or position?
- Should I be using a different method to get the exact crosshair position in 3D space?
- Any best practices for syncing hitscan weapons so that the client’s crosshair matches server hit detection perfectly?
I’d appreciate any advice or suggestions on how to fix this issue and make the hits feel fair and accurate!
Thanks in advance.
( just saying, normally it connects to another module script and uses that for raycasting, the server just handles the events and connecting stuff
)