RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

GE Hook CatalogInput Bindings & Keybinding SystemDebugging Your ModData Persistence & SavingVehicle Engine Documentation MapVehicle Engine Tag IndexGE Documentation MapDocumentation Tag IndexPhone UI SystemAngular Overlay Pattern (Mod UI)

Reference

UI

Resources

BeamNG Game Engine Lua Cheat SheetGE Developer RecipesMCP Server Setup

// RLS.STUDIOS=true

Premium Mods for BeamNG.drive. Career systems, custom vehicles, and immersive gameplay experiences.

Index

HomeProjectsPatreon

Socials

DiscordPatreon (RLS)Patreon (Vehicles)

© 2026 RLS Studios. All rights reserved.

Modding since 2024

GuidesReference

Phone UI System

Phone overlay system - toggleable in-game phone with apps, minimap, time display, and state management.

Phone overlay system - toggleable in-game phone with apps, minimap, time display, and state management.


Overview

The phone is a GE-side overlay controlled by gameplay_phone. It opens/closes via togglePhone(), enforces a speed check (must be ≤5 mph unless in a cab), and communicates state to the CEF/UI layer through guihooks.trigger.

Extension path: lua/ge/extensions/gameplay/phone.lua Sub-modules: ui/phone/time.lua, ui/phone/minimap.lua


Phone API

FunctionSignatureDescription
togglePhonetogglePhone(reason?)Open/close the phone. Optional reason string shown if blocked.
isPhoneOpenisPhoneOpen() → boolReturns current open state.

Toggle Logic

-- From gameplay_phone
local function togglePhone(reason)
    if isPhoneOpen then
        isPhoneOpen = false
        guihooks.trigger('closePhone')
    else
        local playerSpeed = math.abs(be:getObjectVelocityXYZ(be:getPlayerVehicleID(0))) * speedUnit
        if playerSpeed > 5 then
            ui_message(reason or "You must be stationary to open the phone.", 3, "info", "info")
            return
        end
        isPhoneOpen = true
        if gameplay_taxi.isTaxiJobActive() then
            guihooks.trigger('ChangeState', {state = 'phone-taxi'})
        else
            guihooks.trigger('ChangeState', {state = 'phone-main'})
        end
    end
end

Auto-Close on Movement

The phone polls every 2.5 seconds. If speed exceeds 5 mph while open (and not in a cab), it auto-closes:

local function onUpdate(dt)
    updateTimer = updateTimer + dt
    if updateTimer > updateInterval then
        updateTimer = 0
        if isPhoneOpen then
            local playerSpeed = math.abs(be:getObjectVelocityXYZ(be:getPlayerVehicleID(0))) * speedUnit
            if playerSpeed > 5 then
                isPhoneOpen = false
                guihooks.trigger('closePhone')
            end
        end
    end
end

UI Events

EventDataDirectionPurpose
ChangeState{state = 'phone-main'}GE → UIOpen phone main screen
ChangeState{state = 'phone-taxi'}GE → UIOpen phone taxi mode
closePhonenilGE → UIClose phone overlay
phone_time_updatestring (e.g. "3:45 PM")GE → UIUpdate time display
PhoneMinimapData{nodes, terrainTiles}GE → UIInitial minimap data
PhoneMinimapUpdate{controlID, objects, ...}GE → UIPer-frame minimap positions

Time Display

Extension: ui/phone/time.lua

Formats the in-game time-of-day into 12-hour clock strings and pushes updates to the UI.

local function formatTime(time)
    local total_minutes = time * 1440          -- time is 0..1 (fraction of day)
    local hours = math.floor(total_minutes / 60)
    local minutes = math.floor(total_minutes % 60)
    local period = hours < 12 and "PM" or "AM"
    local twelve_hour = hours % 12
    twelve_hour = twelve_hour == 0 and 12 or twelve_hour
    return string.format("%d:%02d %s", twelve_hour, minutes, period)
end
FunctionDescription
onUpdate()Checks scenetree.tod.time, triggers phone_time_update on change
onExtensionLoaded()Sends initial time value
clearTime()Resets cached time (used on phone close)

Key detail: scenetree.tod.time is a float 0–1 representing fraction of a 24-hour day.


Phone Minimap

Extension: ui/phone/minimap.lua

Provides road-network data and per-frame vehicle positions to the phone's minimap view (separate from the HUD minimap app).

Data Flow

requestPhoneMap()     → PhoneMinimapData    (once, on load)
onGuiUpdate()         → PhoneMinimapUpdate  (every frame)

Road Network Data (PhoneMinimapData)

{
    nodes = {
        [nodeId] = {
            pos = {x, y},           -- 2D world position
            radius = number,
            links = {
                [targetNodeId] = {
                    drivability = 0..1,
                    oneWay = bool
                }
            }
        }
    },
    terrainTiles = {
        {
            size = {w, h},          -- world block size
            offset = {x, y},        -- terrain position
            file = "path/to/minimap.png"
        }
    }
}

Per-Frame Update (PhoneMinimapUpdate)

{
    controlID = vehicleId,
    isFreeCam = bool,
    camPosition = {x = num, y = num},
    camRotationZ = degrees,
    objects = {
        [vehId] = {
            posX = number,      -- note: negated X
            posY = number,
            rot = degrees,
            speed = number,
            isPlayer = bool
        }
    }
}

Lifecycle Hooks

HookBehavior
onExtensionLoadedPhone resets isPhoneOpen = false
onUIPlayStateChangedCloses phone on play state change
onUpdate(dt)Speed-check polling every 2.5s

Quick Reference

-- Toggle the phone
gameplay_phone.togglePhone()

-- Check if phone is open
if gameplay_phone.isPhoneOpen() then ... end

-- Get formatted in-game time
-- (internally: scenetree.tod.time → 12hr string)

-- Request minimap data refresh
ui_phone_minimap.requestPhoneMap()

-- UI listens for:
-- 'ChangeState' {state='phone-main'|'phone-taxi'}
-- 'closePhone'
-- 'phone_time_update' → time string
-- 'PhoneMinimapData'  → road network + terrain
-- 'PhoneMinimapUpdate' → vehicle positions each frame

Common Patterns

Custom Phone State

-- Open phone to a custom state
guihooks.trigger('ChangeState', {state = 'phone-myapp'})

Speed Gate Pattern

-- The phone uses mph internally (speedUnit = 2.2369362921)
local speedUnit = 2.2369362921
local speed = math.abs(be:getObjectVelocityXYZ(be:getPlayerVehicleID(0))) * speedUnit
if speed <= 5 then
    -- Safe to open phone
end

See Also

  • Apps System - HUD minimap app and custom app lifecycle
  • GUIHooks - UI communication bridge
  • Communication Guide - Cross-context patterns

Documentation Tag Index

Complete reference for all documentation tags across GE and VE contexts — use these tags to find relevant files via grep or IDE search.

Angular Overlay Pattern (Mod UI)

How to create a full-screen overlay UI (splash screen, popup, modal) from a mod that renders on top of everything in BeamNG's CEF UI.

On this page

OverviewPhone APIToggle LogicAuto-Close on MovementUI EventsTime DisplayPhone MinimapData FlowRoad Network Data (PhoneMinimapData)Per-Frame Update (PhoneMinimapUpdate)Lifecycle HooksQuick ReferenceCommon PatternsCustom Phone StateSpeed Gate PatternSee Also