Docs / Quick start

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:latest

You 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 game

Option 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 ./game

The 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 node

In-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?