Raycast Ability Help!

Hello Guys!
Hope your day is going well, but sadly mine has not because of raycasting. :sob:
I want to make a raycast ability which gets fired from a keybind (I used F). Here is the script from the Client:

--//Services and Player
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local TS = game:GetService("TweenService")
local DS = game:GetService("Debris")

local Player = PS.LocalPlayer
local Camera = workspace.CurrentCamera
--//Remotes
local RayExecutionServiceRS = RS:WaitForChild("ServicesReplications").RayExecutionService
local ExecuteRayRemote = RayExecutionServiceRS.ExecuteRay

--//Gui
local RayExecuterGui = script.Parent
local ExecuteRayButton = RayExecuterGui.ExecuteButton
local EnabledLabel = RayExecuterGui.EnabledLabel
local CooldownFrame = RayExecuterGui.CooldownFrame

--//Modules
local CoreContextModule = require(RayExecuterGui.Parent.Parent.Parent.ClientGui.ContextUtilities.CoreContext)

--//Variables and Debounce
local DB = false
local ModeSelect = false


--//Functions
local function ToggleModeSelect()
	if ModeSelect == false and DB == false then 
		ModeSelect = true
		ExecuteRayButton.UIStroke.Color = Color3.fromRGB(0, 121, 30)
		EnabledLabel.Text = "Enabled"
		EnabledLabel.TextColor3 = Color3.fromRGB(0, 255, 8)
	elseif ModeSelect == true then
		ModeSelect = false
		ExecuteRayButton.UIStroke.Color = Color3.fromRGB(0,0,0)
		EnabledLabel.Text = "Disabled"
		EnabledLabel.TextColor3 = Color3.fromRGB(255, 0, 0)
	end
end

local function CooldownThread(Cooldown)
	task.spawn(function()
		task.wait(Cooldown)
		DB = false
	end)
	local Frame = CooldownFrame:Clone()
	Frame.Visible = true
	Frame.Parent = ExecuteRayButton
	local Tween = TS:Create(Frame, TweenInfo.new(Cooldown, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut), {Size = UDim2.fromScale(0,1)})
	Tween:Play()
	DS:addItem(Frame, Cooldown)
end

--MAIN EXECUTION
local function ExecuteClient()
	local MousePosition = UIS:GetMouseLocation()
	local MouseRay = Camera:ViewportPointToRay(MousePosition.X, MousePosition.Y)
	local Execution, Cooldown, Return = ExecuteRayRemote:InvokeServer(MouseRay--[[MouseRay]])
	
	if Execution then
		warn("RayCast Executed Successfully. "..Return.." [RayExecutionService]") --toggle prints here.
		task.spawn(CooldownThread, Cooldown)
	elseif Execution == false then
		warn("RayCast Failed, "..Return.." [RayExecutionService]")
		task.wait(0.1)
		DB = false
	end
end

--//Connections

--Toggle Button/Input Connection
UIS.InputBegan:Connect(function(Input, GP)
	if GP == true then return end
	if Input.KeyCode == Enum.KeyCode.F then
		ToggleModeSelect()
	end
end)

ExecuteRayButton.MouseButton1Click:Connect(function()
	ToggleModeSelect()
end)

--Firing Ray Connection
UIS.InputBegan:Connect(function(Input, GP)
	if GP == true then return end
	if Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch then
		if ModeSelect == true then
			if DB == false then
				DB = true
				ExecuteClient()
				ToggleModeSelect()
			end
		end
	end
end)

Here is the serverscript which recieves the request:

--//Services and Player
local RS = game:GetService("ReplicatedStorage")
local PS = game:GetService("Players")

--//Remotes
local RayExecutionServiceRS = RS:WaitForChild("ServicesReplications").RayExecutionService
local ExecuteRayRemote = RayExecutionServiceRS.ExecuteRay

--//Modules
local ConfigurationModule = require(script.Parent.Configurations)
local Settings = ConfigurationModule.Settings

--//Variables and Debounce
local Debounces = {}

--//Functions and Connections
local function CooldownThread(Player, Cooldown)
	task.wait(Cooldown)
	Debounces[Player] = nil
end

