Quick start - Godot
Connect a Godot 4.x project to a running Asobi server in about five minutes. Don't have a server yet? Run the server quickstart first - it gives you a backend on localhost:8084 with a default match mode, which is what this page connects to.
1. Install the addon
Clone or copy asobi-godot into addons/asobi/, then enable it via Project → Project Settings → Plugins. The plugin registers an Asobi autoload for you, so you use Asobi directly from any script - you do not create a client node yourself.
2. Where this code lives
Put the code below on a script attached to a node in your main scene (or your own autoload). Asobi is driven by two Godot lifecycle callbacks:
_ready()- runs once when the node enters the tree. Authenticate, wire up signals, and connect here._process(delta)- runs every frame. Read input and send it here.
Realtime events arrive as Godot signals (match_matched, match_state, ...), each carrying a Dictionary payload. You connect them to your own handler functions.
3. The complete client
This is the whole flow - authenticate, connect, queue, join, receive state, send input - in one script. Each part is explained below.
extends Node
func _ready() -> void:
# Point the autoload at your server (matches the server quickstart).
Asobi.host = "localhost"
Asobi.port = 8084
# 1. Authenticate. await the result before doing anything else - the
# WebSocket connects with this session.
# A fresh username each run - registering the same one twice returns
# 409 username_taken (see below).
var resp := await Asobi.auth.register("player_%d" % randi(), "secret1234", "Player One")
if resp.has("error"):
push_error("auth failed: %s" % resp.error)
return
# 2. Wire up the signals you care about BEFORE connecting, so you don't
# miss the first events.
Asobi.realtime.match_matched.connect(_on_matched)
Asobi.realtime.match_state.connect(_on_state)
# 3. Open the WebSocket, then queue for a match. "default" is the mode
# your server's match.lua registers.
Asobi.realtime.connect_to_server()
Asobi.realtime.add_to_matchmaker("default")
func _on_matched(payload: Dictionary) -> void:
# The matchmaker found a match. You must join it before state flows.
Asobi.realtime.join_match(payload["match_id"])
func _on_state(payload: Dictionary) -> void:
# The server's authoritative tick. Update your game world from here.
var players: Dictionary = payload.get("players", {})
print("tick %s, %d players" % [payload.get("tick", 0), players.size()])
func _process(_delta: float) -> void:
# Send input every frame a key is held. send_match_input takes a
# Dictionary - no JSON encoding.
if Input.is_action_pressed("ui_right"):
Asobi.realtime.send_match_input({"action": "move", "x": 1, "y": 0})4. What each part does, and when
- Authenticate first (in _ready). The WebSocket authenticates with your session, so
awaittheregister(orlogin) call and checkresp.has("error")before you connect. Registering a username that already exists returns409 username_taken- a real game registers once and callsloginon return (this quickstart uses a fresh random name each run). Auth is rate-limited to 5/sec per IP; production builds should use a platform provider (see Auth & rate limiting). - Connect signals before connect_to_server (in _ready). Connect your handlers to
match_matchedandmatch_statefirst, then callconnect_to_server()andadd_to_matchmaker("default"). The mode string must match a mode your server registers. - Join on match_matched. Queuing gets you matched; you then have to
join_match(payload["match_id"]). Without the join,match_statenever arrives - you sit idle after matching. - Send input from _process. Poll
Inputand callsend_match_input(dict)once per frame. It is fire-and-forget: the nextmatch_statereflects the server's authoritative response.
What's next
- Full SDK reference
- asobi-godot-demo - a working arena demo (login/lobby/arena scenes, bots,
_processinput). - game.* Lua API - write the server-side gameplay your client connects to.
- Live-edit your game