-- GUIHelper runtime (LocalScript) – rblxplugin.com -- This is the complete script GUIHelper puts into your ScreenGui. -- It only reads attributes on your own UI objects. No network access, -- no data collection, no require() of external models. -- Published so anyone can audit it before buying. __DEFAULTS__ is replaced -- on import by the effect defaults you set in the Figma plugin. -- GUIHelper-Laufzeit: skaliert Text, Ecken, Konturen und Grids passend zum -- Bildschirm und spielt die Effekte aus den Figma-Tags ab (GH_Tags). -- Jeden Effekt-Wert kannst du am Element als Attribut ändern (z. B. SpinSpeed). local RunService = game:GetService("RunService") local UserInputService = game:GetService("UserInputService") local GuiService = game:GetService("GuiService") local gui = script.Parent local screenW = gui:GetAttribute("GH_ScreenW") or 1920 local screenH = gui:GetAttribute("GH_ScreenH") or 1080 local camera = workspace.CurrentCamera while not camera do task.wait() camera = workspace.CurrentCamera end local DEFAULTS = __DEFAULTS__ local function px(v) return math.floor(v + 0.5) end -- ---------------------------------------------------------------- Skalierung local f = 1 local function rescale() local vp = camera.ViewportSize local nf = math.min(vp.X / screenW, vp.Y / screenH) if nf <= 0 then return end f = nf for _, inst in ipairs(gui:GetDescendants()) do local ts = inst:GetAttribute("GH_TextSize") if ts and (inst:IsA("TextLabel") or inst:IsA("TextButton")) then inst.TextSize = math.max(1, math.floor(ts * f + 0.5)) end local r = inst:GetAttribute("GH_Radius") if r and inst:IsA("UICorner") then inst.CornerRadius = UDim.new(0, math.floor(r * f + 0.5)) end local th = inst:GetAttribute("GH_Thickness") if th and inst:IsA("UIStroke") then inst.Thickness = math.max(0.5, th * f) end local cw = inst:GetAttribute("GH_CellW") if cw and inst:IsA("UIGridLayout") then inst.CellSize = UDim2.fromOffset(px(cw * f), px((inst:GetAttribute("GH_CellH") or 0) * f)) inst.CellPadding = UDim2.fromOffset(px((inst:GetAttribute("GH_GapX") or 0) * f), px((inst:GetAttribute("GH_GapY") or 0) * f)) end if inst:IsA("UIPadding") and inst:GetAttribute("GH_PadL") then inst.PaddingLeft = UDim.new(0, px(inst:GetAttribute("GH_PadL") * f)) inst.PaddingTop = UDim.new(0, px((inst:GetAttribute("GH_PadT") or 0) * f)) inst.PaddingBottom = UDim.new(0, px((inst:GetAttribute("GH_PadB") or 0) * f)) end local bar = inst:GetAttribute("GH_Bar") if bar and inst:IsA("ScrollingFrame") then inst.ScrollBarThickness = math.floor(bar * f + 0.5) end end end rescale() camera:GetPropertyChangedSignal("ViewportSize"):Connect(rescale) -- ---------------------------------------------------------------- Effekte local ANIM = { smooth = true, magnet = true, up = true, turn = true, squish = true, lean = true, spin = true, float = true, pulse = true, blink = true, jelly = true, wiggle = true, shake = true, pop = true, rays = true, drift = true, shiny = true, glint = true, } local BUTTONISH = { button = true, up = true, turn = true, squish = true, close = true, open = true, toggle = true } local function tagsOf(inst) local s = inst:GetAttribute("GH_Tags") if type(s) ~= "string" or s == "" then return nil end local t = {} for tag in s:gmatch("[^,]+") do t[tag] = true end return t end local function cap(s) return s:sub(1, 1):upper() .. s:sub(2) end local function readParams(inst, tags) local p = {} for tag in pairs(tags) do local d = DEFAULTS[tag] if d then local t = {} for key, def in pairs(d) do local v = inst:GetAttribute(cap(tag) .. key) if type(v) ~= type(def) then v = def end t[key] = v end p[tag] = t end end return p end local function isButton(tags) for t in pairs(tags) do if BUTTONISH[t] then return true end end return false end local entries, byInst = {}, {} for _, inst in ipairs(gui:GetDescendants()) do if inst:IsA("GuiObject") then local tags = tagsOf(inst) local wanted = false if tags then for t in pairs(tags) do if ANIM[t] or BUTTONISH[t] then wanted = true end end end if wanted then local e = { inst = inst, tags = tags, p = readParams(inst, tags), s = { popStart = 0 }, basePos = inst.Position, baseSize = inst.Size, baseRot = inst.Rotation, phase = math.random() * math.pi * 2, button = isButton(tags), } inst.AttributeChanged:Connect(function() e.p = readParams(inst, e.tags) end) if tags.smooth or tags.pulse or tags.jelly or tags.pop or tags.rays then local sc = inst:FindFirstChild("GH_Scale") if not sc then sc = Instance.new("UIScale") sc.Name = "GH_Scale" sc.Parent = inst end e.scale = sc end table.insert(entries, e) byInst[inst] = e end end end -- Lean/Glint reagieren auf den nächsten Button darüber (sonst auf den Eltern) local function hostOf(inst) local p = inst.Parent while p and p ~= gui do local t = tagsOf(p) if t and isButton(t) then return p end p = p.Parent end return inst.Parent end for _, e in ipairs(entries) do if e.tags.lean or e.tags.glint then local host = hostOf(e.inst) if host and host:IsA("GuiObject") and not byInst[host] then local he = { inst = host, tags = {}, p = {}, s = {}, passive = true } table.insert(entries, he) byInst[host] = he end e.host = host and byInst[host] end end -- Abdunkeln beim Drücken: Bilder, Flächen, Texte des Buttons (außer _nodim) local function collectDim(e) local list = {} local function add(inst) local t = tagsOf(inst) if t and t.nodim then return end if inst:IsA("ImageLabel") or inst:IsA("ImageButton") then table.insert(list, { inst, "ImageColor3", inst.ImageColor3 }) end if inst:IsA("GuiObject") and inst.BackgroundTransparency < 1 then table.insert(list, { inst, "BackgroundColor3", inst.BackgroundColor3 }) end if inst:IsA("TextLabel") or inst:IsA("TextButton") then table.insert(list, { inst, "TextColor3", inst.TextColor3 }) end for _, c in ipairs(inst:GetChildren()) do if c:IsA("GuiObject") then add(c) end end end add(e.inst) return list end for _, e in ipairs(entries) do if e.tags.button then e.dim = collectDim(e) end if e.tags.blink then local i = e.inst e.alphaBase = { img = (i:IsA("ImageLabel") or i:IsA("ImageButton")) and i.ImageTransparency or nil, txt = (i:IsA("TextLabel") or i:IsA("TextButton")) and i.TextTransparency or nil, grp = i:IsA("CanvasGroup") and i.GroupTransparency or nil, bg = i.BackgroundTransparency, } end end -- ---------------------------------------------------------------- Aktionen local playerGui = gui:FindFirstAncestorWhichIsA("PlayerGui") local function findPanel(inst) local p = inst.Parent while p and p ~= gui do local t = tagsOf(p) if t and t.panel then return p end p = p.Parent end return nil end local function findTarget(name) local hit = gui:FindFirstChild(name, true) if hit and (hit:IsA("GuiObject") or hit:IsA("LayerCollector")) then return hit end if playerGui then hit = playerGui:FindFirstChild(name) or playerGui:FindFirstChild(name, true) if hit and (hit:IsA("GuiObject") or hit:IsA("LayerCollector")) then return hit end end return nil end local function isOpen(target) if target:IsA("LayerCollector") then return target.Enabled end local sg = target:FindFirstAncestorWhichIsA("LayerCollector") return target.Visible and (not sg or sg.Enabled) end local function setOpen(target, open) if target:IsA("LayerCollector") then target.Enabled = open return end target.Visible = open if open then local sg = target:FindFirstAncestorWhichIsA("LayerCollector") if sg then sg.Enabled = true end end end for _, e in ipairs(entries) do local inst, tags = e.inst, e.tags if inst:IsA("GuiButton") and (tags.close or tags.open or tags.toggle) then inst.Activated:Connect(function() if tags.close then local panel = findPanel(inst) if panel then panel.Visible = false else gui.Enabled = false end return end local name = inst:GetAttribute("GH_Target") if type(name) ~= "string" or name == "" then return end local target = findTarget(name) if not target then warn("[GUIHelper] Ziel „" .. name .. "“ nicht gefunden (von " .. inst:GetFullName() .. ")") return end if tags.toggle then setOpen(target, not isOpen(target)) else setOpen(target, true) end end) end end -- ---------------------------------------------------------------- Rechnen local function ease(cur, target, dt, tau) return cur + (target - cur) * (1 - math.exp(-dt / math.max(tau, 0.001))) end local function spring(s, k, target, dt, freq, damp) local x, v = s[k] or 0, s[k .. "v"] or 0 local w = 2 * math.pi * freq local rem = dt while rem > 0 do local st = math.min(rem, 1 / 120) v += (w * w * (target - x) - 2 * damp * w * v) * st x += v * st rem -= st end s[k], s[k .. "v"] = x, v return x end local function easeOutBack(p) local c1 = 1.70158 local c3 = c1 + 1 return 1 + c3 * (p - 1) ^ 3 + c1 * (p - 1) ^ 2 end local function blinkAlpha(t, p) if t < p.Delay then return 1 end local cyc = p.Visible + p.Hidden local tau = (t - p.Delay) % cyc local fd = math.min(p.Fade, p.Visible, p.Hidden) if tau < p.Visible - fd then return 1 elseif tau < p.Visible then return fd > 0 and 1 - (tau - (p.Visible - fd)) / fd or 0 elseif tau < cyc - fd then return 0 end return fd > 0 and (tau - (cyc - fd)) / fd or 1 end local function shown(inst) if not gui.Enabled then return false end local p = inst while p and p ~= gui do if p:IsA("GuiObject") and not p.Visible then return false end p = p.Parent end return true end local function inside(inst, pos) local ap, as = inst.AbsolutePosition, inst.AbsoluteSize return pos.X >= ap.X and pos.X <= ap.X + as.X and pos.Y >= ap.Y and pos.Y <= ap.Y + as.Y end -- sichtbar im Scroll-Fenster / in beschneidenden Eltern? local function clipsOk(inst, pos) local p = inst.Parent while p and p ~= gui do if p:IsA("GuiObject") and (p.ClipsDescendants or p:IsA("CanvasGroup")) and not inside(p, pos) then return false end p = p.Parent end return true end local pointerDown = false local touchPos = nil UserInputService.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 then pointerDown = true elseif input.UserInputType == Enum.UserInputType.Touch then pointerDown = true touchPos = Vector2.new(input.Position.X, input.Position.Y) + GuiService:GetGuiInset() end end) UserInputService.InputChanged:Connect(function(input) if input.UserInputType == Enum.UserInputType.Touch and pointerDown then touchPos = Vector2.new(input.Position.X, input.Position.Y) + GuiService:GetGuiInset() end end) UserInputService.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then pointerDown = false touchPos = nil end end) local function applyAlpha(e, a) local i, b = e.inst, e.alphaBase if b.grp then i.GroupTransparency = 1 - (1 - b.grp) * a elseif b.img then i.ImageTransparency = 1 - (1 - b.img) * a elseif b.txt then i.TextTransparency = 1 - (1 - b.txt) * a for _, c in ipairs(i:GetChildren()) do if c:IsA("UIStroke") then c.Transparency = 1 - a end end end if b.bg < 1 then i.BackgroundTransparency = 1 - (1 - b.bg) * a end end local function applyDim(e, d) for _, rec in ipairs(e.dim) do local o = rec[3] rec[1][rec[2]] = Color3.new(o.R * d, o.G * d, o.B * d) end end local t0 = os.clock() local function step(e, t, dt) local inst, tags, p, s = e.inst, e.tags, e.p, e.s local hv, pr = e.hover, e.press local dx, dy, rot, sc, sx, sy, alpha, dim = 0, 0, 0, 1, 1, 1, 1, 1 local usePos, useRot, useSize = false, false, false if tags.button then local q = p.button s.dim = ease(s.dim or 1, pr and q.PressDim or hv and q.HoverDim or 1, dt, 0.04) dim = s.dim end if tags.smooth then local q = p.smooth s.sh = ease(s.sh or 0, hv and 1 or 0, dt, q.Time / 3) s.sp = ease(s.sp or 0, pr and 1 or 0, dt, q.Time / 3) sc *= (1 + (q.Hover - 1) * s.sh) * (1 + (q.Press - 1) * s.sp) end if tags.up then local q = p.up s.up = ease(s.up or 0, hv and 1 or 0, dt, q.Time / 3) dy -= q.Distance * s.up usePos = true end if tags.turn then local q = p.turn s.tu = ease(s.tu or 0, hv and 1 or 0, dt, q.Time / 3) rot += q.Angle * s.tu useRot = true end if tags.lean then local q = p.lean s.le = ease(s.le or 0, (e.host and e.host.hover) and 1 or 0, dt, q.Time / 3) rot += q.Angle * s.le useRot = true end if tags.squish then local q = p.squish local v = spring(s, "sq", pr and 1 or 0, dt, 1.4 / math.max(0.1, q.Time), pr and 0.9 or 0.3) sx *= 1 + q.Amount / 100 * v sy *= 1 - q.Amount / 100 * v useSize = true end if tags.magnet then local q = p.magnet local tx, ty = 0, 0 local ptr = e.ptr if ptr then local ap, as = inst.AbsolutePosition, inst.AbsoluteSize local k = (s.sc or 1) local cx = ap.X + as.X / 2 - (s.mx or 0) * f local cy = ap.Y + as.Y / 2 - (s.my or 0) * f local ddx, ddy = (ptr.X - cx) / f, (ptr.Y - cy) / f local hw, hh = as.X / f / k / 2, as.Y / f / k / 2 if math.abs(ddx) < hw + q.Radius and math.abs(ddy) < hh + q.Radius then tx, ty = ddx * q.Pull, ddy * q.Pull end end dx += spring(s, "mx", tx, dt, 3, 0.35) dy += spring(s, "my", ty, dt, 3, 0.35) usePos = true end if tags.spin then rot += p.spin.Speed * t useRot = true end if tags.float then local q = p.float local d = q.Distance / 2 * math.sin(2 * math.pi * q.Speed * t) local a = math.rad(q.Angle) dx += d * math.sin(a) dy -= d * math.cos(a) usePos = true end if tags.pulse then local q = p.pulse local ph = (t * q.Speed) % 1 sc *= 1 + q.Strength / 100 * math.sin(math.pi * ph) ^ 2 end if tags.blink then alpha = blinkAlpha(t, p.blink) end if tags.jelly then local q = p.jelly local w = 2 * math.pi * q.Speed * t + e.phase rot += q.Angle * math.sin(w) sc *= 1 + 0.02 * math.sin(w * 1.3 + 1) useRot = true end if tags.wiggle then local q = p.wiggle local tau = t % q.Every if tau < 1.2 then rot += q.Angle * math.exp(-4 * tau) * math.sin(2 * math.pi * 4 * tau) end useRot = true end if tags.shake then local q = p.shake local tau = t % q.Every if tau < q.Burst then local k = 1 - tau / q.Burst dx += q.Distance * k * math.sin(2 * math.pi * 20 * tau) dy += q.Distance * 0.4 * k * math.sin(2 * math.pi * 15 * tau + 1) end usePos = true end if tags.pop then local q = p.pop local tau = t - (s.popStart or 0) if tau < q.Time then sc *= q.Start + (1 - q.Start) * easeOutBack(math.clamp(tau / q.Time, 0, 1)) end end if tags.rays then local q = p.rays rot += q.Speed * t sc *= 1 + q.Depth / 100 * (0.5 - 0.5 * math.cos(math.pi * t)) useRot = true end if tags.drift then local tex = inst:FindFirstChild("Texture") local tx, ty = inst:GetAttribute("GH_TileX"), inst:GetAttribute("GH_TileY") if tex and tx and ty and tx > 0 and ty > 0 then local q = p.drift local a = math.rad(q.Angle) local w, h = inst.AbsoluteSize.X / f, inst.AbsoluteSize.Y / f local ux = t * q.Speed * math.sin(a) / math.max(tx * w, 1) local uy = -t * q.Speed * math.cos(a) / math.max(ty * h, 1) tex.Position = UDim2.fromScale((ux - math.floor(ux) - 1) * tx, (uy - math.floor(uy) - 1) * ty) end end if tags.shiny or tags.glint then local par = inst.Parent if par and par:IsA("GuiObject") then local P = par.AbsoluteSize.X / f local w, h = inst.AbsoluteSize.X / f, inst.AbsoluteSize.Y / f local r = math.rad(e.baseRot) local wA = math.abs(math.cos(r)) * w + math.abs(math.sin(r)) * h local x0, D = -wA / 2 - 2, P + wA + 4 local cx = e.basePos.X.Scale * P + e.basePos.X.Offset / f local x if tags.shiny then local q = p.shiny local v = D / math.max(0.05, q.Time) local per = D + v * q.Pause x = x0 + (cx - x0 + v * t) % per if x > x0 + D then x = x0 + D end else local q = p.glint local hov = e.host and e.host.hover if hov and not s.gHover then s.gStart = t end s.gHover = hov local tau = t - (s.gStart or -1e9) x = (tau >= 0 and tau < q.Time) and (x0 + D * tau / q.Time) or x0 end dx += x - cx usePos = true end end if usePos then local par = inst.Parent local ps = par and par:IsA("GuiObject") and par.AbsoluteSize or Vector2.new(1, 1) inst.Position = e.basePos + UDim2.fromScale(dx * f / math.max(ps.X, 1), dy * f / math.max(ps.Y, 1)) end if useRot then inst.Rotation = e.baseRot + rot end if e.scale then e.scale.Scale = sc s.sc = sc end if useSize then local b = e.baseSize inst.Size = UDim2.new(b.X.Scale * sx, b.X.Offset * sx, b.Y.Scale * sy, b.Y.Offset * sy) end if e.alphaBase then applyAlpha(e, alpha) end if e.dim and math.abs((s.lastDim or 1) - dim) > 0.002 then applyDim(e, dim) s.lastDim = dim end end RunService.RenderStepped:Connect(function(dt) local t = os.clock() - t0 dt = math.min(dt, 0.1) local mouse = UserInputService.MouseEnabled and UserInputService:GetMouseLocation() or nil local ptr = mouse or touchPos for _, e in ipairs(entries) do local vis = shown(e.inst) local hv = false if vis and ptr then hv = inside(e.inst, ptr) and clipsOk(e.inst, ptr) end e.hover = hv e.press = hv and pointerDown e.ptr = vis and ptr or nil -- _pop spielt bei jedem Erscheinen if vis and not e.wasShown then e.s.popStart = t end e.wasShown = vis e.visible = vis end for _, e in ipairs(entries) do if not e.passive and e.visible then step(e, t, dt) end end end)