Projectile weapon hitreg problem

I have a ball projectile weapon that I’m using for my new game. The only problem is I’ve had quite of few complaints about how it’s hitreg is bad. I’m talking like sometimes when you hit somebody, it hits them and bounces them back but they take absolutely no damage. I’m reviewed my scripts for problems like what parts does it check to make sure its a player then still make sure to do damage even if the projectile hits something like a hat but to no avail.

Essentially, how should I go about fixing this problem with hit detection. My script is below:

function onTouched(hit)
	if not hit or not hit.Parent then return end 
	local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
	local tag = Ball:FindFirstChild("creator")
	if tag and humanoid then
		if not IsTeamMate(tag.Value, game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
			if IsSelf(tag.Value, game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
				tagHumanoid(humanoid)		
				humanoid:TakeDamage(damage)
				if humanoid.Health <= 0 then
					if not humanoid:FindFirstChild("killed") then
						local killed = Instance.new("IntValue")
						killed.Name = "killed"
						killed.Parent = humanoid
						-- Give points here
					end
				end
				if connection then connection:Disconnect() end
			end
		end
	end
end

FYI: I know that the function works in most cases; I just have problems sometimes.

1 Like

part.Touched is very “touchy”. It likes to decide when to fire, and when not to, and is unpreditable. To fix this, I suggest using a method of raycast hit detection. Create a ray in front of the bullet, and if that ray hits something, then activate your onTouched event. From what I understand, you’ll need to create a new ray every frame.

I hope this helps. If you need a code example, I can write one later this morning after my class.

1 Like

I understand. I’ll wait until I can get a code example due to how I’m not very proficient with raycasts. Thanks.

2 Likes

I haven’t tested it, but it would look something like this:

local run = game:GetService("RunService")


function BulletHit(character)

   -- your old code should work in here 
end

function shoot()
local bullet = Instance.new("Part")

-- customize bullet here

-- new ray every 1/60th of second    
run.HeartBeat:Connect(function ()
  --Create new ray

	local ray = Ray.new(bullet.CFrame, ((bullet.CFrame * CFrame.new(0,0,-3)).p - bullet.CFrame).unit * 2)
	local part, position = workspace:FindPartOnRay(ray, {bullet, localPlayer.Character}, false, true)
    
 if part.Parent:FindFirstChild("Humanoid") then
		BulletHit(part.Parent)
	end
end) 
end
1 Like

Working on testing this right now. I’ll get back to you soon.

Even after adding a print statement into the BulletHit function, it never fires. Just to let you know this is a ball projectile moving at a certain velocity with a curving path.

EDIT: I was able to get your system working. Only there is one problem. I get an error saying argument #2 needs to be a Vector3 value. After configuring it, I can’t get it to work. The error is on the Ray.new line.

local tag = Ball:FindFirstChild("creator")
print(((Ball.CFrame * CFrame.new(0, 0, -3)) - Ball.CFrame).unit * 2)
local ray = Ray.new(Ball.Position, Vector3.new((Ball.CFrame * CFrame.new(0, 0, -3)) - Ball.CFrame).unit * 2)
local part, position = workspace:FindPartOnRay(ray, {Ball, tag.Value}, false, true)

im out of class now, so ill create a place file for you

1 Like

I will be back later so I’ll get back to this thread as soon as possible. I’ll be a little late responding.

This script was tested and works as intended. Projectile Hit detection.rbxl (40.8 KB)

1 Like

I was not able to add this into my game. I’m going to reply with a full amount of code this time. Just to let you know it either didn’t even detect it hit something at all or it didn’t get past the “0” print statement.

All of this code is in a normal script (the server) inside of the projectile block. All of this code is run when the block is created.

local Ball = script.Parent
local upgrade1 = script.upgrade1.Value
local upgrade2 = script.upgrade2.Value
local upgrade3 = script.upgrade3.Value
local damage = 50
if upgrade2 == true then
	damage = 63
end

local r = game:GetService("RunService")
local debris = game:GetService("Debris")

local last_sound_time = r.Stepped:Wait()

function IsTeamMate(Player1, Player2)
	return (Player1 and Player2 and not Player1.Neutral and not Player2.Neutral and Player1.TeamColor == Player2.TeamColor)
end

function IsSelf(Found, Self)
	if Found ~= Self then
		return true
	else
		return false
	end
end

local function FindCharacterAncestor(subject)
	if subject and subject ~= game.Workspace then
		local humanoid = subject:FindFirstChild('Humanoid')
		if humanoid then
			return subject, humanoid
		else
			return FindCharacterAncestor(subject.Parent)
		end
	end
	return nil
end

local function OnExplosionHit(hitPart, hitDistance, blastCenter, tagValue)
	if hitPart and hitDistance then
		local character, humanoid = FindCharacterAncestor(hitPart.Parent)
		if humanoid then -- Humanoids are tagged and damaged
			if game.Players:GetPlayerFromCharacter(hitPart.Parent) then
				humanoid:TakeDamage(100)
				if not humanoid:FindFirstChild("killed") then
					local killed = Instance.new("IntValue")
					killed.Name = "killed"
					killed.Parent = humanoid
					if upgrade1 == true then
						game.ServerScriptService.Server.GivePoints:Fire(tagValue, 2)
					else
						game.ServerScriptService.Server.GivePoints:Fire(tagValue, 1)
					end
				end
			end
		else -- Loose parts and dead parts are blasted
			if hitPart.Name ~= 'Handle' then
				hitPart:BreakJoints()
				local blastForce = Instance.new('BodyForce', hitPart) --NOTE: We will multiply by mass so bigger parts get blasted more
				blastForce.force = (hitPart.Position - blastCenter).unit * 500 * hitPart:GetMass()
				debris:AddItem(blastForce, 0.1)
			end
		end
	end
end

local run = game:GetService("RunService")
function BulletHit(character)
	print("-1")
	if not character or not character.Parent then return end 
	local now = r.Stepped:Wait()
	if (now - last_sound_time > .1) then
		Ball.Boing:Play()
		last_sound_time = now
	else
		return
	end
	print("0")
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	local tag = Ball:FindFirstChild("creator")
	print("1")
	if tag and humanoid then
		print("a")
		if not IsTeamMate(tag.Value,game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
			print("b")
			if IsSelf(tag.Value,game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
				print("c")
				if upgrade3 == true then
					local explosion = Instance.new('Explosion')
					explosion.BlastPressure = 0 -- Completely safe explosion
					explosion.BlastRadius = 1
					explosion.ExplosionType = Enum.ExplosionType.NoCraters
					explosion.Position = character:FindFirstChild("HumanoidRootPart").Position
					explosion.Parent = game.Workspace
					explosion.Hit:connect(function(hitPart, hitDistance) OnExplosionHit(humanoid.Parent:FindFirstChild("HumanoidRootPart"), hitDistance, explosion.Position, tag.Value) end)
					script.Parent = explosion
					tag.Parent = script
					Ball:Destroy()
				elseif upgrade3 == false then
					print("yay")
					tagHumanoid(humanoid)		
					humanoid:TakeDamage(damage)
					if humanoid.Health <= 0 and upgrade3 == false then
						if not humanoid:FindFirstChild("killed") then
							local killed = Instance.new("IntValue")
							killed.Name = "killed"
							killed.Parent = humanoid
							if upgrade1 == true then
								game.ServerScriptService.Server.GivePoints:Fire(tag.Value, 2)
							else
								game.ServerScriptService.Server.GivePoints:Fire(tag.Value, 1)
							end
						end
					end
				end
				--if connection then connection:Disconnect() end
			end
		end
	end
end

run.Heartbeat:Connect(function ()
	local tag = Ball:FindFirstChild("creator")
	local ray = Ray.new(Ball.CFrame.p, ((Ball.CFrame * CFrame.new(0,0,-3)).p - Ball.CFrame.p).unit * 3)
	local part, position = workspace:FindPartOnRayWithIgnoreList(ray, {tag.Value, Ball}, false, true)
	if part and part.Parent:FindFirstChild("Humanoid") then
		print("Hit: " .. part.Parent.Name)
		BulletHit(part.Parent)
	end
end)

function tagHumanoid(humanoid)
	local tag = Ball:FindFirstChild("creator")
	if tag then
		while(humanoid:FindFirstChild("creator")) do
			humanoid:FindFirstChild("creator").Parent:Destroy()
		end
		local new_tag = tag:Clone()
		new_tag.Parent = humanoid
		debris:AddItem(new_tag, 1)
	end
end

EDIT: Looking at it even more, I’ve noticed it only works when the player is hit in the head. I can’t seem to figure out why it doesn’t work with other body parts/hat hitboxes.

I’ve edited the code enough until it is at a state of working order. There is only one problem left to address. Sometimes when it hits a target the function that does damage can fire more than once killing them in one hit when they are not supposed to. I’m also getting this weird bug where the humanoid deletes itself when the NPC dies. Here is my code for the damage function:

function BulletHit(character)
	if not character or not character.Parent then return end 
	local now = r.Stepped:Wait()
	if (now - last_sound_time > .1) then
		Ball.Boing:Play()
		last_sound_time = now
	end
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	local tag = Ball:FindFirstChild("creator")
	if tag and humanoid then
		if not IsTeamMate(tag.Value,game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
			if IsSelf(tag.Value,game.Players:GetPlayerFromCharacter(humanoid.Parent)) then
				if upgrade3 == true then
					local explosion = Instance.new("Explosion")
					explosion.BlastPressure = 0
					explosion.BlastRadius = 1
					explosion.ExplosionType = Enum.ExplosionType.NoCraters
					explosion.Position = character:FindFirstChild("HumanoidRootPart").Position
					explosion.Parent = game.Workspace
					explosion.Hit:connect(function(hitPart, hitDistance) OnExplosionHit(humanoid.Parent:FindFirstChild("HumanoidRootPart"), hitDistance, explosion.Position, tag.Value) end)
					script.Parent = explosion
					tag.Parent = script
					Ball:Destroy()
				elseif upgrade3 == false then
					print("yay: " .. character.Name)
					tagHumanoid(humanoid)		
					humanoid:TakeDamage(damage)
					if humanoid.Health <= 0 and upgrade3 == false then
						if not humanoid:FindFirstChild("killed") then
							local killed = Instance.new("IntValue")
							killed.Name = "killed"
							killed.Parent = humanoid
							if upgrade1 == true then
								game.ServerScriptService.Server.GivePoints:Fire(tag.Value, 2)
							else
								print("point: " .. humanoid.Name)
								game.ServerScriptService.Server.GivePoints:Fire(tag.Value, 1)
							end
						end
					end
				end
			end
		end
	end
end

Have you considered setting bullet’s NetworkOwnership to the client, this way you can smoothly move the part and detect it on clients end. Although it can be exploited.

https://developer.roblox.com/en-us/api-reference/function/BasePart/SetNetworkOwner

The tests of the raycasts are on the server. But It just hits the player twice. Seeing as it creates a new ball when you throw the projectile, would it be smart to just say you can’t do anymore damage with that specific projectile after its already hit the player?

I personally suggest using remotes to create a local bullet on each client so that it’s smooth for everyone and to reduce server lag.

And deleting the bullet / using a Boolean will prevent your bug from happening

function IsSelf(Found, Self)

would be best as

function IsSelf(Found, Self)
    return Found ~= Self
end