Custom Games
A game is the second shape a resource can take inside the headset. Where a custom app is a window you fill, a game is a session: RoadVR provides the launcher entry, the lobby, friend invitations, ready checks, reconnect grace and the immersive full-view host. Your resource provides an HTML game and the rules that decide what a move means. RoadVR is not modified in the process. You register from your own server script; there is no RoadVR source edit and no custom RoadVR client script.Working example
Target Garden ships in
examples/roadvr_examplegame — a cooperative
Three.js game for up to four players. Both players see the same active target
and the same score, and the server awards every point.This is Game SDK v1. It runs on a single FiveM server and covers games
that exchange discrete actions and one small shared state. A distribution
catalog, cross-server sessions, persistent scores and shared world-space
rendering are not part of this version.
What RoadVR does and what you do
RoadVR provides
The launcher tile, the lobby with its six-letter code, invitations to online
friends, ready checks, host handover, the away/reconnect grace, the
immersive view with its pause and exit controls, and the Character Studio
that gives every player a body.
You provide
An HTML page that renders the game, and two server-side handlers —
onStart and onAction — that hold the rules. That is the whole contract.Try the example first
From the RoadVR source checkout, with the UI dependencies already installed:roadvr_examplegame folder beside
roadvr in your server resources.
server.cfg
Register from the server
registerRoadVrGame is a server export — unlike apps, widgets and markers,
which are registered client-side. The rules run on the server because the server
is the only place a rule cannot be edited by the player it applies to.
server.lua
registerRoadVrGame
string
required
Unique, 2–64 characters. A lowercase letter first, then lowercase letters,
digits,
_ or -. Prefix it with your resource name. Built-in app ids are
reserved.string
required
The launcher title, 1–80 bytes.
number
required
Must be
1.string
required
A path inside your own resource, up to 160 bytes. Remote URLs and
.. are
rejected.string
required
A path inside your own resource, same rules as
page.number
default:"1"
1 to 16. Cannot exceed
maxPlayers.number
default:"4"
1 to 16.
function
required
onStart(room) — returns { state = table, finished = boolean? }. The
opening state of a round.function
required
onAction(room, player, action, payload) — returns a replacement
{ state = table, finished = boolean? }, or { reason = 'invalid' } to
reject the action outright.boolean
false when registration was refused.string
One of
already_registered, invalid, invalid_id, version,
invalid_path, invalid_name, invalid_players, invalid_handler.Ownership comes from the invoking resource. A second registration under the
same id returns
already_registered — including one from the same owner.
Stopping the owner removes its entries and closes its rooms, so register again
on restart, which is what the onResourceStart handler above is for.Declaring the files
fxmanifest.lua
dependencies { 'roadvr' } here. The registration
already guards on GetResourceState, so the game resource can start on a server
where RoadVR is not running instead of refusing to start at all.
Rules live on the server
RoadVR derives the acting player from the server callback source and verifies membership, phase and revision before it calls your handler. It passes copies of the public room and player, so a handler cannot reach into RoadVR’s own state by mutating what it was given.What state may contain
Accepted state and action payloads hold plain Lua tables, strings, finite numbers and booleans.
Cycles, functions and metatables are rejected. These are content bounds rather
than an exact encoded-JSON guarantee.
An empty Lua table can arrive in JavaScript as an object rather than an array.
Normalize optional collections before you iterate them.
Installed resources are trusted server code. The iframe is a rendering
boundary, not a sandbox for untrusted Lua.
The page
Copypublic/sdk/game.js out of the built RoadVR resource into your game and
load it before your application. The source copy lives at
ui/public/sdk/game.js, with the TypeScript declarations beside it.
ui/index.html
app.js
Full Game SDK reference
Every method, every event, the room snapshot and the character module
Three things the page must get right
Do not send frames through send()
Do not send frames through send()
RoadVR serializes local requests and rate limits mutating server actions to
one per 100 ms per source, across all games. Render locally and submit
meaningful actions — a scored hit, a played card, a finished turn — never
animation frames or continuous positions.
Size to the iframe, not to the window
Size to the iframe, not to the window
The top 76 CSS pixels of the viewport belong to RoadVR’s controls; your
iframe receives the space that is left. Measure the iframe’s actual
dimensions and size the renderer to those.The host appends
?lang=<language> to your page — or adds it to the query
string you already have. Use it for your own strings, with English as the
fallback. Escape inside the iframe opens RoadVR’s pause sheet.Release everything on close
Release everything on close
Release Three.js geometries, materials, textures, animation frames, timers
and audio on
close and on pagehide. Suspend animation and audio on
paused and while the document is hidden.Closing and reopening the immersive view creates a new iframe. Restore
your visuals from game.state, never from module globals that a previous
view left behind.The lobby, in order
RoadVR’s shared lobby handles create, join by code, invite, accept and decline, ready, start and leave. Your page only deals with game actions, pause and leave.1
Create or join
A lobby carries a six-letter code. Invitations go to online RoadVR
friends only and reserve a seat for 60 seconds; a decline, an expiry or a
closed room releases it again.
2
Ready and start
Starting requires the host, the minimum player count, every player present
and ready, and no outstanding invitation or seat reservation.
3
Play
New participants cannot join after the start — but an away seat that is
still reserved can resume during play. Your
onAction is now the authority
on every move.4
Finish
At
finished, keep the results on screen. Leaving and creating a new lobby
is how a rematch happens; v1 does not restart a room in place.Leaving, pausing and coming back
config.lua
config.lua.
Every player has a body
Each shared lobby has a Customize character button that opens RoadVR’s Character Studio. The player saves a look there and returns to the lobby; the server loads that saved look on admission and hands it to you inroom.players[].appearance — including on the player passed to onAction.
public/sdk/character.js, character.d.ts and the whole
public/sdk/character/ directory into your game’s SDK folder — the example
build does it for you.
A saved look broadcasts a new room revision to everyone, including during
play. Update your meshes from the next
state event. Away seats keep the
new look when they return.Appearances are copies: game rules cannot change a player’s saved character by
modifying them. appearance is false when unavailable, and the model
factory accepts that and uses the default look. Account profile pictures stay
separate, in avatar.Checking your work
The example can be rebuilt on its own withnpm install and npm run build
inside its folder. It bundles Three.js and needs no CDN. Opening its page
outside RoadVR times out, because there is no SDK host to answer.
For a real two-player browser test from the source checkout:
http://127.0.0.1:5186/game-review.html?player=1 and the same address with
player=2. The fixture uses synthetic friends and runs the real Lua lobby,
registration and example rules through wasmoon. Walk the whole path: create →
invite → accept → ready both → start → enter both → hit → pause/resume → back to
lobby → leave. Add &lang=de for German. Customize character opens the
actual studio, and fixture saves stay in memory rather than touching the
database.
Related
Game SDK reference
The browser API in full
Custom Apps
A window on the home screen instead of a session
Custom Widgets
Your page on a wall
Discord Support
Questions about integrating