Raycast Hitbox 4.01: For all your melee needs!

In case this was happening to anyone, I did some searching around the forums and found a solution. Create a new collision group that collides with all collision groups. Then, use RaycastParams.CollisionGroup to set its collision group to the new one you created.

1 Like

Sorry for the late replies, was on vacation for a bit.

@ErskinePierre There is a tutorial on my github wiki regarding on how to use Setpoints which hopefully can help better explain how to use them.

One way I would do is to manually put attachments in the player’s legs, but don’t have any attachments in the player’s arm and use setpoints for them instead.

local ArmsHitbox = RAYCAST_HITBOX:Initialize(chr.LeftArm, {chr})

local Points = {
     Vector3.new(0, 10, 0),
     Vector3.new(5, 10, 0),
     Vector3.new(5, 10, 10)
     --- These coordinates are the same as an attachment's Position property.
     --- Add as much as you want 
}

ArmsHitbox:SetPoints(chr.RightArm, Points)
ArmsHitbox:SetPoints(chr.LeftArm, Points)

local LegsHitbox = RAYCAST_HITBOX:Initialize(chr, {chr})
--- We don't need to use SetPoints for legs since we have attachments already made for 'em


Regarding collision groups, I frankly have not been using them as much as I should so I will need to research a bit before adding any additional support for them.

3 Likes

V.2.3 Stable

Some people have came to me still worried about the performance of MainHandler, which is understandable. I have performed extensive tests and removed some function calls that weren’t necessary during the loop which will increase the performance. HitStart and HitStop will now only perform the most minimal tasks, making it a very light function call.

Tests include 100 hitboxes running at a time, with 1000 attachments each, raycasting simultaneously. (100*1000 = 100,000 raycasts a frame!) Lost only around 5 - 6 frames, but very very rarely will a game require even a fraction of a fraction of that amount of raycasting so this is still very good performance.

If your game still suffers from performance lost due to this module, please let me know and I’ll diagnose it further.

8 Likes

Are you able to change the trail of your weapon when using this module? I want a blue weapon trail instead of a red one.

You can change the color if you expand the module’s content, and inside CastLogics > Debug > Debugger, there is a line that change’s its brickcolor which you can edit. Remember to only use it for debugging and not production.

2 Likes

V.3.0 Stable

Wow, another update, so soon??!

This update employs a bunch of rewrites in the MainHandler and Hitbox APIs, making its performance costs almost unnoticeable! There is also a new feature, which I like to call “Hit Distinction”! Take a look at some of these quick benchmarks I’ve made:

Raycast Module 2.3 Performance

NEW Raycast Module 3.0 Performance

New Feature: Hit Distinction

You can now tag your raycast points with specific group names! Using this group names, you can code in logic that can do different things depending on where the weapon hit! Instructions on how to do so can be found here:

No more disconnecting OnHit!

This is pretty big, especially for people who are new to scripting or just does not know.

Before, if you wanted to use OnHit in a new thread, you had to make sure to disconnect it like so:

function newThread()
     local newConn
     newConn = Hitbox.OnHit:Connect(function(hit, human)
          human:TakeDamage(10)
     end)
     wait(5)
     newConn:Disconnect()
end

Now you can be a bit more careless and use Hitbox.OnHit anywhere, in loops, new threads, whatever! The event uses a new minimalistic coroutine system that ensures only one connection line can be active at one time.

27 Likes

Hey could you upload a place with a working demo, since right now the demo place seems totally broken. I know it’s obsolete but many an other place demo?

Hi! I just updated it for 3.0. Looks like Roblox did something and the debug rays are more laggy than usual, essentially breaking a lot of things. Do let me know if you are getting the same issues.

1 Like

That’s amazing. How did you get it to be so much more performant, while still doing the same thing?

3 Likes

Yah so what I mean is the NPC’s are getting damaged after 10 seconds after a hit. The debug rays are appearing at wrong spots. They’re really really laggy. And the hit detection seems to be messed up.

https://gyazo.com/c2bf6f16766880eca7f80b1fbe454e90

