Defold client SDK for the Asobi game backend.
Add the SDK and the WebSocket extension to your game.project:
[project]
dependencies#0 = https://github.com/widgrensit/asobi-defold/archive/refs/tags/v1.2.1.zip
dependencies#1 = https://github.com/defold/extension-websocket/archive/refs/tags/4.2.2.zip
Then Project → Fetch Libraries in the Defold editor. Pin to a tag — main is unstable. See releases for available versions.
The SDK talks to an Asobi server. The fastest way to get one is the canonical SDK demo backend:
git clone https://github.com/widgrensit/sdk_demo_backend
cd sdk_demo_backend && docker compose up -dThat serves at http://localhost:8084 (HTTP + WebSocket on /ws) with a 2-player demo mode. For the full reference game (arena shooter, boons, modifiers, bots) see asobi_arena_lua.
The SDK supports both world-mode (persistent shared rooms with zoned interest management) and match-mode (transient matchmade games). Pick the one that fits your game.
Defold-specific: register WebSocket callbacks from a
.scriptinmain.collection(a script that lives for the whole app). Don't register them from agui_scriptor any collection that gets unloaded — Defold invalidates the WS callback when its owning script is gone.
The simplest multiplayer pattern. One persistent world, both players walk around, both see each other.
local asobi = require("asobi.client")
local client
function init(self)
client = asobi.create("localhost", 8084)
client.auth.register(client, "player_" .. tostring(math.random(1, 1e9)),
"pass1234", nil, function(data, err)
if err then print("register failed: " .. tostring(err.error)) return end
client.realtime:on("entity_added", function(id, state)
if id == client.realtime.local_player_id then return end
-- factory.create("#ghost_factory", vmath.vector3(state.x, state.y, 0))
end)
client.realtime:on("entity_updated", function(id, state, changed)
if id == client.realtime.local_player_id then return end
-- go.set_position(vmath.vector3(state.x, state.y, 0), ghosts[id])
end)
client.realtime:on("entity_removed", function(id)
-- go.delete(ghosts[id])
end)
-- connect() authenticates asynchronously; wait for "connected" before
-- sending game messages, or they race ahead of the session.
client.realtime:on("connected", function()
client.realtime:find_or_create_world("walkers", function(payload, err)
if err then print("join failed: " .. tostring(err.error)) return end
client.realtime:send_world_input({kind = "move", x = 500, y = 200})
end)
end)
client.realtime:connect()
end)
endA complete runnable version is in example/multiplayer.lua.
Worlds require a backend with a world mode. The
sdk_demo_backenddocker quickstart only ships a match mode, so run the Matchmaking example below against it; Worlds need a world-mode backend (e.g.asobi_arena_lua).
local asobi = require("asobi.client")
local client
function init(self)
client = asobi.create("localhost", 8084)
client.auth.register(client, "player_" .. tostring(math.random(1, 1e9)),
"pass1234", nil, function(data, err)
if err then print("register failed: " .. tostring(err.error)) return end
-- The matchmaker places you into a match and pushes match.matched. It
-- auto-places you, so there is no join step - match.state starts flowing.
client.realtime:on("match_matched", function(payload)
print("matched into " .. tostring(payload.match_id))
end)
client.realtime:on("match_state", function(payload)
print("elapsed_ms: " .. tostring(payload.elapsed_ms))
end)
-- Wait for "connected" before queueing, or the request races auth.
client.realtime:on("connected", function()
client.realtime:add_to_matchmaker("demo")
end)
client.realtime:connect()
end)
endSee example/example.lua for the matchmaker REST + realtime flow.
Sign a player in with no username or password. You supply a stable
device_id and a device_secret (base64 of >=32 CSPRNG bytes, generated and
stored by your game — the SDK just passes it through). The same pair resumes
the same guest on later launches.
local asobi = require("asobi.client")
local client = asobi.create("localhost", 8084)
client.auth.guest(client, device_id, device_secret, function(data, err)
if err then print("guest sign-in failed: " .. tostring(err.error)) return end
-- data.created is true on first sign-in, absent on resume.
print("signed in as guest " .. tostring(data.player_id))
end)Later, convert the guest into a full account (keeps the same player_id).
The call is authenticated with the guest's current access token, so run it
after a successful guest(...):
client.auth.upgrade_guest(client, "chosen_name", "pass1234", function(data, err)
if err then print("upgrade failed: " .. tostring(err.error)) return end
-- Tokens are rotated to the claimed account automatically.
print("upgraded to " .. tostring(data.username))
end)The SDK maintains a managed registry of all entities in your current world or match and applies the server's partial diffs for you. Game code listens to high-level callbacks instead of merging diffs by hand:
client.realtime:on("entity_added", function(id, state)
-- A new player or NPC joined; `state` is the full initial state.
end)
client.realtime:on("entity_updated", function(id, state, changed)
-- An entity moved or changed. `state` is the FULL merged state
-- (never partial); `changed` lists which fields the server diffed.
end)
client.realtime:on("entity_removed", function(id)
-- Despawn the ghost.
end)
-- Iterate or query:
for id, state in pairs(client.realtime.entities) do
if id ~= client.realtime.local_player_id then
-- render ghost
end
endThe SDK listens to both world.tick and match.state server frames internally, so it works the same in world-mode and match-mode games. You don't need to register on on_world_tick / on_match_state yourself unless you want the raw frame.
See example/multiplayer.lua for a runnable starter.
- Auth - Register, login, guest (anonymous), guest upgrade, token refresh
- Players - Profiles, updates
- Worlds - List, create, find-or-create, join, leave, input, entity sync
- Matchmaker - Queue, status, cancel
- Matches - List, details
- Leaderboards - Top scores, around player, submit
- Economy - Wallets, store, purchases
- Inventory - Items, consume
- Social - Friends, groups, chat history
- Tournaments - List, join
- Notifications - List, read, delete
- Storage - Cloud saves, generic key-value
- Realtime - WebSocket for worlds, matches, chat, presence, matchmaking
See the WebSocket protocol guide for the full event surface.
Apache-2.0