Raycasts aren’t accurate if the player is moving (CLIENT SIDED RAYCASTS)

Ive recently created a raycast gun system that works very nicely while the player is stationary or moving in a very linear way, however, when the player moves in a less straight/linear way OR the target is also moving, the raycast fails to detect the target and doesnt cause any damage: the raycasts are created on the client (the scripts are provided below) and the damage is done on the server. Ive tried using unreliable remote events in case the issue was too much information being sent out too quickly but that did not remedy the issue. any and all help is greatly appreciated.

-- client
repeat
	wait()
until game:IsLoaded()
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local t = script.Parent
local configurations = t.Configuration

local Rep_Storage = game:WaitForChild("ReplicatedStorage")
local EventsFolder = Rep_Storage:WaitForChild("RemoteEvents")

local Anims = t.Anims

local ammo_UI = t.Ammo
local Shoot_UI = t.ShootGUI

local reload_event = EventsFolder:WaitForChild("ReloadEvent")

local Device = nil

local animsC = {Hold = Anims.Hold,ReloadAnim = Anims.Reload}
local loadedHold = player.Character.Humanoid.Animator:LoadAnimation(animsC.Hold)
local loadedReload = player.Character.Humanoid.Animator:LoadAnimation(animsC.ReloadAnim)

local configs = {
	Ammo = configurations.Ammo.Value,
	
	Bullet = configurations.Bullet.Value,
	
	Casting = configurations.Casting.Value,
	
	Damage = configurations.Damage.Value,
	
	HeadShotMultiply = configurations.HeadShotMulti.Value,
	
	Cooldown = configurations.Cooldown.Value,
	
	ObsticalDamage = configurations.ObsticalDamage.Value,
	
	Range = configurations.Range.Value,
	
	Reload = configurations.Reload.Value,
	
	Type = configurations.Type.Value
}

local ammo_folder = player:WaitForChild("AmmoValues")

local Local_gun_Ammo = ammo_folder[configs.Bullet.Name]


local CurrentAmmo = configs.Ammo
local canShoot = false

local ShootEvent = t:WaitForChild("Fire")
local shootingM = false

local uis = game:GetService("UserInputService")
local runservice = game:GetService("RunService")
local lighting = game:GetService("Lighting")
local tweenservice = game:GetService("TweenService")
local debris = game:GetService("Debris")


local db = false

local detailAttachment = t.Handle.DetailAttachment

local reloading = false

local function reload()
	if reloading == false and t.Parent == player.Character then
		local ValueOfSubtraction
		if Local_gun_Ammo.Value >= configs.Ammo then
			ValueOfSubtraction = configs.Ammo - CurrentAmmo
		else
			ValueOfSubtraction = Local_gun_Ammo.Value - CurrentAmmo
		end
		
		if ValueOfSubtraction <= 0 then
			return
		end
		
		print(ValueOfSubtraction)
		reload_event:FireServer(ValueOfSubtraction,configs.Bullet.Name)
		
		reloading = true
		loadedReload:Play()
		t.ReloadSound:Play()
		wait(configs.Reload)
		
		CurrentAmmo += ValueOfSubtraction
		CurrentAmmo = math.min(CurrentAmmo,configs.Ammo)
		
		reloading = false
	end
end

coroutine.wrap(function()
	runservice.RenderStepped:Connect(function()
		if CurrentAmmo <= 0 and Local_gun_Ammo.Value > 0 then
			canShoot = false
			reload()
		elseif CurrentAmmo >= 1 and t.Parent == player.Character then
			canShoot = true
		end
	end)
end)()

coroutine.wrap(function()
	while wait() do
		if reloading == false then
			ammo_UI.Amnt.Text = ""..CurrentAmmo.."|"..Local_gun_Ammo.Value
		elseif reloading == true then
			ammo_UI.Amnt.Text = "RELOADING"
		end
	end
end)()

local function shoot()
	if db == false and canShoot == true  and CurrentAmmo > 0 then
		CurrentAmmo -= 1
		db = true
		t.Click:FireServer(configs.Type)
		local originPos = t.Handle.Origin.CFrame
		local direction = (t.Handle.Origin.CFrame.LookVector*configs.Range)
		local size1 = Vector3.new(0.5,1,0.5)
		local params = RaycastParams.new()
		params.FilterDescendantsInstances = {script.Parent,script.Parent.Parent}
		params.FilterType = Enum.RaycastFilterType.Blacklist
		local raycast = workspace:Blockcast(originPos,size1,direction,params)
		if raycast then
			script.Parent.EffectHitEVent:FireServer(raycast.Position)
			if raycast.Instance.Name == "Crate" then
				EventsFolder.CratePunch:FireServer(raycast.Instance,configs.ObsticalDamage)
			else
			if raycast.Instance.Parent:FindFirstChild("Humanoid") then
				if raycast.Instance.Name == "Head" then
					ShootEvent:FireServer(raycast.Instance.Parent.Humanoid,raycast.Instance,true)
				elseif raycast.Instance.Name ~= "Head" then
					ShootEvent:FireServer(raycast.Instance.Parent.Humanoid,raycast.Instance,false)
				end
				end
			end
		end
		ShootEvent:FireServer()
		--
		wait(configs.Cooldown)
		db = false
	end
end


--SHOTGUN SHOOT FUNCTION
local function SpreadShot()
	local Origins = t.Handle.OriginsFolder
	if db == false and canShoot == true then
		CurrentAmmo -= 1
		db = true
		t.Click:FireServer(configs.Type)
		for i,v in pairs(Origins:GetChildren()) do
			if v:IsA("Attachment") then
				local originPos = v.Position
				local direction = (v.CFrame.LookVector *configs.Range)
				local params = RaycastParams.new()
				params.FilterDescendantsInstances = {script.Parent,script.Parent.Parent}
				params.FilterType = Enum.RaycastFilterType.Blacklist
				local raycast = workspace:Raycast(originPos,direction,params)
				if raycast then
					if raycast.Instance.Parent:FindFirstChild("Humanoid") then
						if raycast.Instance.Name == "Head" then
							ShootEvent:FireServer(raycast.Instance.Parent.Humanoid,raycast.Instance,true)
						elseif raycast.Instance.Name ~= "Head" then
							ShootEvent:FireServer(raycast.Instance.Parent.Humanoid,raycast.Instance,false)
						end
					else
						if raycast.Instance.Name == "Crate" then
                            --t.DestroyEvent:FireServer(raycast.Instance)
                           -- EventsFolder:WaitForChild("CratePunch"):FireServer(raycast.Instance)
						end
					end
				end
				ShootEvent:FireServer()
				wait(configs.Cooldown)
				db = false
			end
		end
	end
end

local function DeviceCheck()
	if uis.TouchEnabled and not uis.KeyboardEnabled and not uis.MouseEnabled then
		Device = 1
		print("MOBLE")
	elseif not uis.TouchEnabled and uis.KeyboardEnabled and uis.MouseEnabled then
		Device = 2
		print("PC")
	end
end


t.Equipped:Connect(function()
	DeviceCheck()
	if Device == 1 then
		Shoot_UI.Parent = player.PlayerGui
	end
	loadedHold:Play()
	ammo_UI.Parent = player.PlayerGui
	if CurrentAmmo <= 0  then
		CurrentAmmo = math.min(Local_gun_Ammo.Value,configs.Ammo)
	end
	
end)

t.Unequipped:Connect(function()
	loadedHold:Stop()
	Shoot_UI.Parent = t
	ammo_UI.Parent = t
	shootingM = false
end)

repeat
	wait()
until Device

if configs.Type == "Single" then
	if Device == 1 then
		print("ONE")
		Shoot_UI.Shoot.MouseButton1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				shoot()
			end
		end)
	elseif Device == 2 then
		print("TWO")
		mouse.Button1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				shoot()
			end
		end)
	end
