Functions using World.Each do not Repeat

-- PRESS THAT BUTTON.
local Button = self:FindFirstChild("ButtonPart")
local ButtonOrigin = handles.Button.Position
local ButtonActivatedParts = game.World.Each("Name")
local ButtonColor = handles.Button.Color
local Pressed = false

local function ActivateParts()
	for i in ButtonActivatedParts do
		local IName = i.Name
		local PTrans = 0
		local CanCollide = "Auto"
		if i:FindFirstChild("ButtonActivated") then		
			IName = "ButtonActivatedPart"
		end
		if IName == "ButtonActivatedPart" then
			if i:FindFirstChild("Invert") then
				CanCollide = "None"
				PTrans = 0.5
			end
			if i:FindFirstChild("FullHide") then
				PTrans = 1
			end
			if i:FindFirstChild("SetTransparency") then
				PTrans = i:FindFirstChild("SetTransparency").Transparency 
			end
			i.Transparency = PTrans
			i.Collider = CanCollide
		end
	end
end

local function ActivatePartsByColor()
	for i in ButtonActivatedParts do
		local IColor = i.Color
		local IName = i.Name
		local PTrans = 0
		local CanCollide = "Auto"
		if i:FindFirstChild("ButtonActivated") then		
		 IName = "ButtonActivatedPart"
		end
		if IName == "ButtonActivatedPart" and i:FindFirstChild("ColorOverride") then		
		 IColor = i:FindFirstChild("ColorOverride").Color
		end
		if IName == "ButtonActivatedPart" and ButtonColor == IColor then
			if i:FindFirstChild("Invert") then
				CanCollide = "None"
				PTrans = 0.5
			end
			if i:FindFirstChild("FullHide") then
				PTrans = 1
			end
			if i:FindFirstChild("SetTransparency") then
				PTrans = i:FindFirstChild("SetTransparency").Transparency 
			end
			i.Collider = CanCollide
			i.Transparency = PTrans
		end
	end
end

Button.Touched:Connect(function(hit: Instance)
	print(hit,"touched button")
	if handles.Button.Material ~= "Neon" then
		print("not neon!")
		Pressed = false
	end
    if hit:HasComponent("BodyPart") == true and Pressed == false and self:FindFirstChild("AcceptPlayers") then
		handles.Button.Material = "Neon"
		handles.Button.Position = ButtonOrigin - Vector3.new(0,0.5,0)
		Pressed = true
		if self:FindFirstChild("ColorSensitive") then
			ActivatePartsByColor()
		else
			ActivateParts()
		end
    elseif hit.Name == "Pushbox" and Pressed == false and self:FindFirstChild("AcceptPushbox") and hit.Color == ButtonColor then
		handles.Button.Material = "Neon"
		handles.Button.Position = ButtonOrigin - Vector3.new(0,0.5,0)
		Pressed = true
		if self:FindFirstChild("ColorSensitive") then
			ActivatePartsByColor()
		else
			ActivateParts()
		end
	elseif hit.Name == "Pushbox" and Pressed == false and self:FindFirstChild("AcceptPushbox") and self:FindFirstChild("BoxColorIgnore") then
		handles.Button.Material = "Neon"
		handles.Button.Position = ButtonOrigin - Vector3.new(0,0.5,0)
		Pressed = true
		if self:FindFirstChild("ColorSensitive") then
			ActivatePartsByColor()
		else
			ActivateParts()
		end
	else
		warn("Cant find it! / Pressed / Not accepted! / Didn't match!")
	end
end)
-- where MAKING THIS HAPEN -pushbox script-
local Button = self:FindFirstChild("ButtonPart")
local Pushbox = self:GetChildren("Pushbox")
local PushboxOrigin = {}
local OrientationOrigin = {}
local OriginTransparency = {}
local OriginColor = {}

