I implemented guns using workspace:FindPartOnRayWithIgnoreList() because I want the gun beam to pass through parts without CanCollide. Currently, every time I raycast, I create a table and iterate through each descendant of the workspace and append it to the previously mentioned table if it matches the desired criteria. However, this causes lag if there’s a lot of descendants in the workspace (around 10 000) and the gun has a high fire rate.
Possible solutions:
Of course, I could cache the ignore list and update it every at an interval but for things such as turrets, there might be a bit of lag between a part removing CanCollide and the turret beam updating and displaying how it should interact with the environment.
Another solution could be to recreate the ignore list when a descendant’s CanCollide is changed. The problem is that parts change their CanCollide fairly often which could make the code run at a higher frequency than if I just updated it at an interval making it possibly even laggier than the first solution.
My final idea was to listen to all the CanCollide property change events of the BasePart descendants of the workspace and delete or append the BasePart to the ignore list table. The problem is that the only way I know of deleting an object from a table is with table.remove(index) which requires knowledge of the entry’s index which will change as other entries get removed.
No need to add everything possible to the ignore list. Just keep raycasting and adding parts you hit to the ignore list until the next raycast either hits nothing or a collidable part.
local function rayFilter(hit)
if hit then
return hit.CanCollide
else
return true
end
end
local maxHit = 5 --limit before you don't check parts anymore, so the game doesn't slow down too much
local ray = -- ray here
local IgnoreTable = {} -- obviously non-collidable parts will probs be in here
local hit
for i = 1, maxHit do
hit = workspace:FindPartOnRayWithIgnoreList(ray,IgnoreTable)
IgnoreTable[#IgnoreTable + 1] = hit
if rayFilter(hit) then
break
end
end
-- do something with hit/ other variables
if you don’t want to restrict yourself to a limited number of hits, even though this is not recommended since it could potentially slow down the client, this would replace the for loop:
local hit
repeat
hit = workspace:FindPartOnRayWithIgnoreList(ray,IgnoreTable)
IgnoreTable[#IgnoreTable + 1] = hit
until rayFilter(hit)
This is usually the best solution. Managing a potentially enormous ignore list is probably not what you want; it could mean all raycasts have the overhead of a huge ignorelist of things that mostly won’t be hit. It would only be a good option if the whole place has just a handful of non-collidable parts. To build this list you’d initially iterate through all workspace descendants, but you’d never do that again, you’d instead maintain the list by add/remove. The list does not need to be an array, it can be a dictionary keyed by part instance, so you can remove by ignoreList[part] = nil. Even still, you’d want to handle workspace.DescendantAdded and Removing, and probably also make a utility function to wrap getting and setting of CanCollide, so you don’t have a property change listener for every part in game!
Keep in mind too, that Character limb parts are normally CanCollide=false, so you don’t want to ignore these unless you only want head and trunk hits to be counted.
What do you mean by “a utility function to wrap getting and setting of CanCollide?” I didn’t know there was an approach different from property change listeners.
What @EmilyBendsSpace means is that you create a custom function for getting and setting properties instead of doing it all raw.
So instead of something like:
part.CanCollide = true
You use a function that does it for you, plus some other stuff:
local function setCollideWrapper(part, bool)
part.CanCollide = bool
IgnoreTable[part] = not bool or nil
-- change the part's transparency or whatever else
end
This is so you never forget to mend these statements together and you can use this additional logic in your raycasting function.
I think you need to keep the ignoreList all in the array part of a table for it to work, and the array can’t be sparse, so you either have to rebuild the array every time (lazy O(N) way), or you need to so something like this to retain the O(1) benefit of the dictionary:
IgnoreList = {}
IgnoreDict = {}
local function setCollideWrapper(part,bool)
part.CanCollide = bool
if bool then
local index = IgnoreDict[part]
if index then
IgnoreList[index] = IgnoreList[#IgnoreList]
IgnoreList[#IgnoreList] = nil
IgnoreDict[part] = nil
end
else
local index = #IgnoreList + 1
IgnoreList[index] = part
IgnoreDict[part] = index
end
end