https://gyazo.com/d902ce61e6281ac0b5e1bd2ceeb3c7ce

Basically just stopped using bindables in favour of more instant coroutine systems (bindables require information to be sent to an object for it to be eventually taken care of), removed a ton of fat from the loops that runs the raycasts, switched from pairs to ipairs for slightly more performance.

@0Shank have you tried the new demo place? Roblox definitely messed up the old one so I cleaned it up a bit so it should work better. Not sure what updates theyve made but seems like with every update they do, this module seems to be less and less viable on the server side.

The new version works perfectly thank you, I look forward to seeing more updates on this module.

And yeah it’s kinda weird that Roblox updates keep breaking the module.

1 Like

Hey, do you recommend doing this on the client or the server? (or is there already comment on this)

The hit detection even on the server seems consistent but I’m worried about players with bad latency. But here’s the problem when I do it on the client. I’m making a game where swords can defend other swords by hitting the sword (like the Mortdhau game). The problem I see on doing it on the client is this.

The reason for this is, that the client has to tell the server it’s position to the server, and the server has to tell all the other clients. This means the server and other clients will be more similar to the positions than the player who is actually moving.

Should I sacrifice the fighter’s consistency with the hit detection for the better of everyone else? Or do client hit detection and sacrifice everyone else’s hit detection consistency (what I mean by this is the fighter sees his sword defend the other sword but since the client’s who fought actually had his sword in a different position so he gets damaged).

Technically it works for both but if you need Accuracy and Precision then do it on the client and use Sanity checks.

However I am sure that I read somewhere about the OP mentioning recent versions running better Locally (Client context)

Here Reply 41 Reply 244

2 Likes

But only the fighter will get accuracy and precision that way (doing it on the client), since the other fighters will see the position of the fighter way off. For example, sword hit’s a person, the person see’s the sword position way off but it still hits him.

This is because Server and other clients will have the position of the fighter closer, then the fighter and the other clients.

Doing it on the server still means the other clients will see it off. But this time the other clients will feel the hit detection closer to the position of where they see the fighters sword.

That is latency and frankly there isn’t much we can do about it without tearing our hair out and writing custom Replication code or Prediction code. Networking is Roblox’s job so it’s their fault for not providing us with lower latency servers, but to be fair it has gotten better over time.

ClientSide detection has it’s trade offs but provides better experience and it’s fair for everyone anyways and all Frontpage games does this, ie Arsenal, MM2

4 Likes

Unfortunately, there is not much you can get around with that, especially how Roblox servers are not ideal for competitive gameplay with its low tick rate and inconsistent latency. Ruizukun beat me to it.

It’s not recommended, but one way I did before was running RaycastHitbox on both client and server. Clients will hear the hits first to ensure instant feedback making gameplay smoother (so put pretty effects!), and the server might lag a bit behind before dealing damage but its bearable cause the latency is masked behind the feedback. This way you don’t need to do much sanity checks, and the other player can see when the sword will hit while also satisfying the user experience of the one who swung first.


Overall though, no matter how much ping compensation or animation workaround we try to do to get a fair playing experience, in the end, it all has to go through Roblox and everything you do is subject to latency. All we can do is hope Roblox can give us better servers eventually. It also happens with FPS games, to a lesser degree, as peakers advantage can allow clients to see other players before the other players can see them. Favour the shooter as they say.

2 Likes

Hey, so I think there is a raycast ignore list bug in the demo game. I put the character in the ignore list but it still fires the function when things like the Handle of the tool, or the player’s arm hits.

Here is the code of the server. I don’t think anything is wrong with it, though I might be wrong, since is the default code in the demo place just with a print statement.

https://gyazo.com/8dc37c3591d28dfad9367cc4f2e107ff

local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")

local Character = Tool.Parent
local RaycastHitbox = require(game:GetService("ReplicatedStorage").RaycastHitboxV3)
local Hitbox = RaycastHitbox:Initialize(Tool, {Character, workspace.Terrain})

Hitbox:PartMode(true) --- Makes it so OnHit connection line will fire for every part hit similar to how normal Touched works
Hitbox:DebugMode(true)

function Create(ty)
	return function(data)
		local obj = Instance.new(ty)
		for k, v in pairs(data) do
			if type(k) == 'number' then
				v.Parent = obj
			else
				obj[k] = v
			end
		end
		return obj
	end
end

local BaseUrl = "rbxassetid://"

local Players = game:GetService("Players")
local Debris = game:GetService("Debris")
local RunService = game:GetService("RunService")

DamageValues = {
	BaseDamage = 5,
	SlashDamage = 10,
	LungeDamage = 30
}

--For R15 avatars
Animations = {
	R15Slash = 522635514,
	R15Lunge = 522638767
}

Damage = DamageValues.BaseDamage

Grips = {
	Up = CFrame.new(0, 0, -1.70000005, 0, 0, 1, 1, 0, 0, 0, 1, 0),
	Out = CFrame.new(0, 0, -1.70000005, 0, 1, 0, 1, -0, 0, 0, 0, -1)
}

Sounds = {
	Slash = Handle:WaitForChild("SwordSlash"),
	Lunge = Handle:WaitForChild("SwordLunge"),
	Unsheath = Handle:WaitForChild("Unsheath")
}

ToolEquipped = false

--For Omega Rainbow Katana thumbnail to display a lot of particles.
for i, v in pairs(Handle:GetChildren()) do
	if v:IsA("ParticleEmitter") then
		v.Rate = 20
	end
end

Tool.Grip = Grips.Up
Tool.Enabled = true

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

function TagHumanoid(humanoid, player)
	local Creator_Tag = Instance.new("ObjectValue")
	Creator_Tag.Name = "creator"
	Creator_Tag.Value = player
	Debris:AddItem(Creator_Tag, 2)
	Creator_Tag.Parent = humanoid
end

function UntagHumanoid(humanoid)
	for i, v in pairs(humanoid:GetChildren()) do
		if v:IsA("ObjectValue") and v.Name == "creator" then
			v:Destroy()
		end
	end
end

function Blow(Hit)
	print(Hit, "    ", Hit.Parent)
	
	if not Hit or not Hit.Parent or not CheckIfAlive() or not ToolEquipped then
		return
	end
	local RightArm = Character:FindFirstChild("Right Arm") or Character:FindFirstChild("RightHand")
	if not RightArm then
		return
	end
	local RightGrip = RightArm:FindFirstChild("RightGrip")
	if not RightGrip or (RightGrip.Part0 ~= Handle and RightGrip.Part1 ~= Handle) then
		return
	end
	local character = Hit.Parent
	if character == Character then
		return
	end
	local humanoid = character:FindFirstChildOfClass("Humanoid")
	if not humanoid or humanoid.Health == 0 then
		return
	end
	local player = Players:GetPlayerFromCharacter(character)
	if player and (player == Player or IsTeamMate(Player, player)) then
		return
	end
	UntagHumanoid(humanoid)
	TagHumanoid(humanoid, Player)
	humanoid:TakeDamage(Damage)	
end


function Attack()
	Damage = DamageValues.SlashDamage
	Sounds.Slash:Play()

	if Humanoid then
		if Humanoid.RigType == Enum.HumanoidRigType.R6 then
			local Anim = Instance.new("StringValue")
			Anim.Name = "toolanim"
			Anim.Value = "Slash"
			Anim.Parent = Tool
		elseif Humanoid.RigType == Enum.HumanoidRigType.R15 then
			local Anim = Tool:FindFirstChild("R15Slash")
			if Anim then
				local Track = Humanoid:LoadAnimation(Anim)
				Track:Play(0)
			end
		end
	end	
	
	wait(0.2)
	Hitbox:HitStop()
end

function Lunge()
	Damage = DamageValues.LungeDamage

	Sounds.Lunge:Play()
	
	if Humanoid then
		if Humanoid.RigType == Enum.HumanoidRigType.R6 then
			local Anim = Instance.new("StringValue")
			Anim.Name = "toolanim"
			Anim.Value = "Lunge"
			Anim.Parent = Tool
		elseif Humanoid.RigType == Enum.HumanoidRigType.R15 then
			local Anim = Tool:FindFirstChild("R15Lunge")
			if Anim then
				local Track = Humanoid:LoadAnimation(Anim)
				Track:Play(0)
			end
		end
	end	
	--[[
	if CheckIfAlive() then
		local Force = Instance.new("BodyVelocity")
		Force.velocity = Vector3.new(0, 10, 0) 
		Force.maxForce = Vector3.new(0, 4000, 0)
		Debris:AddItem(Force, 0.4)
		Force.Parent = Torso
	end
	]]
	
	wait(0.2)
	Tool.Grip = Grips.Out
	wait(0.6)
	Tool.Grip = Grips.Up
	
	Hitbox:HitStop()
	Damage = DamageValues.SlashDamage
end

Tool.Enabled = true
LastAttack = 0

function Activated()
	if not Tool.Enabled or not ToolEquipped or not CheckIfAlive() then
		return
	end
	Tool.Enabled = false
	Hitbox:HitStart()
	local Tick = RunService.Stepped:wait()
	if (Tick - LastAttack < 0.35) then
		Lunge()
	else
		Attack()
	end
	LastAttack = Tick
	--wait(0.5)
	Damage = DamageValues.BaseDamage
	local SlashAnim = (Tool:FindFirstChild("R15Slash") or Create("Animation"){
		Name = "R15Slash",
		AnimationId = BaseUrl .. Animations.R15Slash,
		Parent = Tool
	})
	
	local LungeAnim = (Tool:FindFirstChild("R15Lunge") or Create("Animation"){
		Name = "R15Lunge",
		AnimationId = BaseUrl .. Animations.R15Lunge,
		Parent = Tool
	})
	Tool.Enabled = true
end

function CheckIfAlive()
	return (((Player and Player.Parent and Character and Character.Parent and Humanoid and Humanoid.Parent and Humanoid.Health > 0 and Torso and Torso.Parent) and true) or false)
end

function Equipped()
	Character = Tool.Parent
	Player = Players:GetPlayerFromCharacter(Character)
	Humanoid = Character:FindFirstChildOfClass("Humanoid")
	Torso = Character:FindFirstChild("Torso") or Character:FindFirstChild("HumanoidRootPart")
	if not CheckIfAlive() then
		return
	end
	ToolEquipped = true
	Sounds.Unsheath:Play()
end

function Unequipped()
	Tool.Grip = Grips.Up
	ToolEquipped = false
end

Tool.Activated:Connect(Activated)
Tool.Equipped:Connect(Equipped)
Tool.Unequipped:Connect(Unequipped)

Hitbox.OnHit:Connect(Blow)

Also I have an other question about how this module works. I get that it fires a ray from the attachment, but until where does this ray fire? Since im planning to make a system like this for my game.

1 Like

Woops, looks like the swordscript had a problem, not the module. Try downloading the place again, should be fixed as normal.

If you are curious of what happened
local Tool = script.Parent
local Handle = Tool:WaitForChild("Handle")

local Character = Tool.Parent
local RaycastHitbox = require(game:GetService("ReplicatedStorage").RaycastHitboxV3)
local Hitbox = RaycastHitbox:Initialize(Tool, {Character, workspace.Terrain})

Since scripts run instantly, and since the tool starts in the player’s backpack, the Character variable is first set to Backpack, therefore the RaycastHitboxModule is using Character (which in reality is the Backpack) as its ignore. So its technically working, just not the right object lol. I moved everything to the equipped function so now everything should be in the right order.

Regarding how it works, on a fundamental level, it just compares the last frame with the current frame’s attachment positions. The rays will draw between these points. This ensures that no matter the distance difference between 2 frames, the rays should always compensate that distance. It also is fine with more laggy players with inconsistent frames, which the module will compensate with as it uses heartbeat to determine the frequency it will draw the rays @0Shank

Minor issue that doesn’t really matter, but on line 74 you use the term ascendant when I think you mean ancestor.