Custom Games

Adding a game to the UI

The UI lives in ui/app.js. Each game is an entry in the GAMES table:

JavaScript
GAMES.example = {
    title: 'My game', width: 24,
    build(g, o) {
        // g is the game context, o is the options sent from Lua
        const btn = h('button', { text: 'Click me', on: { click: () => g.finish(true) } });
        g.body.append(btn);
        g.hint('Press ', kbd('E'), ' or click');
        g.key(e => { if (e.key.toLowerCase() === 'e') g.finish(true); });
        g.timer(o.time);
    },
};

Use the context helpers instead of raw timers and listeners. They clean up when the game ends.

Helper Use
g.body The element you build the game in.
g.key(fn) Keydown handler. Key repeat is ignored. Pass true as the second argument to receive repeats.
g.on(target, type, fn) Event listener, removed at the end.
g.after(ms, fn) and g.every(ms, fn) Timers that stop when the game ends.
g.loop((dt, ts) => {}) Animation loop. dt is in seconds.
g.timer(seconds, onExpire) Shows the time bar. Fails the game on expiry, unless you pass your own handler.
g.stat(text) and g.hint(...nodes) Header status text and footer hint.
g.finish(success, delay) Ends the game and shows the result.

Adding the Lua export

Copy this into client/main.lua, and add a matching table in shared/config.lua:

Lua
exports('Example', function(a1, a2)
    if gameOpen then return false end
    local opts, cb
    if IsCallable(a1) then opts, cb = {}, a1
    elseif type(a1) == 'table' then opts, cb = a1, a2
    else opts, cb = { time = a1 }, a2 end
    local o = {}
    o.time = Num(opts.time, Default('ExampleDefaults', 'time', 15), 0, 60, true)
    return Launch('example', o, cb)
end)

The action name passed to Launch is the lowercase key in GAMES.

Recipes

Lock a door behind a pick

Lua
local function TryLockpick(door)
    if exports['sbminigames']:IsOpen() then return end
    exports['sbminigames']:Lockpick({ pins = 4, picks = 3, time = 40 }, function(success)
        if success then
            TriggerServerEvent('myresource:unlockDoor', door)
        else
            print('The pick snapped.')
        end
    end)
end

Run games one after another

Lua
CreateThread(function()
    if not exports['sbminigames']:Keypad({ length = 5 }) then return end
    if not exports['sbminigames']:Wires({ pairs = 4 }) then return end
    if not exports['sbminigames']:Scrambler({ length = 6 }) then return end
    print('Hacked')
end)

Harder with every attempt

Lua
local attempt = 0

local function Hack()
    attempt += 1
    exports['sbminigames']:Circle({
        difficulty = attempt > 2 and 'expert' or 'medium',
        circles    = math.min(attempt + 1, 6),
    }, function(success)
        if success then attempt = 0 end
    end)
end

Keep rewards on the server

A minigame result is decided on the player's machine, so a cheater can fake true. Never give a reward just because a client event says the player won. Check on the server that the player is near the target, has the required item, and hasn't triggered the reward recently. Keep the minigame as the player-facing part only.

Last updated 2026-11-08