local function Execute(Player, MouseRay)
	Debounces[Player] = true
	
	local Character = Player.Character or Player.CharacterAdded:Wait()
	local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
	
	local ExecutingRay = Player:WaitForChild("ExecutingRay")
	local Eliminated = Player:WaitForChild("Eliminated")
	
	if ExecutingRay.Value then
		return false, nil, "Player is still executing a ray!"
	end
	if Eliminated.Value then
		return false, nil, "Player is eliminated!"
	end
	
	--Identify Raycast
	local RCParams = RaycastParams.new()
	RCParams.FilterType = Enum.RaycastFilterType.Exclude
	RCParams.FilterDescendantsInstances = {Character}
	
	local RaycastResult = workspace:Raycast(MouseRay.Origin, MouseRay.Direction * 15000, RCParams)
	
	if RaycastResult == nil then
		task.spawn(CooldownThread, Player, Settings.Cooldown)
		return true, Settings.Cooldown, "Nothing Found - nil."
	end
	
	local TargettedCharacter = RaycastResult.Instance:FindFirstAncestorWhichIsA("Model")
	local TargettedHumanoid = TargettedCharacter:FindFirstChildWhichIsA("Humanoid")
	local TargettedPlayer = PS:GetPlayerFromCharacter(TargettedCharacter)
	
	print(tostring(RaycastResult.Instance),TargettedCharacter,TargettedHumanoid,TargettedPlayer)
	
	if TargettedHumanoid == nil and TargettedPlayer == nil then
		task.spawn(CooldownThread, Player, Settings.Cooldown)
		return true, Settings.Cooldown, "Nothing Found related to Character/Player!"
	end
	
	ExecutingRay.Value = true
	--[[for i, Descendant in pairs(Character:GetDescendants()) do
		if Descendant:IsA("BasePart") then
			Descendant.Transparency = 1
			Descendant.CanCollide = false
		end
	end]]
	
	task.spawn(CooldownThread, Player, Settings.Cooldown)
	ExecutingRay.Value = false
	return true, Settings.Cooldown, "Player Found."
end

ExecuteRayRemote.OnServerInvoke = function(Player,Target)
	--ERROR HANDLING: [Success, Cooldown, Error]
	if Player == nil then
		return false, nil, "Incorrect Paramaters!"
	end
	if Target == nil then
		return false, nil, "Nothing Found!"
	end
	if Debounces[Player] then
		return false, nil, "On Cooldown!"
	end
	
	local Execution, Cooldown, Return = Execute(Player, Target)
	return Execution, Cooldown, Return
end

Now ofc the script works, but not in the intended way. When it directly fires WHEN MY MOUSE IS HITTING A BODY PART, it registers it as something else.
I want some ideas and scripting help on making this system better. I also want it to ignore things like accessories and just go through it directly.
Thanks for stopping by!

The main problem seems to be in the server-side script, specifically in the Execute function.

local function Execute(Player, MouseRay)
    Debounces[Player] = true
    
    local Character = Player.Character or Player.CharacterAdded:Wait()
    local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
    
    local ExecutingRay = Player:WaitForChild("ExecutingRay")
    local Eliminated = Player:WaitForChild("Eliminated")
    
    if ExecutingRay.Value then
        return false, nil, "Player is still executing a ray!"
    end
    if Eliminated.Value then
        return false, nil, "Player is eliminated!"
    end
    
    -- Improved Raycast parameters
    local RCParams = RaycastParams.new()
    RCParams.FilterType = Enum.RaycastFilterType.Exclude
    RCParams.FilterDescendantsInstances = {Character}
    RCParams.IgnoreWater = true
    
    local RaycastResult = workspace:Raycast(MouseRay.Origin, MouseRay.Direction * 15000, RCParams)
    
    if RaycastResult == nil then
        task.spawn(CooldownThread, Player, Settings.Cooldown)
        return true, Settings.Cooldown, "Nothing Found - nil."
    end
    
    -- Improved target detection
    local TargetCharacter = RaycastResult.Instance
    while TargetCharacter and not TargetCharacter:FindFirstChildOfClass("Humanoid") do
        TargetCharacter = TargetCharacter.Parent
    end
    
    if not TargetCharacter then
        task.spawn(CooldownThread, Player, Settings.Cooldown)
        return true, Settings.Cooldown, "No valid target found."
    end
    
    local TargetHumanoid = TargetCharacter:FindFirstChildOfClass("Humanoid")
    local TargetPlayer = PS:GetPlayerFromCharacter(TargetCharacter)
    
    if TargetHumanoid then
        ExecutingRay.Value = true
        -- Add your effect or damage logic here
        
        task.spawn(CooldownThread, Player, Settings.Cooldown)
        ExecutingRay.Value = false
        return true, Settings.Cooldown, "Player Found: " .. (TargetPlayer and TargetPlayer.Name or "NPC")
    else
        task.spawn(CooldownThread, Player, Settings.Cooldown)
        return true, Settings.Cooldown, "No valid humanoid found."
    end
end
1 Like

this is the mistake. Usually when invoking a server or a client, you want to have NO delay unless necessary. That cooldown line is so called necessary but you can use a different method to achieve it .

  1. Instead of storing if the ability can be use with a boolean value, you should instead store the time the client last fired by using the tick() function . tick() would give you the amount of seconds from idk . but yea tick() allows you check how many seconds pass by taking (tick()-LastFiredTick) .

to implement this , make a new table called LastFiredTicks. to check if the player can fire, write this line .

local LastFiredTick = LastFiredTicks[plr.Name] or 0
if (tick()-LastFiredTick) < cooldown then
     --cannot fire
     return
end

after that , store the new tick
LastFiredTick[plr.Name] = tick()

1 Like

No but the problem is that its with the raycast
The results are not correct at all

Ok I will test and see!
Thanks for letting me know. I think the problem is how Iā€™m firing the ray

1 Like

oh my bad . I did not see the raycast part lol . However, I found some problems

  1. you should use cam:ScreenPointToRay(x,y,d) instead . X and Y are the position of the cursor and d is dept(recommend put 1) . that function will return a table with origin and direction .

  2. on the server , always remember to .Unit any directional vector. This is because clients can exploit that vulnerability by making the direction vector 100 units long and the server will take 100*range

1 Like

So what exactly is depth and whatā€™s the difference between :ViewportPointToRay()?
On your second point, I will do that.
Thanks for letting me know! I will see how it goes

ViewportPointToRay origin cords is from the VERY TOP OF THE SCREEN above the bar(the roblox icon and chat and stuff thingy) while ScreenPointToRay origin cord is under the top bar. Since mouse position origin is from below the top bar, you should use ScreenPointToRay

1 Like

So I looked at the depth but am still not getting what it does. Can you explain more?
Also do I need to change anything in any script if I start using ScreenPointToRay?

No you do not need to change anything other then the depth . Also may I ask , when you say target is inaccurate, is it completely different or slgihtly

1 Like

So I did change the function, and this is what results from it:

External Media

Here is the output:
image
Clearly I was aiming at body parts and its going straight through. I also want it to ignore acccessories and only count body parts

EDIT: I did add this btw:
image

did you try going in first person? did you try firing around the target? try printing the origin and direction value

1 Like

Ok let me try that.
Thanks for letting me know

That doesnā€™t really work, and that it still registers other parts. For some reason it keeps hitting other objects like baseplate, accessories, etc.

local function RayCast(Origin,Direction,Params)
local Raycast = workspace:Raycast(Origin,Direction,Params)
if Raycast then
if Raycast.Instance.Parent:IsA("Accessory") then
Params:AddToFilter(Raycast.Instance.Parent)
return RayCast(Origin,Direction,Params)
end
return Raycast
end
end

I made this custom raycast function to ignore accessories. Pardon the indentations . I am on mobile lol

Can you try targeting a blank dummy from like 2 studs away ? If it is still not targeting correctly, there is something wrong with the math

1 Like

Its a bit more accurate but sometimes it just registers another part. Sometimes, I click upper right arm and then it registers it as lower arm :skull: lol
I mean I think it could be the math too
Do you think that the game must be in first person, because the ā€œmoreā€ accurate results came from F.P?

EDIT: I did a bit more testing and for some apparent reason, some body parts are getting ignored and when I do hit something like a Hand, it registers it as baseplate or a leg part for some other reason. I think there is some type of offset causing this because I hit the head directly and it registers as a humanoidrootpart or like torso.
I am clearly hitting an arm part (lower or upper idk) and it registers it as a hand:
image
image

did you change to ScreenPointToRay?

1 Like

Yes, it didnā€™t work.
For some reason it works in F.P lock when the ray is from Cam:ViewPortPointToRay()

How can I make it accurate on third person

task.spawn() just creates the new autothread. It wonā€™t delay anything in the mainline and will return immediately. I donā€™t see a problem with thisā€¦
I will look into this though!