for i = 1, #Pushbox do
	if Pushbox[i].Name == "Pushbox" then 
	PushboxOrigin[i] = Pushbox[i].Position
	OrientationOrigin[i] = Pushbox[i].Orientation
	OriginTransparency[i] = Pushbox[i].Transparency
	OriginColor[i] =  Pushbox[i].Color
	end
end

local function RespawnPushbox()
	for i = 1, #Pushbox do
		if Pushbox[i].Name == "Pushbox" then 
		if Pushbox[i]:HasComponent("Anchored") and Pushbox[i]:HasComponent("Collider") then
			Pushbox[i]:RemoveComponent("Anchored")
			Pushbox[i]:RemoveComponent("Collider")
		end
		Pushbox[i].Position = PushboxOrigin[i]
		if Pushbox[i]:FindFirstChild("KeepOrientation") then
		else
			Pushbox[i].Orientation = OrientationOrigin[i]
		end
		if Pushbox[i]:FindFirstChild("KeepVelocity") then
		else
			Pushbox[i].Velocity = Vector3.new(0,0,0)
			end
			Pushbox[i].Transparency = OriginTransparency[i] or 0
			Pushbox[i].Color = OriginColor[i]
		end
	end
end

Button.Touched:Connect(function(hit: Instance)
	print(hit.BillboardText," touched pushbox button")
    if hit:HasComponent("BodyPart") == true then
		RespawnPushbox()
    else
		warn("Cant find it!")
	end
end)

heres an example of what i mean

1 Like

You think you can also share the code in the button resetter? The only problem I see with the logic is that the button setter isn’t resetting the material of the button its tied to. There are some other things that I’d change, but that’s more of optimization/scalebility stuff rather than actual problems

-- DO NOT PRESS THAT BUTTON.
local Deactivator = self:FindFirstChild("ButtonDeactivator")
local Buttons = game.World.Each("ScriptRef")
local ButtonActivatedParts = game.World.Each("Name")
local DeactivatorColor = Deactivator.Color

local function DeactivateParts()
	for i in ButtonActivatedParts do
		local CanColide = "None"
		local PTrans = 0.5
		local IName = i.Name
		if i:FindFirstChild("ButtonActivated") then		
		 IName = "ButtonActivatedPart"
		 print(i.Name," was found, IName changed.")
		end
		if IName == "ButtonActivatedPart" then
			if i:FindFirstChild("Invert") then
				PTrans = 0 
				CanColide = "Auto"
			end 
			if i:FindFirstChild("FullHide") then
				PTrans = 1
			end
			i.Collider = CanColide
			if i:FindFirstChild("IgnoreTransparency") then
				i.Transparency = i:FindFirstChild("IgnoreTransparency").Transparency
			else	
			i.Transparency = PTrans
			end
		end
	end
end

local function DeactivatePartsByColor()
	for i in ButtonActivatedParts do
		local IColor = i.Color
		local CanColide = "None"
		local PTrans = 0.5
		local IName = i.Name
		if i:FindFirstChild("ButtonActivated") then		
		 IName = "ButtonActivatedPart"
		 print(i.Name," was found, IName changed.")
		end
		if IName == "ButtonActivatedPart" and i:FindFirstChild("ColorOverride") then		
		 IColor = i:FindFirstChild("ColorOverride").Color
		end
		if IName == "ButtonActivatedPart" and IColor == DeactivatorColor  then
			if i:FindFirstChild("Invert") then
				PTrans = 0 
				CanColide = "Auto"
			end 
			if i:FindFirstChild("FullHide") then
				PTrans = 1
			end
			i.Collider = CanColide
			if i:FindFirstChild("IgnoreTransparency") then
				i.Transparency = i:FindFirstChild("IgnoreTransparency").Transparency
			else	
			i.Transparency = PTrans
			end
		end
	end
end

local function DeactivateButtons()
	for i in Buttons do
		if i.Name == "ButtonObject" and i:FindFirstChild("Button").Material == "Neon" then
			print("worler")
			i:FindFirstChild("Button").Position = i:FindFirstChild("Button").Position + Vector3.new(0,0.5,0)
			i:FindFirstChild("Button").Material = "SmoothPlastic"
		end
	end
