For a while, I’ve been running into slight problems that, when I solve them, I put into a module script I like to call “ObjectFuncs.” The goal of this module script is to make generalized tasks, such as finding out if a chosen part is the descendant of another part that you know the name of, a heck of a lot easier with just one function. So, here is my module script for all o’ y’all to use:
local module = {}
local RunService = game:GetService("RunService")
-- Condenses all the things you could want about Instance.new() into a single line
function module.ShorthandNew(TypeName:string|Instance,Parent:Instance,ObjName:string,PresetValues:{[string]: any}?):Instance
-- Create a new object, go through PresetValues list, and set the value of the new Object to that preset value
local NewObj:Instance --= Instance.new(TypeName)
if type(TypeName) == "string" then
NewObj = Instance.new(TypeName)
else
NewObj = TypeName:Clone()
end
NewObj.Name = ObjName
if PresetValues ~= nil then
for i,v in pairs(PresetValues) do
NewObj[i] = v
end
end
NewObj.Parent = Parent
return NewObj
end
-- Auto deletes the given Instance after WaitTime. Sets the parent of the AutoDeleteScript to the given Parent. It does not delete the script's parent if the parent is different from the target object
function module.CreateAutoDelete(Obj:Instance,Parent:Instance,WaitTime:number)
local Scr = game.ReplicatedStorage.InGameStuff.AutoDeleteScript:Clone()
Scr.Object.Value = Obj
Scr.WaitTime.Value = WaitTime
Scr.Parent = Parent
end
-- Does not overwrite original list. Goes through given list and removes from the list all
-- items that are not parts of FilterClass or FilterName, or items that ARE parts of
-- ExcludeClass and ExcludeName. Recommended to only use either: FilterClass, FilterName, FilterClass
-- and Exclude Name, or FilterName and ExcludeClass.
function module.FilterTable(Table:{Instance},FilterClass:{string}?,ExcludeClass:{string}?,FilterName:{string}?,ExcludeName:{string}?):{Instance}
local List = table.clone(Table)
local AmountRemoved = 0
local function CheckClass(Obj:Instance)
for i2,v2 in pairs(FilterClass) do
if Obj:IsA(v2) then
if ExcludeName then
if not table.find(ExcludeName,Obj.Name) then
return true
else
return false
end
end
return true
end
end
return false
end
local function CheckName(Obj:Instance)
for i2,v2 in pairs(FilterName) do
if Obj.Name == v2 then
if ExcludeClass then
if not table.find(ExcludeClass,Obj.Name) then
return true
else
return false
end
end
return true
end
end
return false
end
for i,v in pairs(Table) do
local ItemWanted = false
if FilterClass ~= nil and FilterName == nil then
-- Only check for class and not name
if CheckClass(v) then
ItemWanted = true
end
elseif FilterClass ~= nil and FilterName ~= nil then
-- Check for both Class and Name
if CheckClass(v) and CheckName(v) then
ItemWanted = true
end
elseif FilterClass == nil and FilterName ~= nil then
-- Only check for Name and not class
if CheckName(v) then
ItemWanted = true
end
end
if ItemWanted == false then
table.remove(List,i - AmountRemoved)
AmountRemoved += 1
end
end
return List
end
-- Calls Instance:Destroy() if the given Object exists
function module.SafeDestroy(Obj:Instance?)
if Obj then
Obj:Destroy()
end
end
-- Combines SafelyGetProperty and SafelyGetChild
function module.SafelyGetPropertyFromChild(Parent:Instance,ChildName:string|{string},PropertyName:string,ReturnValIfNil:any?):any?
if ReturnValIfNil == nil then
ReturnValIfNil = nil
end
if not Parent then return ReturnValIfNil end
local Child:Instance
if type(ChildName) == "table" then
local ParentOn:Instance = Parent
for i,v in pairs(ChildName) do
if i == #ChildName then
-- On the last item
Child = ParentOn:FindFirstChild(tostring(v))
if not Child then
return ReturnValIfNil
end
break
else
Child = ParentOn:FindFirstChild(tostring(v))
if not Child then
return ReturnValIfNil
end
ParentOn = Child
end
end
else
if Parent then
Child = Parent:FindFirstChild(tostring(ChildName))
else
return ReturnValIfNil
end
end
if Child then
local Val = pcall(function() if Child[PropertyName] then return Child[PropertyName] end end)
if Val then
return Child[PropertyName]
else
return ReturnValIfNil
end
else
return ReturnValIfNil
end
end
-- Returns the value of the property of the given object. Returns DefaultReturnVal if the object or property doesn't exist.
function module.SafelyGetProperty(Obj:Instance,PropertyName:string,DefaultReturnValIfNil:any?):any?
if Obj == nil then
return DefaultReturnValIfNil
else
local Val = Obj[PropertyName]
if Val then
return Val
else
return DefaultReturnValIfNil
end
end
end
-- basically FindFirstChild():FindFirstChild():FindFirstChild but it doesn't break if one of them returns nil
function module.SafelyGetChild(Parent:Instance,ChildName:string|{string}):Instance
local Child:Instance
if not Parent then return end -- Parent didn't even exist, lol
if type(ChildName) == "table" then
local ParentOn:Instance = Parent
for i,v in pairs(ChildName) do
if i == #ChildName then
-- On the last item
Child = ParentOn:FindFirstChild(v)
if not Child then
return nil
end
break
else
Child = ParentOn:FindFirstChild(v)
if not Child then
return nil
end
ParentOn = Child
end
end
else
Child = Parent:FindFirstChild(ChildName)
end
return Child
end
-- Condenses raycasting to one line so your own code doesn't look so long.
-- ColGroup = name of the collision group the raycast is a part of
function module.ShorthandRaycast(StartPos:Vector3,Direction:Vector3,ExcludeGroup:{Instance},ColGroup:string,RespectCanCollide:boolean):RaycastResult
local RayParams = RaycastParams.new()
RayParams.CollisionGroup = ColGroup
RayParams.FilterDescendantsInstances = ExcludeGroup
RayParams.RespectCanCollide = RespectCanCollide
local Cast = game.Workspace:Raycast(StartPos,Direction,RayParams)
if Cast then
return Cast
else
-- make my own RaycastResult
local Result:RaycastResult = {}
Result.Distance = Direction.Magnitude
Result.Instance = nil
Result.Material = Enum.Material.Air
Result.Position = StartPos + Direction
Result.Normal = (StartPos + Direction).Unit
return Result
end
end
-- Adds a space for every for every capital letter. Ex: "HelloThereBro" becomes "Hello There Bro"
function module.ConvertStringToDisplay(Str:string):string
local NewString = ""
for i=1, #Str, 1 do
local Letter = string.sub(Str,i,i)
if Letter == string.upper(Letter) then
-- Letter is a capital letter
NewString = NewString .." " ..Letter
else
NewString = NewString ..Letter
end
end
return NewString
end
-- If DecimalPlace = 0.1, then truncate Num to the 0.1s place. ex: 0.583 becomes 0.5
function module.CutoffDecimal(Num:number,DecimalPlace:number):number
local NewNum:number = math.floor(Num * (1 / DecimalPlace)) / (1 / DecimalPlace)
return NewNum
end
-- this function adds commas where there needs to be them
function module.BeautifyNumber(InputNum:number|string):string
local Num = tostring(InputNum)
local FinalStr_Backwards = ""
local GroupNumOn = 0
for i=0, #Num-1,1 do
local char = string.sub(Num,#Num-i,#Num-i)
GroupNumOn = GroupNumOn + 1
if GroupNumOn >= 3 and i ~= #Num-1 then
FinalStr_Backwards = FinalStr_Backwards ..char ..","
GroupNumOn = 0
else
FinalStr_Backwards = FinalStr_Backwards ..char
end
end
-- Now, swap all the characters backwards
local FinalStr = ""
for i=0,#FinalStr_Backwards-1,1 do
FinalStr = FinalStr ..string.sub(FinalStr_Backwards,#FinalStr_Backwards-i,#FinalStr_Backwards-i)
end
return FinalStr
end
-- Asks if the given Object is a descendant of another object with the given name. If true, returns that parent Object. MaxTries is the max number of Parent Tries that will occur to try to check the bool
function module.IsDescendantOfName(Obj:Instance,ParentName:string,MaxTries:number):Instance?
local ObjOn:Instance = Obj
if Obj == nil then return nil end
for i=0,MaxTries,1 do
if ObjOn["Parent"] == nil then
return nil
end
ObjOn = Obj.Parent
if ObjOn.Name == ParentName then
return ObjOn
elseif ObjOn == game.Workspace then
return nil
end
end
return nil
end
-- Useful if you want to get a list of, for example, all the positions of a list of parts
function module.GetPropertyValuesFromTable(List:{any},PropertyName:string):{any}
local FinalList = {}
for i,v in pairs(List) do
local Val = v[PropertyName]
if Val then
FinalList[#FinalList + 1] = v[Val]
end
end
return FinalList
end
-- Sets the given property of all Instances in the given table to the same given value
function module.MassSetProperty(Table:{Instance},PropName:string,Val:any)
for i,v in pairs(Table) do
if v[PropName] then
v[PropName] = Val
end
end
end
--[[
Turns a TweenInfo object into a table so it can be passed as an Event argument
]]
function module.CompressTweenInfo(Info:TweenInfo)
local NewInfo = {}
NewInfo.Time = Info.Time
NewInfo.EasingDirection = Info.EasingDirection
NewInfo.EasingStyle = Info.EasingStyle
NewInfo.Reverses = Info.Reverses
NewInfo.DelayTime = Info.DelayTime
NewInfo.RepeatCount = Info.RepeatCount
return NewInfo
end
-- Changes a single coordinate to the value of ChangeTo inside a Vector3. Does not overwrite original given Vector.
-- Instead, returns a copy of the given vector with the changed coordinate.
function module.ChangeCoordinateFromVector3(Vector:Vector3,ChangeTo:number,Coord:string)
if not Vector then return end
if Coord == "x" then
return Vector3.new(ChangeTo,Vector.y,Vector.z)
elseif Coord == "y" then
return Vector3.new(Vector.x,ChangeTo,Vector.z)
elseif Coord == "z" then
return Vector3.new(Vector.x,Vector.y,ChangeTo)
end
return Vector
end
-- Will start at the given object, and keeps getting its parents until either it reaches the game, or it reaches an ancestor with EndParentName. Returns a list that does NOT include the EndParentName. MaxParentChecks defaults to 10. ReturnAsString defaults to false
function module.GetHierarchy(Obj:Instance,EndParentName:string,ReturnAsString:boolean,MaxParentChecks:number)
MaxParentChecks = MaxParentChecks or 10
local OverallTable:{string}|string = {Obj.Name}
if ReturnAsString then
OverallTable = Obj.Name
end
local LastObjOn:Instance = Obj
for i=1, MaxParentChecks, 1 do
if LastObjOn.Parent == nil then
-- reached the end of the line. Presumably at game now
if ReturnAsString == true then
local StartPos = string.find(OverallTable,"Game/")
local StringToAvoid = string.sub(OverallTable,StartPos,(StartPos + string.len("Game/")))
OverallTable = string.sub(OverallTable,(StartPos + string.len("Game/")))
else
table.remove(OverallTable,table.find(OverallTable,"Game"))
end
break
end
if LastObjOn.Parent.Name == EndParentName then
-- reached the end of the line
break
end
LastObjOn = LastObjOn.Parent
if ReturnAsString then
OverallTable = LastObjOn.Name .. "/" ..OverallTable
else
table.insert(OverallTable,1,LastObjOn.Name)
end
end
return OverallTable
end
-- Uses a descendant path starting at game and using "/" as the child/parent denominator.
-- If total wait time exceeds MaxTimeout, returns nil. MaxTimeout defaults to 5 seconds.
-- Useful for when the client needs to wait for the entirey of a object hierarchy line
-- to exist.
function module.WaitToReplicateObj_WaitForChild(StartingParent:Instance,ObjPath:string,MaxTimeout:number)
local ChildTable:{string} = string.split(ObjPath,"/")
local ChildToReturn:Instance = nil
MaxTimeout = MaxTimeout or 5
local StartTime = time()
local CurrentParent = StartingParent
for i,v in pairs(ChildTable) do
--print("Waiting for " ..v)
ChildToReturn = CurrentParent:WaitForChild(v,MaxTimeout - (time() - StartTime))
if not ChildToReturn then
return nil
end
CurrentParent = ChildToReturn
end
print(ChildToReturn)
return ChildToReturn
end
-- Waits until the Property of the given object, specified by PropName, becomes any value that is NOT ValToAvoid.
-- Timeout defaults to -1 which is an indefinite wait.
function module.WaitUntilPropertyIsNotDefault(Obj:Instance,PropName:string,ValToAvoid:any,Timeout:number)
if not Timeout then Timeout = -1 end
local StartTime = time()
repeat
if Timeout > 0 then
if time() - StartTime >= Timeout then
break
end
end
RunService.Stepped:Wait()
until Obj[PropName] ~= ValToAvoid
end
-- Disconnects an event connection from a function. Does not break
-- if the script has already disconnected, or if the connection ceases
-- to exist.
function module.SafelyDisconnect(Con:RBXScriptConnection)
if Con then
if Con.Connected == true then
Con:Disconnect()
end
end
end
-- Only runs on client. Relies on the Player:HasAppearanceLoaded() boolean function.
function module.WaitForPlayerCharacterToLoad(Player)
if not Player and RunService:IsClient() then
Player = game.Players.LocalPlayer
end
while not Player:HasAppearanceLoaded() do
print("Player: " ..tostring(Player:HasAppearanceLoaded()))
RunService.Stepped:Wait()
end
--if not Player:HasAppearanceLoaded() then
-- Player.CharacterAppearanceLoaded:Wait()
--end
end
return module