elseif configs.Type == "Automatic" then
	if Device == 1 then
		Shoot_UI.Shoot.MouseButton1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				shootingM = true
			end
		end)
		Shoot_UI.Shoot.MouseButton1Up:Connect(function()
			shootingM = false
		end)
	elseif Device == 2 then
		mouse.Button1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				shootingM = true
			end
		end)
		mouse.Button1Up:Connect(function()
			shootingM = false
		end)
	end
elseif configs.Type == "Shotgun" then
	if Device == 1 then
		Shoot_UI.Shoot.MouseButton1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				SpreadShot()
			end
		end)
	elseif Device == 2 then
		mouse.Button1Down:Connect(function()
			if t.Parent == player.Character and reloading == false then
				SpreadShot()
			end
		end)
	end
end



coroutine.wrap(function()
	runservice.RenderStepped:Connect(function()
		if shootingM == true and reloading == false and canShoot == true then
			shoot()
		else
			return
		end
	end)
end)()

uis.InputBegan:Connect(function(input,_gameProcessed)
	if input.KeyCode == Enum.KeyCode.R and _gameProcessed == false then
		if CurrentAmmo < configs.Ammo then
			reload()
		end
	end
end)

-- server 

local rep_storage = game:GetService("ReplicatedStorage")
local debris = game:GetService("Debris")

local effectsStorage = rep_storage:WaitForChild("Effects")
local effects = {blood = effectsStorage.Blood}

local killsoundEffects = rep_storage.KillSounds:GetChildren()

local t = script.Parent
local configurations = t.Configuration

local configs = {
	Ammo = configurations.Ammo.Value,

	Bullet = configurations.Bullet.Value,

	Casting = configurations.Casting.Value,

	Damage = configurations.Damage.Value,

	HeadShotMultiply = configurations.HeadShotMulti.Value,

	Cooldown = configurations.Cooldown.Value,

	ObsticalDamage = configurations.ObsticalDamage.Value,

	Range = configurations.Range.Value,

	Reload = configurations.Reload.Value,

	Type = configurations.Type.Value
}


local tweenservice = game:GetService("TweenService")
local info = TweenInfo.new(
	.001,
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.In,
	0,
	false,
	0
)


local posT

coroutine.wrap(function()
	while wait() do
		if configs.Type ~= "Shotgun" then
			posT = t.Handle.TweenLocation.WorldPosition
		end
	end
end)()


local function AdjustKillerGUN(plr,humanoid)
	local name = humanoid.parent.Name
	local shotplayer = nil
	for i,v in pairs(game.Players:GetChildren()) do
		if v.Name == name then
			shotplayer = v
			shotplayer.InformationValues.LastPlayer.Value = plr
		--	print(""..shotplayer.Name.." was shot by: "..plr.Name)
		end
	end
end

local function ResetPlayerValues(humanoid)
	local PlayerOfReset = nil
	for i,v in pairs(game.Players:GetChildren()) do
		if v.Name == humanoid.Parent.Name then
			PlayerOfReset = v
			PlayerOfReset.InformationValues.LastPlayer.Value = nil
		end
	end
end

script.Parent.Fire.OnServerEvent:Connect(function(plr,humanoid,PartHit,Headshot)
	if humanoid then
		if humanoid.Health ~= 0 and humanoid.Health > 0 then
			if Headshot == true then
				AdjustKillerGUN(plr,humanoid)
				local clonedBlood = effects.blood:Clone()
				clonedBlood.Parent = PartHit
				clonedBlood:Emit(50)
				debris:AddItem(clonedBlood,1)
				humanoid.Health -= script.Parent.Configuration.Damage.Value*script.Parent.Configuration.HeadShotMulti.Value
				humanoid.Parent.HumanoidRootPart.BULLETHIT:Play()
				if humanoid.Health <= 0 then
					local chosenSound = killsoundEffects[math.random(1,#killsoundEffects)]
					local clone = chosenSound:Clone()
					clone.Parent = script.Parent
					clone:Play()
					debris:AddItem(clone,10)
					plr.Kills.Value +=1
					rep_storage.RemoteEvents.Kill:FireClient(plr,humanoid.Parent,script.Parent.Name)
					rep_storage.RemoteEvents.KillALL:FireAllClients(plr,humanoid.Parent,script.Parent.Name)
					coroutine.wrap(function()
						wait(1)
						ResetPlayerValues(humanoid)
					end)
					humanoid.Parent.HumanoidRootPart.Anchored = true
					for i,v in pairs(PartHit.Parent:GetDescendants()) do
						if v:IsA("BasePart") then
							v.CanCollide = false
							v.CanQuery = false
							v.CanTouch = false
						end
					end
				end
			elseif Headshot == false and humanoid then
				AdjustKillerGUN(plr,humanoid)
				local clonedBlood = effects.blood:Clone()
				clonedBlood.Parent = PartHit
				clonedBlood:Emit(50)
				debris:AddItem(clonedBlood,1)
				humanoid.Health -= script.Parent.Configuration.Damage.Value
				humanoid.Parent:FindFirstChild("HumanoidRootPart").BULLETHIT:Play()
				if humanoid.Health <= 0 then
					plr.Kills.Value +=1
					local chosenSound = killsoundEffects[math.random(1,#killsoundEffects)]
					local clone = chosenSound:Clone()
					clone.Parent = script.Parent
					clone:Play()
					debris:AddItem(clone,10)
					rep_storage.RemoteEvents.Kill:FireClient(plr,humanoid.Parent,script.Parent.Name)
					rep_storage.RemoteEvents.KillALL:FireAllClients(plr,humanoid.Parent,script.Parent.Name)
					humanoid.Parent.HumanoidRootPart.Anchored = true
					for i,v in pairs(PartHit.Parent:GetDescendants()) do
						if v:IsA("BasePart") then
							v.CanCollide = false
							v.CanQuery = false
							v.CanTouch = false
						end
					end
				end
			end
		end
	end
end)


t.Click.OnServerEvent:Connect(function(plr,typeG)
	if typeG == 'Single' or typeG == "Automatic" then
		local detailAttachment = t.Handle.DetailAttachment
		detailAttachment.ShotFire:Emit(90)
		t.Handle.Sound:Play()
		local clonedcasting = configs.Casting:Clone()
		local bullet = configs.Bullet
		clonedcasting.Parent = t
		clonedcasting.CFrame = t.Handle.CastingsAttachment.WorldCFrame
		t.Handle.TweenLocation.Shoot:Emit(10)
		clonedcasting.Velocity = clonedcasting.CFrame.LookVector * 5
		debris:AddItem(clonedcasting,1)
		coroutine.wrap(function()
			detailAttachment.ShotLight.Enabled = true
			task.wait(.1)
			detailAttachment.ShotLight.Enabled = false
		end)()
	elseif typeG == "Shotgun" then
		local detailAttachment = t.Handle.DetailAttachment
		detailAttachment.ShotFire:Emit(90)
		t.Handle.Sound:Play()
		local clonedcasting = configs.Casting:Clone()
		local bullet = configs.Bullet
		clonedcasting.Parent = t
		clonedcasting.CFrame = t.Handle.CastingsAttachment.WorldCFrame
		clonedcasting.Velocity = clonedcasting.CFrame.LookVector * 5
		debris:AddItem(clonedcasting,1)
		coroutine.wrap(function()
			detailAttachment.ShotLight.Enabled = true
			task.wait(.1)
			detailAttachment.ShotLight.Enabled = false
		end)()
		for i,v in pairs(t.Handle.OriginsFolder:GetChildren()) do
			v.Shoot:Emit(10)
		end
	end
end)

![Screen Shot 2025-06-10 at 12.06.18 PM|664x500](upload://yq4ed8iDuHRkQPtlUSq7ANuhTpg.png)
1 Like

It is not accurate becouse handle is attached to a part that plays animation, movement influences that
Best idea is to raycast from a camera

Some tips:

repeat
	wait()
until game:IsLoaded()

Can be simplified into:

if not game:IsLoaded() then
	game.Loaded:Wait()
end

You can separate envirement by doing:

do
local hi = "a"
end
print(hi) --nil

Avoid creating function if it has same envirement

Its best to have function “cached” instead of being created every time if it keeps the same envirement (closure)

2 Likes

I strongly recommend picking apart Roblox’s FPS system template which already works and from there you will learn all about it. I modified their FPS system and created my own weapons with it. You can experience it here.

1 Like

Thank you for the game loaded tip, as for the animation, it is not constantly playing, only when the player reloads does the gun ever move, other than that it remains in a constant position the entire time, therefore the origin position should not move

(mind you the game is NOT an fps so i cant raycast from the camera)

That doesn’t really metter.
You should always be raycasting from a camera.
Any movement otherwise will influnce the hit position very weirdly;
Unless you are doing VR game you should always be raycasting from a camera.

Edit:
If its a 2D game then you should raycast from a HumanoidRootPart

1 Like

Alright ill try raycasting from the humanoid root part, just for context though, why would this be better? or why would it remedy the issue

Unless you want beyond cursed controlls then continue what you where doing.
Raycast goes in a straight line, any movement or animation will obviously influence that.
Your argumentation only proves that “fires fine when standing”;
Becouse no animations plays that could influence it when you are standing still.

I just tried it from the HumanoidRootpart and the issue persists

local originPos = player.Character.HumanoidRootPart.CFrame
--local direction = (t.Handle.Origin.CFrame.LookVector*configs.Range)
local direction = (player.Character.HumanoidRootPart.CFrame.LookVector * configs.Range)

Original pos has to be Vector3:
player.Character.HumanoidRootPart.CFrame.Position

When i do that i receive an error

12:42:12.038 Unable to cast Vector3 to CoordinateFrame - Client - MAINCLIENT~~:132

Oh that becouse you are using shapecasts and not raycasts.
I would recomend you to use raycasts instead

Thank you for visiting my experience! I will provide helpful feedback and my analysis on this issue. Your video displays something different than what you would see traditionally when you have a first person shooter. This draws attention to the fact that it would be a complex challenge to transition the code for Roblox’s FPS system template to a third person shooter. Nonetheless I do believe it is still the correct option in this case. You already have all of the code laid out and it works both on the client and on the server. You then piece by piece change the code to align with the exact logic you need applied for your third person shooter. When it is finished, you won’t run into any issues, as the code is already laid out and working. It may not be perfect, which I know it is not because they do an angle check to verify valid hits which should be a magnitude check instead(already replaced that in my game). Overall it’s still the better option. This concludes my feedback and analysis. I hope it helps :slightly_smiling_face:

ive just tried it and it still hasnt worked, for context, i USED to have raycasts but i switched to block casts in an effort to increase the size of the potentional intersection point, meaning if the player missed by even a small amount it would still count → furthermore trying to solve this current issue

Absolutely, Ill look into the code and see what i can implement t into my system

1 Like

alright so i just used some parts to visualise the raycasts and something odd is occuring, when the player moves EVEN THOUGH their movment is perfectly linear the raycats appear to be going in odd directions (the origin position/direction are still based on the humanoidRootPart)

alright so i believe ive found the issue: when the raycasts are launched while the player is moving they are not forming in a perfectly straight line, instead its usually at an angle, my theory is by the time the information has been received (player position/lookvector) it would have already changed ands thats why they NEVER cast perfectly

I told you the exact same thing…
It gets changed becouse of an animation…
Also did you just…Man…Make proper titles next time
DID YOU ALL THIS TIME BEEN PLOTTING THAT ITS NETWORK PART THAT DOESNT WORK? :skull:
Obviously it would not becouse you are not calculating velocity and it has to be done on the server anyway.

Be nice he’s not talking to you that way

The problem is not an animation? if that WERE the case it could have been easily fixed by changing the origin position to the humanoidrootpart AS YOU suggested (which did not work) please be more clear with that

Also networkowernship has nothing to do with this, the effects are all particles and not blocks