Quick start
Install Asobi, run the engine, ship a tiny game, and connect a test client. About 15 minutes. Each step is shown in both Lua and Erlang — pick whichever your team writes.
Which should I use? Lua is the fastest path to shipping (hot reload, no rebar3, smaller mental model). Erlang gives you full behaviour-level control and better performance on CPU-heavy loops. You can mix: a mostly-Lua game can drop into Erlang for one hot module.
Prerequisites: Erlang/OTP 28+, rebar3, Docker (for Postgres), and a terminal.
1. Run the engine
Start Postgres, then the Asobi engine. Same one-liner regardless of which language you write your game in — the engine loads both.
docker run -d --name asobi-postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 postgres:17
docker run --rm -it --name asobi \
-p 8080:8080 \
-e ASOBI_DB_HOST=host.docker.internal \
-e ERLANG_COOKIE=$(openssl rand -hex 32) \
ghcr.io/widgrensit/asobi_lua:latestYou should see a Nova application started log line. Port 8080 is the WebSocket endpoint for clients; the HTTP API lives on the same port.
2. Write the game
We'll build a click counter: every player who sends a click input increments a shared counter, broadcast to everyone.
Option A — Lua
Create game/hello.lua:
-- game/hello.lua
local game = {}
function game.init(config)
return { hits = 0 }
end
function game.join(player_id, state)
game.send(player_id, { kind = "welcome", msg = "hi " .. player_id })
return state
end
function game.leave(_player_id, state) return state end
function game.handle_input(_player_id, input, state)
if input.action == "click" then
state.hits = state.hits + 1
game.broadcast("update", { hits = state.hits })
end
return state
end
function game.tick(state) return state end
function game.get_state(_player_id, state) return { hits = state.hits } end
return gameOption B — Erlang
Create src/hello_game.erl in a rebar3 project that depends on asobi:
-module(hello_game).
-behaviour(asobi_match).
-export([init/1, join/2, leave/2, handle_input/3, tick/1, get_state/2]).
init(_Config) ->
{ok, #{hits => 0}}.
join(PlayerId, State) ->
asobi_match:send(PlayerId, #{kind => <<"welcome">>, msg => <<"hi ", PlayerId/binary>>}),
{ok, State}.
leave(_PlayerId, State) ->
{ok, State}.
handle_input(_PlayerId, #{action := <<"click">>}, #{hits := H} = State) ->
NewState = State#{hits := H + 1},
asobi_match:broadcast(<<"update">>, #{hits => H + 1}),
{ok, NewState};
handle_input(_PlayerId, _Input, State) ->
{ok, State}.
tick(State) -> {ok, State}.
get_state(_PlayerId, #{hits := H}) -> #{hits => H}.Both versions implement the same asobi_match contract. The Lua runtime translates each callback into the Erlang equivalent at the edge — there's no semantic difference.
3. Deploy the game
Option A — Lua
Install the asobi CLI once, then push the bundle to the engine:
go install github.com/widgrensit/asobi-cli/cmd/asobi@latest
asobi config set url http://localhost:8080
asobi config set api_key dev
asobi deploy ./gameThe engine hot-loads your Lua. No restart, no dropped connections. You'll see "Deployed 1 script successfully".
Option B — Erlang
Erlang game modules are hot-reloaded by nova/ rebar3. From your project root:
rebar3 compile
rebar3 nova reload # pushes new beam files to the running nodeIn-flight matches finish on the old module version; new matches bind the new one. Same guarantee as the Lua path.
4. Connect a client
Any WebSocket client works. Quick test with wscat:
npm install -g wscat
wscat -c ws://localhost:8080/ws
> {"type":"session.connect","payload":{"token":"dev-token"}}
> {"type":"match.create","payload":{"mode":"hello"}}
> {"type":"match.input","payload":{"action":"click"}}You'll see {"type":"match.state","payload":{"hits":1}} — every click increments the counter.
For a real client, use an SDK: Defold, Unity, Godot, Dart/Flutter.
5. Iterate with hot reload
Edit the game file, re-deploy, watch changes take effect without disconnecting anyone. This is the BEAM's killer feature and Asobi's biggest differentiator — any non-BEAM backend will drop connections on deploy.
That's it. You have a live Asobi server running a bilingual-capable game with hot-reload deploys.
Where next?
- Core concepts — matches, worlds, zones, voting, phases — each with Lua + Erlang snippets.
- Lua API reference — every
game.*function. - Erlang API reference — the behaviours and modules that power it all.
- Self-host — deploy Asobi to your own infrastructure.