hello!
Your question was very interesting, because I’ve never actually developed a wire system myself…
But I decided I wanted to try it, so I made a wire system for you, the code might look chaotic because I made it quickly, because it is an example code, but you might change it or make it more beautiful!
I’ve added comments where I think they would be useful, but I recommend reading the documentation on stuff you’ve never seen!
Anyway, so for this system, you need a tool, two remote events inside replicated storage, and three tags:
“power” - a power source
“conductor” - conducts power through wires.
“action” - something like light, which receives power and performs an action, stated by the name of the part.
then inside serverscriptservice, add a script, this will manage the three tags:
local col = game:GetService("CollectionService")--collectionservice, ruler of tags.
local actions = require(game.ServerScriptService.Actions)--the module with actions
local connections = {}--store the connections
local function add(inst: Part)
if not inst:IsA("BasePart") then return end--required if check, for no errors
inst:SetAttribute("Activated", false)--create a new attribute for status
local click = Instance.new("ClickDetector")--create click for turning on power source
click.MouseClick:Connect(function(plr)--click event, changing color and attribute
if inst:GetAttribute("Activated") then
inst:SetAttribute("Activated", false)
inst.BrickColor = BrickColor.new('Really red')
else
inst:SetAttribute("Activated", true)
inst.BrickColor = BrickColor.new('Lime green')
end
end)
click.Parent = inst--parent click to the target instance
--do some other config here...
end
local function sub(inst: Instance)
if not inst:IsA("BasePart") then return end--required if check for no errors
local click = inst:FindFirstChildOfClass("ClickDetector")--find a click
if click then click:Destroy() end--destroy if it exists
inst:SetAttribute("Activated", nil)--remove activated attribute, optional
--do some other cleaning here...
end
col:GetInstanceAddedSignal("power"):Connect(add)--on instance added with tag, run add func
col:GetInstanceRemovedSignal("power"):Connect(sub)--on instance removed with tag, run sub func
for _, obj in ipairs(col:GetTagged("power")) do--for all existing power sources, run add func on them.
add(obj)
end
--conductor tag handling
local function addcon(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", false)--add an attribute
--do some other config here...
connections[inst] = inst:GetAttributeChangedSignal("Activated"):Connect(function()
if inst:GetAttribute("Activated") then
inst.BrickColor = BrickColor.new("Lime green")
else
inst.BrickColor = BrickColor.new("Really red")
end
end)
end
local function subcon(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", nil)--not really necessary
--do some custom cleanup here
if connections[inst] then
connections[inst]:Disconnect()
end
end
col:GetInstanceAddedSignal("conductor"):Connect(addcon)
col:GetInstanceRemovedSignal("conductor"):Connect(subcon)
for _, con in ipairs(col:GetTagged("conductor")) do
addcon(con)
end
--action tag handling
local function addac(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", false)--create an attribute
--do some custom config for action objects
connections[inst] = inst:GetAttributeChangedSignal("Activated"):Connect(function()
if actions[inst.Name] then actions[inst.Name](inst) end--call the instance's action func if it exists
end)
end
local function subac(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", nil)--not really necessary
--do some custom cleanup here
if connections[inst] then connections[inst]:Disconnect() end
end
col:GetInstanceAddedSignal("action"):Connect(addac)
col:GetInstanceRemovedSignal("action"):Connect(subcon)
for _, ac in ipairs(col:GetTagged("action")) do
addac(ac)
end
I know the names are afwul, but It’s because my C developer-type brain picked the names, (in c and c++ you might see names like ‘INT32_LEAST_MIN’ and ‘size_t’ or ‘basic_string<char_allocator… etc’)
(my lua brain and c brain where fighting in this script haha)
then create a tool, and add a localscript to the tool, make sure the tool has a handle, because otherwise .Equipped won’t run!
local tool = script.Parent
local wire = game.ReplicatedStorage:WaitForChild("wire")
local unwire = game.ReplicatedStorage:WaitForChild("unwire")
local uis = game:GetService("ContextActionService")--context action ss for input
local selected = {}--to save the selected parts!
local debounce = false--deboucne for reaction speed
local hovered: Instance = nil--for the currently hovering part
local hoverhighlight = nil --to store the hover highlight
local mousemoveconnection = nil--to store our mousemove con
local player = game.Players.LocalPlayer--player
local mouse = player:GetMouse()--player's mouse
local function unselect()
hovered = nil
mouse.TargetFilter = nil --remove them from target filter
if hoverhighlight then hoverhighlight:Destroy() end--clean up effects
end
local function deselectAll()
for _, obj: Instance in ipairs(selected) do--destroy all highlights
local highlight = obj:FindFirstChildOfClass("Highlight")
if highlight then highlight:Destroy() end
end
table.clear(selected)
end
local function addtoselected()
local highlight = Instance.new("Highlight")--customize selection highlight
highlight.FillColor = Color3.fromRGB(0, 170, 255)
highlight.OutlineColor = Color3.fromRGB(0, 125, 188)
highlight.Parent = hovered
table.insert(selected, hovered)
end
local function selectt()--two tts because there is a built in func named 'select'
hovered = mouse.Target
mouse.TargetFilter = hovered
hoverhighlight = Instance.new("Highlight")--customize highlight
hoverhighlight.FillTransparency = 1
hoverhighlight.OutlineColor = Color3.fromRGB(255, 255, 255)
hoverhighlight.Parent = hovered--set parent LAST
end
local function clean()
unselect()--clean up everything
deselectAll()--clean up currently selected
uis:UnbindAction("PressToWire")--unbind our key press
uis:UnbindAction("PressToUnwire")
if mousemoveconnection then mousemoveconnection:Disconnect() end--disconnect event
end
tool.Equipped:Connect(function()
mousemoveconnection = mouse.Move:Connect(function()--when the mouse is moved, manage highlights
if not mouse.Target then unselect() return end
if mouse.Target == hovered then return end--object is the same, no need to reapply the effects
if hovered then unselect() end--if there is an old object, clean up effects
if not mouse.Target:HasTag("action") and not mouse.Target:HasTag("conductor") and not mouse.Target:HasTag("power") then return end--not eligible
selectt()--select the new target
end)
uis:BindAction("PressToWire", function(name: string, state: Enum.UserInputState)
if state ~= Enum.UserInputState.Begin then return end--has to begin to work
wire:FireServer(table.unpack(selected))--unpack selected, and send it with the event
deselectAll()--deselect everything afterwards
end, false, Enum.KeyCode.C)--any keycode
uis:BindAction("PressToUnwire", function(name: string, state: Enum.UserInputState)
if state ~= Enum.UserInputState.Begin then return end--has to begin to work
unwire:FireServer(table.unpack(selected))--unpack selected, and send it with the event
deselectAll()--deselect everything afterwards
end, false, Enum.KeyCode.V)
end)
tool.Activated:Connect(function()--add to selected, if eligible
if not hovered then return end--error check
if table.find(selected, hovered) then return end--is already selected
addtoselected()--add obj to selected
end)
tool.Unequipped:Connect(clean)--connect the cleanup operation to both finalize events
tool.Destroying:Connect(clean)
this client script manages the client sided effects, like selection boxes and yeah.
Yes the system comes with its own effects, because I decided it would be cool.
The system also allows for multiple selection, so you select three parts, and they’d get connected in the order you selected them!
then in the serverscriptservice add a new script, different from the collectionscript, this script will manage the server sided wires; when a client sends a request to wire two objects, the server will connect them!
local wire = game.ReplicatedStorage.wire--get remote
local unwire = game.ReplicatedStorage.unwire--get remote
local connections = {}--keep track of the connections
wire.OnServerEvent:Connect(function(plr, ...)--server connection
local tab = table.pack(...)
for i=1, #tab do
if i <= 1 then continue end--avoid connecting solely one object & gaurantee the existance of 2 objects
local obj1 = tab[i-1]
local obj2 = tab[i]
if connections[obj1] and connections[obj1][obj2] then return end--obj1 already connected to obj2
local attach1 = Instance.new("Attachment", obj1)
local attach2 = Instance.new("Attachment", obj2)
local ibeam = Instance.new("Beam")--customize wire
ibeam.Attachment0 = attach1
ibeam.Attachment1 = attach2
ibeam.Color = ColorSequence.new(Color3.fromRGB(255, 170, 0), Color3.fromRGB(0, 170, 255))
ibeam.Parent = obj1--set parent AFTER EVERYTHING ELSE
if not connections[obj1] then connections[obj1] = {} end--add an empty array if it doesn't exist yet
connections[obj1][obj2] = obj1:GetAttributeChangedSignal("Activated"):Connect(function()--when set to true, set our obj2 to true aswell.
obj2:SetAttribute("Activated", obj1:GetAttribute("Activated"))--change activated in obj2 to match 1's status
end)
end
end)
unwire.OnServerEvent:Connect(function(plr,...)--the disconnect func, cleans up everything.
local tab = table.pack(...)--transform ... to table
for i=1, #tab do
if i <= 1 then continue end--avoid disconnecting between 1 obj, and make sure 2 objs are present
local obj1 = tab[i - 1]
local obj2 = tab[i]
if not connections[obj1] or not connections[obj1][obj2] then continue end--doesn't have any connections, so no disconnection needed
local attach1 = obj1:FindFirstChildOfClass("Attachment")--get the effects
local attach2 = obj2:FindFirstChildOfClass("Attachment")
local ibeam = obj1:FindFirstChildOfClass("Beam")
if attach1 then attach1:Destroy() end--destroy the effects between the objects
if attach2 then attach2:Destroy() end
if ibeam then ibeam:Destroy() end
connections[obj1][obj2]:Disconnect()--disconnect the attribute listener
--[[
if you want to make it so the conductor and action don't stay on after disconnecting just do:
obj2:SetAttribute("Activated", false)
]]
end
end)
and lastly, you might’ve seen a module require in the collection script, well that’s because I wanted ia module, where all of the functions are defined for electrical actions, and when an action object turns on it would take the object’s name and search for a function that has the same name in the module. Then it would call that function on the object.
This promotes not adding a bunch of script instances that listen to attriubte changes, aswell as you get a nice overview of all possible actions an action object can perform!
module:
local actions = {}
function actions.light(obj: BasePart)
if obj:GetAttribute("Activated") then
local pointlight = Instance.new("PointLight")--customize pointlight
pointlight.Brightness = 5
obj.Material = Enum.Material.Neon
obj.BrickColor = BrickColor.new("Baby blue")
else
local pointlight = obj:FindFirstChildOfClass("PointLight")--clean up pointlight
if pointlight then pointlight:Destroy() end
obj.Material = Enum.Material.SmoothPlastic--default here
obj.BrickColor = BrickColor.new("Grey")--default here
end
end
return actions
it currently only has one function, ‘light’, for lights.
put this modulescript in serverscriptservice!
explanation on the system:
when the tool hovers over an eligible part (a part with any of the three tags mentioned before), it will highlight it (so the player knows it is interactable).
once the mouse is pressed on the part, it becomes blue (indicating it is selected), and when you press ‘C’ it will connect all selected parts, in the order of selection, by looping through each part and connecting them to the previous part in the array.
when you select parts and press ‘V’ it will disconnect all parts from eachother in the order of selection.
when You turn on a power source (by clicking on it, since the collection script adds a clickdetector) it becomes green, indicating it is active, any connected part will also become active, and the collectionscript takes care of identifying special behavior, for example, if it is a conductor it doesn’t need to do anything except change the color to green, but if it is an action it looks for the matching function, calling it on the target if it exists.
here is a video:
this is my first try on a wire system, so hope it is better than yours atleast, because that is what you asked for!
Hope this helps!
edit: realized I’m not using the debounce variable in the tool script, well you can remove it.
edit: I never actually fully read requests, I always just look at it for 3s and my brain fills in the rest of the request automatically so I missed your request where you wanted the beams to change color when power turns off.
well you can add it in the collection script, for each tag, in the if statement for attribute checking, in the else block, search for a beam using:
local beam = inst:FindFirstChildOfClass("Beam");
beam.Color = ColorSequence.new(Color3.new())--black
and in the if block, you might want to change the color back to normal so something like:
local beam = inst:FindFirstChildOfClass("Beam");
beam.Color = ColorSequence.new(Color3.new(1, 1, 1))--white
modified script:
local col = game:GetService("CollectionService")--collectionservice, ruler of tags.
local actions = require(game.ServerScriptService.Actions)--the module with actions
local connections = {}--store the connections
local function add(inst: Part)
if not inst:IsA("BasePart") then return end--required if check, for no errors
inst:SetAttribute("Activated", false)--create a new attribute for status
local click = Instance.new("ClickDetector")--create click for turning on power source
click.MouseClick:Connect(function(plr)--click event, changing color and attribute
local beam = inst:FindFirstChildOfClass("Beam")--find beam
if inst:GetAttribute("Activated") then
inst:SetAttribute("Activated", false)
inst.BrickColor = BrickColor.new('Really red')
if beam then beam.Color = ColorSequence.new(Color3.fromRGB(255, 170, 0), Color3.fromRGB(0, 170, 255)) end
else
inst:SetAttribute("Activated", true)
inst.BrickColor = BrickColor.new('Lime green')
if beam then beam.Color = ColorSequence.new(Color3.new(0, 0, 0)) end
end
end)
click.Parent = inst--parent click to the target instance
--do some other config here...
end
local function sub(inst: Instance)
if not inst:IsA("BasePart") then return end--required if check for no errors
local click = inst:FindFirstChildOfClass("ClickDetector")--find a click
if click then click:Destroy() end--destroy if it exists
inst:SetAttribute("Activated", nil)--remove activated attribute, optional
--do some other cleaning here...
end
col:GetInstanceAddedSignal("power"):Connect(add)--on instance added with tag, run add func
col:GetInstanceRemovedSignal("power"):Connect(sub)--on instance removed with tag, run sub func
for _, obj in ipairs(col:GetTagged("power")) do--for all existing power sources, run add func on them.
add(obj)
end
--conductor tag handling
local function addcon(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", false)--add an attribute
--do some other config here...
connections[inst] = inst:GetAttributeChangedSignal("Activated"):Connect(function()
local beam = inst:FindFirstChildOfClass("Beam")
if inst:GetAttribute("Activated") then
inst.BrickColor = BrickColor.new("Lime green")
if beam then beam.Color = ColorSequence.new(Color3.fromRGB(255, 170, 0), Color3.fromRGB(0, 170, 255)) end
else
inst.BrickColor = BrickColor.new("Really red")
if beam then beam.Color = ColorSequence.new(Color3.fromRGB(0, 0, 0)) end
end
end)
end
local function subcon(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", nil)--not really necessary
--do some custom cleanup here
if connections[inst] then
connections[inst]:Disconnect()
end
end
col:GetInstanceAddedSignal("conductor"):Connect(addcon)
col:GetInstanceRemovedSignal("conductor"):Connect(subcon)
for _, con in ipairs(col:GetTagged("conductor")) do
addcon(con)
end
--action tag handling
local function addac(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", false)--create an attribute
--do some custom config for action objects
connections[inst] = inst:GetAttributeChangedSignal("Activated"):Connect(function()
if actions[inst.Name] then actions[inst.Name](inst) end--call the instance's action func if it exists
local beam = inst:FindFirstChildOfClass("Beam")
if not beam then return end
if inst:GetAttribute("Activated") then
beam.Color = ColorSequence.new(Color3.fromRGB(255, 170, 0), Color3.fromRGB(0, 170, 255))
else
beam.Color = ColorSequence.new(Color3.new(0, 0, 0))
end
end)
end
local function subac(inst: Instance)
if not inst:IsA("BasePart") then return end
inst:SetAttribute("Activated", nil)--not really necessary
--do some custom cleanup here
if connections[inst] then connections[inst]:Disconnect() end
end
col:GetInstanceAddedSignal("action"):Connect(addac)
col:GetInstanceRemovedSignal("action"):Connect(subcon)
for _, ac in ipairs(col:GetTagged("action")) do
addac(ac)
end
add this to collection script
edit: my dumb brain switched the color values in the power source tag handler, so switch those around and it should display the correct colors
also you can change the color sequence value in the server wire handler,if you want a different starting color.
edit: I detected a funny memory leak in the module haha, I forgot to parent the pointlight to the object in the light function . you need to parent it to the object to fix the memory leak, thanks.