end

local function DeactivateButtonsByColor()
	for i in Buttons do
		if i.Name == "ButtonObject" and i:FindFirstChild("Button").Material == "Neon" then
			if DeactivatorColor == i:FindFirstChild("Button").Color and i:FindFirstChild("Button").Material == "Neon" then
			i:FindFirstChild("Button").Position = i:FindFirstChild("Button").Position + Vector3.new(0,0.5,0)
			i:FindFirstChild("Button").Material = "SmoothPlastic"
			end 
		end
	end
end

Deactivator.Touched:Connect(function(hit: Instance)
	print(hit,"deactivated button")
	if hit:HasComponent("BodyPart") == true then
		if self:FindFirstChild("ColorSensitive") then
			DeactivatePartsByColor()
			DeactivateButtonsByColor()
		else
			DeactivateParts()
			DeactivateButtons()
		end
	else
		warn("Cant find character!")
	end
end)

the logic is sound?

to me the difference is that the pushbox DOESNT use World.Each as ive shared what the ButtonDeactivator does now. this is a bug or?

now moving this to the studio bugs category

Ok so I looked into it. This is intentional, and the “bug” here isn’t what you think it is. Basically, Luduvo is intentionally doing this to stop you from shooting your own foot off. If you look closely at game.World.Each, it doesn’t return a list of instances. It returns a function that then returns a list of instances. That function is likely there to intentionally not return anything after a certain period elapses, or when there’s evidence that some instances in that call are outdated or something. If Luduvo didn’t do that and you started trying to use instances that have already been destroyed, or had its ID change, or had its location in memory changed, or if really anything happened to it in such a way that could make the instance variable “stale”, then unintended weirdness could happen (ranging from the app crashing thanks to use after free-related errors mangling the memory to maybe something more malicious if they did so with purpose and started exploiting this to intentionally specific places in memory (luau is sandboxed though so idk what they would even do).

To “fix” this, just rerun game.World.Each every so often instead of calling it once ever at the top of the script. I suggest putting it in an Update loop because that ensures that it’ll always be populated. The reason I say this could still be a “bug” is that it’s usually louder about it. For instance, trying to use a Query object without running Query:Refresh() before its dead man timer goes off crashes the entire script. game.World.Each just fails silently, so thinking that its broken is probably the fault of Luduvo more than yours.

I’d also consider just rewriting everything. Your code is so cooked bro. It’ll probably work functionally, but this was the strangest code I’ve seen yet. The fact that “Button”, “ButtonPart”, and “ButtonObject” are 3 different things is actually scuffed. It doesn’t help that the Button script refers to the button via a handle, while the deactivator refers to the button via its full legal name. Maybe I’m just slow, but it took me half an hour to map what this does, and I had to rewrite this 2 or 3 times because I kept thinking that it was because of a logic error regarding you looking for/checking the wrong name somewhere when really it was because the strange instance structure this project has. I’ll give more constructive feedback later when I’m done writing it

1 Like

thank you so much!

ill clean up this code later prob, “Button” is a visible button that the player sees, “ButtonPart” is a hitbox cause Trigger forces the part to not collide with players, “ButtonObject” is just the name of all Buttons (hope to change this later cuz i dont like having it be NameSensitive)

yea i wrote the deactivator later, it works for now so ill change that later

I did promise to give some tips, so I will. I trust that you mostly know what you’re doing so I’ll try to keep things short.

The first thing I’d change is definitely rethink how to do your tagging and parameter work since that’s relativily easy to fix. I understand that you want to keep things editable in the editor (likely so artists don’t have to code to make towers), but there’s still another user-facing custom variable system that you are ignoring: handles. I say that instead of making the “attributes” based on instance names, make it based on handles instead. The handle name corresponds to the attribute (“Invert”, “FullHide”, SetTransparance", etc.), then you just pick the part that has the information in it. I tested just trying to put all the data in the handle key itself instead of needing to hardcode check for every value, but Luduvo doesn’t make handles a real table. Even still, I like handles more because its easier to maintain. You can have a group of “style sheet” instances that you just keep repointing to for every obby part and you won’t need to repeat yourself as much.

The next thing I’d change is to actually use tags. Much easier to use than the component alias thing I was talking about in the last reply I made. Even though you can’t add tags normally through the editor, I’d just have them attach stub scripts that mark things when it runs. It would literally only do 2 things:

  1. run game.Tags.Add("button/pushbox/whathaveyou"),
  2. Convert all the handles/child parts into attributes and :Destroy() them. Attributes are much faster to use than using FindFirstChild, and it makes your code MUCH more readable and less hacky. For example, you don’t have to have the IName proxy variable because you can safely change the values of attributes without breaking something else down the line. You also don’t have to rely on people magically knowing that the material of a button controls whether or not the code knows that its on or not so if someone wants to customize what buttons look like, they don’t have to unravel hacky code to do so

Because that brings to another problem I have with this, and that’s that every button, pushbox, etc. runs its own script. Lower end PC people wanting to play large towers will thank you! It’s a lot more performant to have 1 script control 100 parts versus 100 scripts controlling 100 parts. Since everything is in tags and attributes now, it’s trivial to just make one (or a small handful) of controller scripts that manage all of your stuff like so:

function Update(_)
  for _, button in game.Tags:Find("button") do
    -- Button logic
  end

  for _, pushbox in game.Tags:Find("pushbox") do
    -- Pushbox logic
    -- Or, you can just put this in a dedicated "pushbox" script. Doesn't matter.
  end
end

A neat but kinda useless bonus of using tags is that your stuff can now be dynamically changed at runtime. For example, your pushbox code uses a button too, right? If you code things right, you can basically reuse your button code. Though I do say “useless” because it has extremely marginal gains lol.

The final thing I’d do is add custom signals. The exact moment that multiple scripts need to communicate with each other is the exact moment you’re going to need to use signals. It’s much nicer to have all the button functionality in a single script, rather than duplicating and distributing activate/deactivate/toggle code across 2-3 different files. For example, I’d merge a lot of the deactivator script code into the button script like this:


-- PRESS THAT BUTTON.

self:GetSignal("Activate"):Connect(function()
    if self.attr.Pressed then return end

    self.Material = "Neon"
    self.Position += Vector3.new(0,0.5,0)
    self.attr.Pressed = true

    if self.attr.ColorSensitive then
        ActivatePartsByColor()
    else
        ActivateParts()
    end

    self:GetSignal("Toggled"):Emit(true)
end)

self:GetSignal("Deactivate"):Connect(function()
    if not self.attr.Pressed then return end

    self.Position -= Vector3.new(0,0.5,0)
    self.Material = "SmoothPlastic"

    if self:FindFirstChild("ColorSensitive") then
        DeactivatePartsByColor()
        DeactivateButtonsByColor()
    else
        DeactivateParts()
        DeactivateButtons()
    end

    self:GetSignal("Toggled"):Emit(false)
end)

self.Touched:Connect(function(hit: Instance)

    if not hit.attr.ButtonInteractible then return end
    if not string.find(hit.attr.Type + "|", self.attr.AcceptedTypes) then return end
    -- During compilation, you would just combine Accept[Type] into 
    -- 1 string and separate it with a special character of some sort

    if self.attr.Pressed then
        self:GetSignal("Activate"):Emit()
    else
        self:GetSignal("Deactivate"):Emit()
    end
end)

And then in the deactivator script, it would simply be this:

Deactivator.Touched:Connect(function(hit: Instance)
    if not hit.attr.Type == "player" or not handles.Button.attr.Pressed then return end

    print(hit,"deactivated button")
    handles.Button:GetSignal("Deactivate"):Emit()
end)

With the handles.Button being the button assigned to that deactivator. If you have a script that needs to extend the functionality of the button, then you’d just listen to a specially designed signal instead:

handles.Button:GetSignal("Toggled"):Connect(function(isPressed) -- useless for this example but still
    print(hit.BillboardText," touched pushbox button")
    RespawnPushbox()
end)

Again, the button handle has to be assigned in-editor.

The only caveat here would be that if you’re combining this with my earlier suggestion, you obviously can’t use self since a single script controls all buttons. Instead, you’d have to do a bit more complicated code:

function Update(_)
  for _, button in game.Tags:Find("button") do
    if not game.Tags.Has(button, "registered") then
      -- Has to be here you don't keep connecting new signals 
      -- every frame and cause catastrophic memory leaks

      button:GetSignal("Activate"):Connect(function()
          -- Button Logic
      end)
      --...
      game.Tags.Add(button, "registered")
    end
  end

  for _, pushbox in game.Tags:Find("pushbox") do
    if not game.Tags.Has(pushbox, "registered") then 
      pushbox:GetSignal("Activate"):Connect(function()
          -- Pushbox Logic
      end)
      --...
      game.Tags.Add(pushbox, "registered")
    end
  end
end

Honestly, with these 3 tips alone, I think you can make a pretty good, performant, legible, and scalable library. TBH there are a lot of smaller things I’d change (like making it so “on” and “off” buttons are 2 user-createable instances that it switches between so that creators can control everything about their buttons stylistically, Activate/Deactivate Parts would not be a different function from Activate/Deactivate Parts by Color, other some small disagreements with how individual settings are handled, etc.) but that’s not nearly as important or impactful is those 3. Basically nit picks


Sorry for taking so long. Most of this was typed the day of but then I got busy and forgot about it. Hopefully I’m not too late :p

1 Like

hiya, omg thats so much tips thank you thank you

im having an issue using game.Tags.Add() in the stub scripts, i get this error
[ERROR] wire: tag 813 on entity 766 has no name id
[ERROR] wire: tag 813 on entity 767 has no name id

[ERROR] update blob out of memory; tick not broadcast
when i comment game.Tags.Add() out, the script converts the rest of the attributes as intended, except…

[ERROR] attr: the server sent id 5 as ‘AcceptPushbox’, but this world already holds it as ‘AcceptPlayers’
[ERROR] PKT_ATTR_NAME: refused

when i changed the attr.'s value from true to a String, the Strings were mixed up?

edit: i later refeshed studio and the mixed value problem was fixed… why

what am i doing wrong here?

local isInitialized = false

local function ButtonConverter()
game.Tags.Add(self, "ButtonObject")
local AccPlayers = self:FindFirstChild("AcceptPlayers")
if AccPlayers then
self.attr.AcceptPlayers = true
AccPlayers:Destroy()
end
local AccBoxes = self:FindFirstChild("AcceptPushbox")
if AccBoxes then
self.attr.AcceptPushbox = true
AccBoxes:Destroy()
end
local BoxColorIgnore = self:FindFirstChild("BoxColorIgnore")
if BoxColorIgnore then
self.attr.BoxColorIgnore = true
BoxColorIgnore:Destroy()
end
end

function Update()
if not isInitialized == true then
ButtonConverter()
isInitialized = true
end
end

How did you run into 3 different engine bugs from 1 piece of code :sob:

Almost certainly an engine bug, make a bug report. Your tag failed midway through its creation or something and now its a frankenstien thing that is technically a created tag but nothing associates it with the word “buttonObject”. Or really any word/string at all

Different bug. Different bug report. I don’t even know how this happened

Also an engine bug, make a different bug report. Something got desynced and it probably started working again because closing the studio and rerunning it makes it reindex whatever bookkeeping it does for attributes


Outside of that, I would take everything out of the update loop if you are able since all that code will only run once ever. You might have done it like this because there was an error yelling at you to put it in an update loop or something, but if there wasn’t, I’d try just letting everything live unnested inside of any function.