Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Last active October 24, 2019 18:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save howmanysmall/d3681cdf315245927b6b4d3da852d077 to your computer and use it in GitHub Desktop.
Save howmanysmall/d3681cdf315245927b6b4d3da852d077 to your computer and use it in GitHub Desktop.
--[[
____________________________________________________________________________________________________________________________________________________________________________
Created by Swordphin123 - 2019. If you have any questions, feel free to message me on DevForum. Credits not neccessary but is appreciated.
[ How To Use - Quick Start Guide ]
1. Insert Attachments to places where you want your "hitbox" to be. For swords, I like to have attachments 1 stud apart and strung along the blade.
2. Name those Attachments "DmgPoint" (so the script knows). You can configure what name the script will look for in the variables below.
3. Open up a script. As an example, maybe we have a sword welded to the character or as a tool. Require this, and initialize:
* Example Code
local Damage = 10
RaycastHitbox:Initialize(Character, {Character})
RaycastHitbox:HitStart(Character, Damage)
wait(2)
RaycastHitbox:HitStop(Character)
4. Profit. Refer to the API below for more information.
____________________________________________________________________________________________________________________________________________________________________________
[ RaycastHitBox API ]
* local RaycastHitbox = require(RaycastHitbox) ---Duh
--- To use, insert this at the top of your scripts or wherever.
* RaycastHitbox:Initialize(Instance model, table ignoreList)
Description
--- Preps the model and recursively finds attachments in it so it knows where to shoot rays out of later.
Arguments
--- Instance model: Model instance (Like your character, a sword model, etc). May support Parts later.
--- table ignoreList: Raycast takes in ignorelists. Heavily recommended to add in a character so it doesn't hurt itself in its confusion.
* RaycastHitbox:HitStart(Instance model, Number damage)
Description
--- Starts drawing the rays. Will only damage the target once. Call HitStop to reset the target pool so you can damage the same targets again.
Arguments
--- Instance model: Same model that you initialized with earlier, will return an error if said model was not init first.
* RaycastHitbox:HitStop(Instance model)
Description
--- Stops drawing the rays and resets the target pool. Will do nothing if no rays are being drawn from the initialized model.
Arguments
--- Instance model: Same model that you initialized with earlier, will return an error if said model was not init first.
* RaycastHitbox:Deinitialize(Instance model)
Description
--- Removes references to the attachments and garbage collects values from the instance. Great if you are deleting the hitbox soon.
--- The script will attempt to run this function automatically if the model ancestry was changed.
Arguments
--- Instance model: Same model that you initialized with earlier. Will do nothing if model was not initialized.
____________________________________________________________________________________________________________________________________________________________________________
[ Troubleshooting ]
Q1 - Rays are not coming out / DebugRays not showing any rays
--- Make sure you initialized a "Model" instance (not a part, not a mesh, etc) and that it contains parts. These parts should contain attachments
named after the AttachmentName variable below.
Q2 - Sometimes my rays "lag" or they do not come out soon enough
--- This is a known issue and I've been actively trying to investigate the cause of it. Though it doesn't really happen often enough to be a concern.
Q3 - How do I set it to damage specific NPCs/Teams?
--- For now, you can look below and edit the Hitboxing:RaysStart() function to suit your needs. If requested enough, I can support it natively later.
Or you can use the ignoreLists during initialization to separate the targets.
____________________________________________________________________________________________________________________________________________________________________________
I do not recommend editing the mayhem below unless you know what you're doing.
____________________________________________________________________________________________________________________________________________________________________________
--]]
local AttachmentName = "DmgPoint" --- The attachment names the script will look for. This is where the rays will shoot out of.
local DontCheckForTransparency = false --- Normally the script won't fire rays if the parent part the attachment is in is transparent (== 1). You can set this to true to override it.
local DebugRays = false --- Highly recommended to test your rays so turn this to true to see where your rays are shooting from.
-------------------------------------------------
local RaycastHitModule = {
Version = "1.0 Beta"
}
-------------------------------------------------
local function assert(condition, m) -- how about just don't make a new function
if not condition == true then -- WHY
warn(m)
end
end
local function CheckForTransparency(Point)
-- Could just do return DontCheckForTransparency or Point.Attachment.Parent.Transparency < 1
-- or better yet don't use a function call
if DontCheckForTransparency or Point.Attachment.Parent.Transparency < 1 then
return true
end
return false
end
local function FindDamageAttachments(model)
local Points = {}
local Model = model:GetChildren()
for i = 1, #Model do -- ipairs faster
local Part = Model[i]
if not Part:IsA("BasePart") then
local MorePoints = FindDamageAttachments(Part)
for v = 1, #MorePoints do -- ipairs faster.
table.insert(Points, MorePoints[v]) -- Use a length variable and insert it with that.
end
else
if Part:FindFirstChild(AttachmentName) then
local DmgPoints = Part:GetChildren()
for v = 1, #DmgPoints do -- Same from above
local DmgPoint = DmgPoints[v]
if DmgPoint.Name == AttachmentName then
table.insert(Points, DmgPoint) -- Same from above as well
end
end
end
end
end
return Points
end
-------------------------------------------------
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local Instances = {}
local Hitboxing = {}
Hitboxing.__index = Hitboxing
function Hitboxing.__tostring(self)
return self.Object.Name
end
function Hitboxing:RaysStart(Damage)
self.Connection = RunService.Heartbeat:connect(function()
local Target; -- unused
for _,Point in next, self.Points do -- use pairs
if CheckForTransparency(Point) then
if self.Connection then
if not Point.LastPosition then
Point.LastPosition = Point.Attachment.WorldPosition
end
local ray = Ray.new(Point.LastPosition, Point.Attachment.WorldPosition - Point.LastPosition) -- don't make a new variable for this
local obj, pos = workspace:FindPartOnRayWithIgnoreList(ray, self.Ignore) -- pos isn't even used
if DebugRays then
local beam = Instance.new("Part")
beam.BrickColor = BrickColor.new("Bright red") -- Why is it not a constant
beam.FormFactor = "Custom" -- Redundant.
beam.Material = "Neon" -- Should use Enums, not strings.
beam.Transparency = 0 -- Redundant.
beam.Anchored = true
beam.Locked = true
beam.CanCollide = false
local Dist = (Point.Attachment.WorldPosition - Point.LastPosition).magnitude
beam.Size = Vector3.new(0.1, 0.1, Dist)
beam.CFrame = CFrame.new(Point.Attachment.WorldPosition, Point.LastPosition) * CFrame.new(0, 0, -Dist / 2)
beam.Parent = workspace -- Use service damn it
Debris:AddItem(beam, 1)
end
if obj and (not self.HitTargets[obj.Parent]) then
local TargetHumanoid = obj.Parent:FindFirstChildOfClass("Humanoid")
if TargetHumanoid then
self.HitTargets[obj.Parent] = true
TargetHumanoid:TakeDamage(Damage)
end
end
Point.LastPosition = Point.Attachment.WorldPosition --- Save the last position in frame
else
break
end
end
end
end)
end
function Hitboxing:RaysStop()
if self.Connection then
for _,Point in next, self.Points do
Point.LastPosition = nil
end
self.HitTargets = {}
self.Connection:Disconnect()
self.Connection = nil
end
end
function Hitboxing:Deactivate()
self.Object = nil
self.Connection = nil
self.Points = nil
self.HitTargets = nil
self.Ignore = nil
end
function Hitboxing:FindDamageAttachments(model)
local Points = FindDamageAttachments(model)
if #Points > 0 then
-- ipairs damn it
for i = 1, #Points do
local DmgPoint = Points[i]
self.Points[DmgPoint] = {} -- Make a local table and set the properties, then set the table like this
self.Points[DmgPoint].Attachment = DmgPoint
self.Points[DmgPoint].LastPosition = nil
end
else
warn("No attachments were found with the name", AttachmentName.."!", "Did you setup correctly?")
end
end
-------------------------------------------------
function RaycastHitModule:Initialize(instanceObject, ignoreList)
assert(typeof(instanceObject) == "Instance", "RaycastHitModule requires an Instance")
local Hitbox = Instances[instanceObject]
if Hitbox then
print("Hitbox for this Instance exists")
else
if instanceObject:IsA("Model") then
local NewHitbox = setmetatable({
Object = instanceObject,
Connection = nil,
Points = {},
HitTargets = {},
Ignore = ignoreList and ignoreList or {}
}, Hitboxing)
Instances[instanceObject] = NewHitbox
Instances[instanceObject]:FindDamageAttachments(instanceObject)
instanceObject.AncestryChanged:Connect(function() -- I don't trust roblox to gc
if not workspace:IsAncestorOf(instanceObject) then
Instances[instanceObject]:Deactivate()
Instances[instanceObject] = nil
print("Hitbox Object was deleted")
end
end)
print("Created Hitbox for Object")
else
print("RaycastHitModule requires a Model instance")
end
end
end
function RaycastHitModule:Deinitialize(instanceObject)
assert(typeof(instanceObject) == "Instance", "RaycastHitModule requires an Instance")
local Hitbox = Instances[instanceObject]
if Hitbox then
Hitbox:Deactivate()
Hitbox = nil
else
print("Hitbox does not exist")
end
end
function RaycastHitModule:HitStart(instanceObject, Damage)
local Hitbox = Instances[instanceObject]
assert(Hitbox ~= nil, "Hitbox does not exist")
if Hitbox then
Hitbox:RaysStart(Damage)
end
end
function RaycastHitModule:HitStop(instanceObject)
local Hitbox = Instances[instanceObject]
assert(Hitbox ~= nil, "Hitbox does not exist")
if Hitbox then
Hitbox:RaysStop()
end
end
return RaycastHitModule
--[[
____________________________________________________________________________________________________________________________________________________________________________
Created by Swordphin123 - 2019. If you have any questions, feel free to message me on DevForum. Credits not neccessary but is appreciated.
[ How To Use - Quick Start Guide ]
1. Insert Attachments to places where you want your "hitbox" to be. For swords, I like to have attachments 1 stud apart and strung along the blade.
2. Name those Attachments "DmgPoint" (so the script knows). You can configure what name the script will look for in the variables below.
3. Open up a script. As an example, maybe we have a sword welded to the character or as a tool. Require this, and initialize:
* Example Code
local Damage = 10
RaycastHitbox:Initialize(Character, {Character})
RaycastHitbox:HitStart(Character, Damage)
wait(2)
RaycastHitbox:HitStop(Character)
4. Profit. Refer to the API below for more information.
____________________________________________________________________________________________________________________________________________________________________________
[ RaycastHitBox API ]
* local RaycastHitbox = require(RaycastHitbox) ---Duh
--- To use, insert this at the top of your scripts or wherever.
* RaycastHitbox:Initialize(Instance model, table ignoreList)
Description
--- Preps the model and recursively finds attachments in it so it knows where to shoot rays out of later.
Arguments
--- Instance model: Model instance (Like your character, a sword model, etc). May support Parts later.
--- table ignoreList: Raycast takes in ignorelists. Heavily recommended to add in a character so it doesn't hurt itself in its confusion.
* RaycastHitbox:HitStart(Instance model, Number damage)
Description
--- Starts drawing the rays. Will only damage the target once. Call HitStop to reset the target pool so you can damage the same targets again.
Arguments
--- Instance model: Same model that you initialized with earlier, will return an error if said model was not init first.
* RaycastHitbox:HitStop(Instance model)
Description
--- Stops drawing the rays and resets the target pool. Will do nothing if no rays are being drawn from the initialized model.
Arguments
--- Instance model: Same model that you initialized with earlier, will return an error if said model was not init first.
* RaycastHitbox:Deinitialize(Instance model)
Description
--- Removes references to the attachments and garbage collects values from the instance. Great if you are deleting the hitbox soon.
--- The script will attempt to run this function automatically if the model ancestry was changed.
Arguments
--- Instance model: Same model that you initialized with earlier. Will do nothing if model was not initialized.
____________________________________________________________________________________________________________________________________________________________________________
[ Troubleshooting ]
Q1 - Rays are not coming out / DebugRays not showing any rays
--- Make sure you initialized a "Model" instance (not a part, not a mesh, etc) and that it contains parts. These parts should contain attachments
named after the AttachmentName variable below.
Q2 - Sometimes my rays "lag" or they do not come out soon enough
--- This is a known issue and I've been actively trying to investigate the cause of it. Though it doesn't really happen often enough to be a concern.
Q3 - How do I set it to damage specific NPCs/Teams?
--- For now, you can look below and edit the Hitboxing:RaysStart() function to suit your needs. If requested enough, I can support it natively later.
Or you can use the ignoreLists during initialization to separate the targets.
____________________________________________________________________________________________________________________________________________________________________________
I do not recommend editing the mayhem below unless you know what you're doing.
____________________________________________________________________________________________________________________________________________________________________________
--]]
local Workspace = game:GetService("Workspace")
local Debris = game:GetService("Debris")
local RunService = game:GetService("RunService")
local AttachmentName = "DmgPoint" --- The attachment names the script will look for. This is where the rays will shoot out of.
local DontCheckForTransparency = false --- Normally the script won't fire rays if the parent part the attachment is in is transparent (== 1). You can set this to true to override it.
local DebugRays = false --- Highly recommended to test your rays so turn this to true to see where your rays are shooting from.
-------------------------------------------------
local RaycastHitModule = {
Version = "1.0 Beta"
}
local pairs = pairs
local ipairs = ipairs
-- Can probably be replaced with GetDescendants.
local function FindDamageAttachments(model)
local Points = {}
local Length = 0
for _, Part in ipairs(model:GetChildren()) do
if not Part:IsA("BasePart") then
for _, Point in ipairs(FindDamageAttachments(Part)) do
Length = Length + 1
Points[Length] = Point
end
else
if Part:FindFirstChild(AttachmentName) then
for _, DmgPoint in ipairs(Part:GetChildren()) do
if DmgPoint.Name == AttachmentName then
Length = Length + 1
Points[Length] = DmgPoint
end
end
end
end
end
return Points
end
-------------------------------------------------
local Instances = {}
local Hitboxing = {}
Hitboxing.__index = Hitboxing
function Hitboxing:__tostring()
return self.Object.Name
end
local BRIGHT_RED = BrickColor.new("Bright red")
function Hitboxing:RaysStart(Damage)
-- probably not smart
self.Connection = RunService.Heartbeat:Connect(function()
for _, Point in pairs(self.Points) do -- use pairs
local Attachment = Point.Attachment
local WorldPosition = Attachment.WorldPosition
if DontCheckForTransparency or Attachment.Parent.Transparency < 1 then
if self.Connection then
if not Point.LastPosition then
Point.LastPosition = WorldPosition
end
-- local ray = Ray.new(Point.LastPosition, Point.Attachment.WorldPosition - Point.LastPosition) -- don't make a new variable for this
local obj = Workspace:FindPartOnRayWithIgnoreList(Ray.new(Point.LastPosition, WorldPosition - Point.LastPosition), self.Ignore) -- pos isn't even used
if DebugRays then
local beam = Instance.new("Part")
beam.BrickColor = BRIGHT_RED -- Why is it not a constant
-- beam.FormFactor = "Custom" -- Redundant.
beam.Material = Enum.Material.Neon -- Should use Enums, not strings.
-- beam.Transparency = 0 -- Redundant.
beam.Anchored = true
beam.Locked = true
beam.CanCollide = false
local Dist = (WorldPosition - Point.LastPosition).Magnitude
beam.Size = Vector3.new(0.1, 0.1, Dist)
beam.CFrame = CFrame.new(WorldPosition, Point.LastPosition) * CFrame.new(0, 0, -Dist / 2)
beam.Parent = Workspace -- Use service damn it
Debris:AddItem(beam, 1)
end
if obj and (not self.HitTargets[obj.Parent]) then
local TargetHumanoid = obj.Parent:FindFirstChildOfClass("Humanoid")
if TargetHumanoid then
self.HitTargets[obj.Parent] = true
TargetHumanoid:TakeDamage(Damage)
end
end
Point.LastPosition = WorldPosition --- Save the last position in frame
else
break
end
end
end
end)
end
function Hitboxing:RaysStop()
if self.Connection then
for _, Point in pairs(self.Points) do
Point.LastPosition = nil
end
self.HitTargets = {}
self.Connection:Disconnect()
self.Connection = nil
end
end
function Hitboxing:Deactivate()
self.Object = nil
self.Connection = nil
self.Points = nil
self.HitTargets = nil
self.Ignore = nil
end
function Hitboxing:FindDamageAttachments(model)
-- local Points = FindDamageAttachments(model)
local Points = {}
local Length = 0
for _, Part in ipairs(model:GetChildren()) do
if not Part:IsA("BasePart") then
for _, Point in ipairs(FindDamageAttachments(Part)) do
Length = Length + 1
Points[Length] = Point
end
else
if Part:FindFirstChild(AttachmentName) then
for _, DmgPoint in ipairs(Part:GetChildren()) do
if DmgPoint.Name == AttachmentName then
Length = Length + 1
Points[Length] = DmgPoint
end
end
end
end
end
if Length > 0 then
-- ipairs damn it
for _, DmgPoint in ipairs(Points) do
self.Points[DmgPoint] = {
Attachment = DmgPoint;
LastPosition = nil;
}
end
else
warn("No attachments were found with the name", AttachmentName.."!", "Did you setup correctly?")
end
end
-------------------------------------------------
function RaycastHitModule:Initialize(instanceObject, ignoreList)
if typeof(instanceObject) ~= "Instance" then warn("RaycastHitModule requires an Instance") end
local Hitbox = Instances[instanceObject]
if Hitbox then
print("Hitbox for this Instance exists")
else
if instanceObject:IsA("Model") then
local NewHitbox = setmetatable({
Object = instanceObject,
Connection = nil,
Points = {},
HitTargets = {},
Ignore = ignoreList and ignoreList or {}
}, Hitboxing)
Instances[instanceObject] = NewHitbox
Instances[instanceObject]:FindDamageAttachments(instanceObject)
local Connection
Connection = instanceObject.AncestryChanged:Connect(function()
if not Workspace:IsAncestorOf(instanceObject) then
Instances[instanceObject]:Deactivate()
Instances[instanceObject] = nil
Connection:Disconnect()
Connection = nil
print("Hitbox Object was deleted")
end
end)
print("Created Hitbox for Object")
else
print("RaycastHitModule requires a Model instance")
end
end
end
function RaycastHitModule:Deinitialize(instanceObject)
if typeof(instanceObject) ~= "Instance" then warn("RaycastHitModule requires an Instance") end
local Hitbox = Instances[instanceObject]
if Hitbox then
Hitbox:Deactivate()
Instances[instanceObject] = nil
Hitbox = nil -- doesn't work if the above is their intention
else
print("Hitbox does not exist")
end
end
function RaycastHitModule:HitStart(instanceObject, Damage)
local Hitbox = Instances[instanceObject]
if not (Hitbox ~= nil) then warn("Hitbox does not exist") end
if Hitbox then
Hitbox:RaysStart(Damage)
end
end
function RaycastHitModule:HitStop(instanceObject)
local Hitbox = Instances[instanceObject]
if not (Hitbox ~= nil) then warn("Hitbox does not exist") end
if Hitbox then
Hitbox:RaysStop()
end
end
return RaycastHitModule
--[[
Length benchmark:
local Functions = {}
local Workspace = game:GetService("Workspace")
local table_insert = table.insert
local function GlobalTableInsert(Parent, ClassName)
local Descendants = {}
for _, Descendant in ipairs(Parent:GetDescendants()) do
if Descendant:IsA(ClassName) then
table.insert(Descendants, Descendant)
end
end
return Descendants
end
local function LocalTableInsert(Parent, ClassName)
local Descendants = {}
for _, Descendant in ipairs(Parent:GetDescendants()) do
if Descendant:IsA(ClassName) then
table_insert(Descendants, Descendant)
end
end
return Descendants
end
local function RecalculateLength(Parent, ClassName)
local Descendants = {}
for _, Descendant in ipairs(Parent:GetDescendants()) do
if Descendant:IsA(ClassName) then
Descendants[#Descendants + 1] = Descendant
end
end
return Descendants
end
local function LengthVariable(Parent, ClassName)
local Descendants = {}
local Length = 0
for _, Descendant in ipairs(Parent:GetDescendants()) do
if Descendant:IsA(ClassName) then
Length = Length + 1
Descendants[Length] = Descendant
end
end
return Descendants
end
Functions["GlobalTableInsert"] = function()
local Table = GlobalTableInsert(Workspace, "ModuleScript")
Table = nil
end
Functions["LocalTableInsert"] = function()
local Table = LocalTableInsert(Workspace, "ModuleScript")
Table = nil
end
Functions["RecalculateLength"] = function()
local Table = RecalculateLength(Workspace, "ModuleScript")
Table = nil
end
Functions["LengthVariable"] = function()
local Table = LengthVariable(Workspace, "ModuleScript")
Table = nil
end
require(4185109675).new(1, "BenchmarkName", Functions)
--]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment