So I’m having a problem which is very annoying. I have to enter a specific number to scale and move the billboard/surface GUI which is pain. Nobody has really mentioned this and I haven’t found anything.
You can try creating the Gui in StarterGui then putting it in SurfaceGui.
If, on the other hand, you have to change the values from SurfaceGui then you have to do it manually.
I know that already but it’s not that effective if you don’t know your sizes for the part to SurfaceGui.
I know but you can try it like I did here:
in the first box I set the Gui to full screen, in the second one I resized it making it more central and small and the result pretty much coincides
(obviously also depends on the size of the part)
Okay, I’ll look into it. If anyone else has any new suggestions please reply to this post.
Would it be beneficial to you to have a plug-in that automatically synchronized changes between the GUI in StarterGui and the one in the SurfaceGui?
Exactly, someone has to make a plugin that does this because scaling is a waste of time.
Alright, here’s the first version of the plugin. I’m not too familiar with GUI editing so it may have some quirks, let me know in a DM when they occur. Save your work before you use this, I think I got all the bugs but if I missed one there’s a chance it might delete part of your GUI.
For now, just paste this into the console when you’re ready to use it. It will stop running when you close the game.
To use it, just select a BillboardGui or SurfaceGui and the editor will pop up on your screen. Let me know what works and what doesn’t and I’ll see if I can fix it before I release it as a full plugin.
--<< GLOBAL VARS >>--
local flat_directory --| {int = {Instance, Instance}} | flat directory of all files, allows us to get dopplegangers easily without recursion.
local world_gui --| Instance | the world gui object.
local editor_gui --| Instance | the editor gui object.
local events --| {RbxScriptConnection} | table containing connected events to assist with cleanup.
--<< FUNCTION VARS >>--
local load_directory --| void | load_directory | SurfaceGui/BillboardGui dir
local connect_events --| void | connect_events | Instance obj
local obj_is_selected --| boolean | obj_is_selected | Instance obj
local is_world_gui --| boolean | is_world_gui | Instance obj
local is_editor_gui --| boolean | is_editor_gui | Instance obj
local get_doppleganger --| Instance | get_doppleganger | Instance obj
local change_property --| void | change_property | Instance obj, string property, any value
local is_editor_selected --| boolean | is_editor_selected | Instance obj
local is_world_selected --| boolean | is_world_selected | Instance obj
-- TODO: Errors on drag and drop children
do --<< FUNCTION CREATION >>--
function load_directory(dir)
-- Load a directory. This function expects dir
-- to be a valid SurfaceGui or BillboardGui.
-- It will load all children of dir into the
-- flat_directory table and connect the events.
-- It will create the new editor GUI.
-- Initialize variables.
world_gui = dir
events = {}
flat_directory = {}
-- Initialize the editor GUI.
if editor_gui then editor_gui:Destroy() end
editor_gui = Instance.new("ScreenGui", game.StarterGui)
editor_gui.Name = "EditorGui"
local main = Instance.new("Frame", editor_gui)
main.Name = "Main"
main.Size = UDim2.new(0, dir.AbsoluteSize.X, 0, dir.AbsoluteSize.Y)
if dir:IsA("SurfaceGui") and dir.Parent and dir.Parent:IsA("BasePart") then
main.BackgroundColor3 = dir.Parent.Color
main.BorderSizePixel = 0
main.BackgroundTransparency = dir.Parent.Transparency
else
main.BackgroundTransparency = 0.9
main.BorderSizePixel = 3
end
-- Create the flat directory id system.
for i, v in dir:GetDescendants() do
v:SetAttribute("EditorId", i)
flat_directory[i] = {v}
end
-- Copy the directory to the editor GUI.
for i, v in dir:GetChildren() do
v:Clone().Parent = main
end
-- Add the editor GUI to the flat directory.
for i, v in main:GetDescendants() do
table.insert(flat_directory[v:GetAttribute("EditorId")], v)
end
-- Connect events.
connect_events(main)
for i, v in flat_directory do
connect_events(v[1])
connect_events(v[2])
end
end
function connect_events(obj)
-- Connect the events for the object.
-- Check if the object is a valid target and save a local variable
-- based on whether this is part of the editor GUI or the world GUI.
local is_editor
if is_editor_gui(obj) then
is_editor = true
elseif is_world_gui(obj) then
is_editor = false
else
return
end
if obj ~= editor_gui.Main then
events[#events+1] = obj.Changed:Connect(function(prop)
-- If the object is selected and the doppleganger is not, change
-- the doppleganger.
local doppleganger = get_doppleganger(obj)
if not obj_is_selected(obj) then return end
if not doppleganger then return end
if obj_is_selected(doppleganger) then return end
-- Use pcall for edge cases with read-only properties
-- such as AbsoluteSize
pcall(change_property, doppleganger, prop, obj[prop])
end)
else
events[#events+1] = obj.Changed:Connect(function(prop)
if world_gui:IsA("SurfaceGui") and world_gui.Parent and world_gui.Parent:IsA("BasePart") then
if prop == "BackgroundColor3" then
world_gui.Parent.Color = obj[prop]
elseif prop == "BackgroundTransparency" then
world_gui.Parent.Transparency = obj[prop]
end
end
end)
end
events[#events+1] = obj.ChildAdded:Connect(function(child)
-- If the main GUI directory is selected, create the doppleganger.
task.wait()
if (is_editor and is_world_selected())
or (not is_editor and is_editor_selected()) then
return
end
-- Abort if this already has an existing entry in the flat directory.
local attr = child:GetAttribute("EditorId")
if attr and flat_directory[attr] and
(flat_directory[attr][1] == child
or flat_directory[attr][2] == child) then
-- warn("Could not sync changes. Manually double check your changes"..
-- " and contact the moron who wrote this plugin. ID 102")
return
end
-- Create the doppleganger and add both to the flat directory.
attr = #flat_directory + 1
child:SetAttribute("EditorId", attr)
local clone = child:Clone()
if is_editor then
flat_directory[attr] = {clone, child}
else
flat_directory[attr] = {child, clone}
end
clone.Parent = get_doppleganger(obj)
-- Connect the events.
connect_events(child)
connect_events(clone)
end)
events[#events+1] = obj.ChildRemoved:Connect(function(child)
-- Clean up references to the child and the doppleganger, if any.
local attr = child:GetAttribute("EditorId")
if not attr then return end
if not flat_directory[attr] then return end
if flat_directory[attr][1] ~= child and flat_directory[attr][2] ~= child then return end
local doppleganger = get_doppleganger(child)
table.remove(flat_directory, attr)
doppleganger:Destroy()
game.Selection:Set({obj})
end)
end
function obj_is_selected(obj)
-- Check if the object is contained in the selection.
return table.find(game.Selection:Get(), obj) ~= nil
end
function is_world_gui(obj)
-- Check if the object is part of the world GUI.
-- These functions only exist for the sake of self-documenting code.
return obj:IsDescendantOf(world_gui) or obj == world_gui
end
function is_editor_gui(obj)
-- Check if the object is a descendant of the editor GUI.
return obj:IsDescendantOf(editor_gui) or obj == editor_gui
end
function get_doppleganger(obj)
-- Get the doppleganger of the object.
local attr = obj:GetAttribute("EditorId")
if attr and flat_directory[attr] then
if flat_directory[attr][1] == obj then
return flat_directory[attr][2]
elseif flat_directory[attr][2] == obj then
return flat_directory[attr][1]
end
end
return nil
end
function change_property(obj, prop, value)
-- This function exists only to make pcall look cleaner.
obj[prop] = value
end
function is_editor_selected()
-- Check if the editor gui or a descendant of it is selected.
for i, v in game.Selection:Get() do
if not is_editor_gui(v) and v ~= editor_gui then
return false
end
end
end
function is_world_selected()
-- Check if the world gui or a descendant of it is selected.
for i, v in game.Selection:Get() do
if not is_world_gui(v) and v ~= world_gui then
return false
end
end
end
end
do --<< EVENT CREATION >>--
game.Selection.SelectionChanged:Connect(function()
task.wait()
local selection = game.Selection:Get()
if #selection > 1 then return end
local new = true
if world_gui then
for i, v in selection do
if is_world_gui(v) or is_editor_gui(v) then
new = false
break
end
end
end
if new then
world_gui = nil
if events then
for i, v in events do
v:Disconnect()
end
events = {}
end
if editor_gui then
editor_gui:Destroy()
editor_gui = nil
end
if #selection == 0 then return end
local world = selection[1]
if world:IsA("BillboardGui") or world:IsA("SurfaceGui") then
load_directory(world)
game.Selection:Set({editor_gui.Main})
else
world = world:FindFirstAncestorWhichIsA("BillboardGui")
or world:FindFirstAncestorWhichIsA("SurfaceGui")
if world then
load_directory(world)
local doppleganger = get_doppleganger(selection[1])
if doppleganger then
game.Selection:Set({doppleganger})
else
game.Selection:Set({editor_gui.Main})
end
end
end
elseif #selection == 1 then
if is_world_gui(selection[1]) then
local doppleganger = get_doppleganger(selection[1])
if doppleganger then
game.Selection:Set({doppleganger})
end
end
end
end)
end
This is really cool, I’ll definitely be using this in the future!
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.