Custom Apps
Write your own resource, register an app, and it appears on the headset’s home screen. Opening it loads your page into a window. RoadVR is not modified in the process, so an update overwrites nothing of yours.Working examples
Two complete, running app resources ship in
examples/:
roadvr_exampleapp, which reads the game through its own Lua, and
roadvr_mediaapp — the Media app itself, which needs almost no Lua and
does everything through the SDK instead.Why a page and not a component
RoadVR’s interface is one compiled Vue bundle. Your resource cannot add a component to a build that shipped before your resource existed — so your app is loaded as a page in a frame instead. That is the better deal anyway. Inside the frame you use whatever you like, plain HTML or a framework, and a crash in your app cannot take the headset down with it.The three exports
registerRoadVrApp
string
required
Unique across the whole server. Prefix it with your resource name.
string
required
A file in your resource, e.g.
ui/index.html. The full address —
https://cfx-nui-<your-resource>/<file> — is built for you, so it cannot
point somewhere else by accident. Put the file in your own files {} block.string
Shown under the icon. Defaults to the id.
table
default:"{ w = 1.2, h = 0.75 }"
Window size in metres. Not pixels — see Writing the
page.
Keep it off the home screen and open it yourself with
openRoadVrPanel(id).boolean
false when the id already belongs to another resource — nobody gets to
replace someone else’s window.string
Why it failed, when it did.
Two icons, if you want the setting to work
icon may be a pair instead of one file:
Give only one side of the pair and the other falls back to it, so half a pair
is still an icon under both settings rather than a gap under one.
sendToRoadVrApp
boolean
false while the window is closed. That is normal, not an error — your
resource has no idea whether the player has your app open.Building the resource
1
Declare your files
fxmanifest.lua
2
Register on start
client.lua
3
Start it
server.cfg
Talking to your own Lua
Nothing is needed from RoadVR for this. FiveM routeshttps://<your-resource>/<callback> by resource name, no matter which page
fires the request, so inside the frame a plain fetch reaches your own
RegisterNUICallback:
SendNUIMessage
would go to your own frame, and that one is never rendered. Use
sendToRoadVrApp instead.
Talking to RoadVR itself
Your own Lua tells you about your world. The SDK tells you about the headset: which theme is on, which language the player picked, how big your window is, whether it is focused — and, the one that matters most, that it is about to close.public/ belongs in it. There
is nothing to add to your own files {} block — the file is served by RoadVR,
so there is no copy in your resource that can go stale.
Full SDK reference
Every field, every event, every function
The events, briefly
Every
on() returns its counterpart:
Two things worth knowing about
watch('placement') tells you how far your window is standing from the
player’s head, in metres, four times a second and only while it changes. It is
what the Media app turns its volume down with as somebody walks away.
setActions() hangs up to three buttons in your window’s ornament, left of
the close button — the place for a control that has to stay reachable while
your page cannot offer one, over a video or a map.
The SDK is optional. A page that only wants pushes from its own Lua can keep
using a raw listener — everything RoadVR forwards carries
source: 'roadvr':Writing the page
The window renders at a fixed 1280×800 CSS pixels whatever its size in metres. Sizes look oversized in a browser on purpose; that is the same scale every built-in app uses.Keep the background transparent
Keep the background transparent
RoadVR’s glass sits behind your page. A background colour on
body shows
up as a rectangle inside the rounded window.Hide the cursor
Hide the cursor
The headset draws its own pointer, and the system cursor would appear next
to it.
Do not scroll
Do not scroll
Set
overflow: hidden and make your page fit its window. A scrollbar down
the side of a floating pane looks like a fault, and the wheel is taken
anyway — it moves the window closer and further away.Make sure nothing can be selected
Make sure nothing can be selected
RoadVR sets
user-select: none on its own panels, but that rule stops at
the frame boundary. Your page is its own document and starts from the
default, which is selectable — so dragging across your window paints it
blue while the headset draws its own pointer next to the smear.RoadVR cannot do this for you. Set it yourself:Take RoadVR's colours
Take RoadVR's colours
Point your own variables at RoadVR’s, with a fallback so the page still
opens in a browser:Then call
RoadVr.applyTheme() once and the page follows the player
switching theme, without knowing a single colour value.Lifecycle
Register once at resource start. There is no need to unregister on stop — RoadVR notices your resource going away, removes the app and closes its window. Registration survives the headset being taken off and put back on. The list is kept on the Lua side and pushed to the interface again on every boot, so a resource that started long before anyone put a headset on still shows up.Trying the examples
registerRoadVrApp call and nothing else — with
every runtime detail coming through the SDK instead. It is also the one to read
for watch('placement') and setActions(), both of which it uses for real.
roadvr_mediaapp registers under the id media, which is the id the built-in
Media app used. Starting it takes that place over rather than adding a second
icon, so openRoadVrPanel('media') keeps working either way.