Lua multiplayer SDK for Defold 1.10+.
Add as a project dependency in game.project:
[project]
dependencies = https://github.com/plot-ws/plot-defold/archive/main.zip
The plot/ directory becomes available as require("plot.plot").
local plot = require("plot.plot")
local client = plot.client({
app_key = "pl_pub_live_xxx",
player_id = sys.get_save_file("plot", "playerId") or "anon",
})
client:join({ room_code = "LOBBY1" }, function(err, room)
if err then print(err); return end
room:on("message", function(from, data) print(from, data) end)
room:on("join", function(pid) print("joined:", pid) end)
room:on("leave", function(pid) print("left:", pid) end)
room:send({ hello = "world" })
end)Smoothly render remote state by interpolating between server snapshots:
room:interpolate("players.*.position", "vec2", 100)
room:on("frame", function(interpolated, ts)
for path, value in pairs(interpolated) do
render_at(path, value)
end
end)
room:set_adaptive_smoothing({ enabled = true, gain = 1.0, max_extra_ms = 100 })Drive frames from your script's update(dt) by calling room:tick_frame(now)
once per frame (now is wall-clock ms, the same domain as the server's
state-* timestamps; omit it to use the SDK's own clock):
function update(self, dt)
self.room:tick_frame()
endSupported types: "number", "vec2", "vec3", "quat"; vector/quat values
are { x =, y =, z =[, w =] } tables. Paths support a single-level * wildcard.
Sample interpolated state at an arbitrary past server time — the rendering-side
analogue of the server's ctx.rewindTo (e.g. client-side hit detection). Pure
read: it never touches the frame loop or correction state.
-- at_server_ts is in the server time domain; convert a client time with
-- now - room:server_clock_offset().
local past = now - room:server_clock_offset() - 120 -- 120ms ago
-- One path: returns { x =, y = } for vec2, or nil when `past` is outside the
-- buffer's retained horizon.
local p1 = room:sample_at("players.p1.position", "vec2", past)
-- Wildcard: a table keyed by resolved path, or nil.
local all = room:sample_at("players.*.position", "vec2", past)
-- Bind once, read many paths at the same frozen instant:
local r = room:rewind_to(past)
local shooter = r:sample("players.p1.position", "vec2")
local target = r:sample("players.p2.position", "vec2")Apply local inputs immediately and reconcile against the server's
authoritative state. The engine cannot run your server handler, so you supply
a deterministic predict_fn(state, input, player) -> state:
room:attach_prediction(initial_state, function(state, input, player)
return reduce(state, input, player)
end)
room:predict("players.me.position", "vec2", 100)
room:on("predicted", function(state, ts, drift) render(state) end)
room:send_predicted({ move = "left" }) -- optimistic; carries _seq upstream
local pos = room:corrected_state_table()["players.me.position"]
local state = room:predicted_state()plot/protocol.lua is generated from packages/protocol/codegen/. Do
not hand-edit. SDK speaks X-Plot-Protocol: v1b.0 via the websocket
extension.
defold-ci.yml runs a luac -p syntax check and luacheck (lint) over
sdks/defold on push + PR.
This README is the quickstart. For concepts, guides, and the server handler reference, see the full docs: https://docs.plot.ws/sdks/defold
