SUNC Input Synthesis
SUNC provides a small family of functions for synthesising user input — pressing keys, clicking the mouse, moving the cursor — without the user actually doing it. They're how an external script can drive Roblox UI, automate repetitive actions, or build features that would otherwise require holding the mouse manually. This article covers each one with a working example.
Why these are different from RBXScriptSignal:Fire
You can fire UserInputService.InputBegan manually with firesignal — but that only notifies listeners you can find. The real input pipeline is wider than that: there's ContextActionService, custom event hooks, and the Roblox engine's own built-in input handling for camera and movement. Real synthetic input — the kind that's indistinguishable from a user's — has to enter at the OS or low-level input-system level.
The SUNC input functions do exactly that: they call into the host's input API as if the user had pressed the key or moved the mouse. Every consumer of that input sees it as real.
keypress and keyrelease
keypress(keycode: number): ()
keyrelease(keycode: number): ()void-- Press W for one second
keypress(0x57) -- VK_W on Windows
task.wait(1)
keyrelease(0x57)The argument is a virtual key code from the host operating system, not a Enum.KeyCode. On Windows that's a Win32 VK constant (0x57 = W, 0x20 = Space, 0x0D = Enter). Most executors that implement SUNC accept these codes uniformly.
Practical pattern — auto-hold a movement key:
local UserInputService = game:GetService("UserInputService")
local holdingW = false
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.F2 then
if holdingW then
keyrelease(0x57)
else
keypress(0x57)
end
holdingW = not holdingW
end
end)mouse1click, mouse2click
mouse1click(): ()
mouse2click(): ()void-- Single left-click at current cursor position
mouse1click()These are shorthand for "press, release." For finer control you can use mouse1press, mouse1release, and the same pair for right mouse. Same applies for the scroll wheel: many SUNC implementations expose mousescroll as well.
mousemoverel and mousemoveabs
mousemoverel(dx: number, dy: number): ()
mousemoveabs(x: number, y: number): ()void-- Slowly drag from the current spot to 100px to the right
for i = 1, 100 do
mousemoverel(1, 0)
task.wait(0.01)
end
-- Or, jump straight to a specific coordinate
mousemoveabs(640, 360) -- centre of a 1280x720 viewportThe relative form is what camera systems use — sending tiny deltas every frame to look around naturally. The absolute form is useful for clicking specific on-screen elements you've already located.
mousemoveabs uses Roblox viewport coordinates (where (0, 0) is the top-left of the game window, not the OS desktop). If you need OS-level coordinates instead, some executors expose mousemoveabsdesktop; check your executor's docs.A worked example: aim-assist style smoothing
A small but realistic example — when the user holds a key, smoothly nudge the cursor toward a target screen position. This is what real aim-assist features look like under the hood: a per-frame delta computation that shrinks each tick.
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local active = false
UserInputService.InputBegan:Connect(function(i) if i.KeyCode == Enum.KeyCode.E then active = true end end)
UserInputService.InputEnded:Connect(function(i) if i.KeyCode == Enum.KeyCode.E then active = false end end)
RunService.RenderStepped:Connect(function(dt)
if not active then return end
local target = findTargetScreenPos() -- your game-specific function
if not target then return end
local cursor = UserInputService:GetMouseLocation()
local dx = (target.X - cursor.X) * 5 * dt
local dy = (target.Y - cursor.Y) * 5 * dt
if math.abs(dx) >= 1 or math.abs(dy) >= 1 then
mousemoverel(dx, dy)
end
end)The math: every frame, compute the delta between cursor and target, scale by a small per-second factor timesdt, send the delta. The cursor approaches the target asymptotically without ever jumping — natural- looking from the outside.
Common key codes
A small reference table of the Windows virtual key codes you'll need most often:
local VK = {
A = 0x41, B = 0x42, C = 0x43, D = 0x44, E = 0x45, F = 0x46,
G = 0x47, H = 0x48, I = 0x49, J = 0x4A, K = 0x4B, L = 0x4C,
M = 0x4D, N = 0x4E, O = 0x4F, P = 0x50, Q = 0x51, R = 0x52,
S = 0x53, T = 0x54, U = 0x55, V = 0x56, W = 0x57, X = 0x58,
Y = 0x59, Z = 0x5A,
Space = 0x20, Enter = 0x0D, Shift = 0x10, Ctrl = 0x11, Alt = 0x12,
Tab = 0x09, Escape = 0x1B, Backspace = 0x08,
Left = 0x25, Up = 0x26, Right = 0x27, Down = 0x28,
F1 = 0x70, F2 = 0x71, F3 = 0x72, F4 = 0x73,
F5 = 0x74, F6 = 0x75, F7 = 0x76, F8 = 0x77,
}On macOS the underlying codes differ, but most executors translate the Windows codes for you so your scripts stay portable across platforms.
Caveats
- Server validation still applies. Synthetic input fires through the normal Roblox pipeline, including the server. A game that validates actions against impossible speeds or rates will still catch you.
- Focus matters. If the Roblox window isn't focused, the OS may drop the synthetic input. Real scripts confirm the window is in focus before firing.
- Rate limits. Firing
mousemoverel1000 times in one frame can overwhelm the input queue. Throttle to one per frame (RenderStepped) for smooth movement. - User experience. Synthetic input the user didn't expect feels terrible. Always tie automation to a clear, user- controlled trigger — a held key, a toggle, an explicit start command.
Wrap-up
Input synthesis is one of the smallest SUNC libraries but probably the one with the most visible effect on the end user. Four core functions (keypress, keyrelease, mouse1click, mousemoverel) cover most use cases. The real engineering is what you wire them into — when to fire, how often, what triggers them. That part stays tasteful.
Ather is the lead developer behind Atherhub. He's been writing Luau and Roblox tooling for the better part of a decade, with a focus on the messy interface between game-script internals and the platforms that host them. Have feedback on this article? Drop it in the Discord.