> For the complete documentation index, see [llms.txt](https://striga.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://striga.gitbook.io/docs/striga-text-ui/3d-prompts.md).

# 3D Prompts

2D and 3D mode, and how to pin prompts in the world with Add3D.

The player picks one of two modes (`/textuisettings` or `/hud` → **Mode**). The server sets the default with `Config.Ui.Depth`.

| Mode          | What shows                                                                                                                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Flat (2D)** | Only the screen prompt. `Add3D` points are not drawn in the world: the nearest point in reach shows as the screen prompt                                                                   |
| **3D**        | Only the world. Nothing is drawn on the screen: `Add3D` points stay in place, and screen prompts (`Show`, `Core.showTextUI`, qb / ox / esx) are pinned **like graffiti** where they appear |

Script authors write the same code for both modes. The player decides how it shows. Works the same on QBCore, Qbox and ESX, no extra resource needed.

***

## Set the mode

{% tabs %}
{% tab title="Server default" %}
In `editable/config.lua`:

```lua
Config.Ui = {
    -- ...
    Depth = '2d',  -- '2d' flat (screen), '3d' world
}
```

{% endtab %}

{% tab title="Player" %}

* `/textuisettings` → **Mode**: Flat (2D) or 3D. With 3D the screen position setting is hidden (not used in 3D).
* With Striga Tokyo HUD the same choice is in the `/hud` menu, **Interaction prompt** block.
* The choice is saved on the player's PC. With `Config.Ui.Settings = false` everyone sees the config mode.
  {% endtab %}

{% tab title="From a script" %}

```lua
exports['striga-text-ui']:SetLayout({ depth = '3d' })
local s = exports['striga-text-ui']:GetSettings() -- s.depth = '2d' | '3d'
```

{% endtab %}
{% endtabs %}

***

## Config (`Config.World`)

```lua
Config.World = {
    Enabled = true,
    Distance = 6.0,
    ActiveDistance = 2.0,
    MaxVisible = 6,
    Occlusion = true,
    OcclusionTolerance = 0.3,
    SelfDistance = 3.0,
    SelfOffset = vector3(0.35, 0.6, 0.3),
}
```

| Option               | Default                   | What it does                                                       |
| -------------------- | ------------------------- | ------------------------------------------------------------------ |
| `Enabled`            | `true`                    | `false` = no 3D prompts at all                                     |
| `Distance`           | `6.0`                     | Within this range (m) the point shows only its key                 |
| `ActiveDistance`     | `2.0`                     | The **nearest** point within this range (m) shows the whole prompt |
| `MaxVisible`         | `6`                       | Most 3D prompts on screen at once                                  |
| `Occlusion`          | `true`                    | A prompt behind a wall or an object is hidden                      |
| `OcclusionTolerance` | `0.3`                     | A wall hit this close (m) to the point still counts as seen        |
| `SelfDistance`       | `3.0`                     | 3D mode: a pinned screen prompt is hidden this far (m) away        |
| `SelfOffset`         | `vector3(0.35, 0.6, 0.3)` | 3D mode: where a screen prompt is pinned (right, forward, up)      |

`Distance` and `ActiveDistance` are defaults. Every point can set its own.

***

## How 3D points look

* Inside `distance` a point shows **only its key** (a small square when it has no key).
* The **nearest** point inside `activeDistance` shows the **whole prompt** (key, text and icon). One point is active at a time.
* The prompt gets smaller with the camera distance and uses the player's look settings (look, key look, color, opacity, size, blur) and the HUD color.
* A point behind a wall or an object (seen from the camera) is hidden and can not be active. Glass, vehicles and peds do not hide it.
* Hidden while the pause menu is open.

In **Flat (2D)** mode points are not drawn in the world: the nearest point in reach shows as the screen prompt while no script prompt is open.

***

## 3D exports (client)

| Export                | Returns   | What it does                                                 |
| --------------------- | --------- | ------------------------------------------------------------ |
| `Add3D(id, data)`     | `ok, err` | Adds a point. The same id from the same resource replaces it |
| `Update3D(id, patch)` | `ok, err` | Changes only the given fields (text, position, color...)     |
| `Remove3D(id)`        | `boolean` | Removes the point                                            |
| `Clear3D()`           | -         | Removes every point of your resource                         |
| `Has3D(id)`           | `boolean` | Whether the point exists                                     |

`id`: 1 to 64 characters, letters, digits and `_ - . :`. Ids are per resource, so two scripts can use the same id. Your points are removed when your resource stops. At most 500 points in total.

### Point fields

| Field            | Type                      | Default                       | What it does                                                              |
| ---------------- | ------------------------- | ----------------------------- | ------------------------------------------------------------------------- |
| `coords`         | `vector3` / `{ x, y, z }` | -                             | Position. `coords` or `entity` is required                                |
| `entity`         | entity handle             | -                             | The point moves with the entity. Hidden while it does not exist           |
| `offset`         | `vector3`                 | `vec3(0, 0, 1.0)`             | Only with `entity`: from its origin (right, forward, up)                  |
| `text`           | string                    | -                             | Required, max 256 bytes. A leading `[E]` becomes the key                  |
| `key`            | string                    | -                             | `E`, `G`, `F1`, `LMB`... (max 8)                                          |
| `style`          | string                    | `'info'`                      | `info` (theme color), `success`, `warning`, `error`                       |
| `icon`           | string                    | -                             | Icon name: `door-open`, `car`, `hand`, `money-bill`, `gas-pump`, `box`... |
| `iconColor`      | string                    | type color                    | `#rrggbb`                                                                 |
| `distance`       | number                    | `Config.World.Distance`       | 0.5 to 50 m                                                               |
| `activeDistance` | number                    | `Config.World.ActiveDistance` | 0.5 to 50 m, never more than `distance`                                   |
| `occlusion`      | boolean                   | `true`                        | `false` = visible through walls                                           |

### Examples

{% tabs %}
{% tab title="Fixed point" %}

```lua
local ui = exports['striga-text-ui']

ui:Add3D('atm_1', {
    coords = vec3(147.5, -1035.8, 29.3),
    text = 'Use the ATM', key = 'E', icon = 'money-bill',
    distance = 4.0, activeDistance = 1.5,
})
```

{% endtab %}

{% tab title="On an entity" %}

```lua
local ui = exports['striga-text-ui']

-- Vehicle trunk: moves with the vehicle (0 right, -2.5 back, 0.5 up)
ui:Add3D('trunk_' .. plate, { entity = vehicle, offset = vec3(0.0, -2.5, 0.5), text = 'Open the trunk', key = 'G', icon = 'box' })

-- Above an NPC's head
ui:Add3D('clerk', { entity = ped, offset = vec3(0.0, 0.0, 1.1), text = 'Talk to the clerk', key = 'E', icon = 'hand' })
```

{% endtab %}

{% tab title="Update / remove" %}

```lua
local ui = exports['striga-text-ui']

-- State changed: only text and color
ui:Update3D('bank_door', { text = 'The door is locked', style = 'error', icon = 'lock' })

-- Moving point: new position
ui:Update3D('atm_1', { coords = newCoords })

-- Visible through walls (e.g. a mission marker)
ui:Add3D('mission', { coords = target, text = 'Drop the package', key = 'E', occlusion = false, distance = 25.0 })

ui:Remove3D('trunk_' .. plate)
ui:Clear3D()
local exists = ui:Has3D('clerk')
```

{% endtab %}
{% endtabs %}

***

## Read the key in your script

{% hint style="warning" %}
Text UI **only shows** the prompt. Your script decides what a key press does.
{% endhint %}

The usual pattern:

```lua
local DOOR = vector3(150.2, -1040.1, 29.4)
local REACH = 2.0 -- keep it equal to activeDistance

CreateThread(function()
    exports['striga-text-ui']:Add3D('bank_door', {
        coords = DOOR, text = 'Open the door', key = 'E', icon = 'door-open', activeDistance = REACH,
    })
    while true do
        local dist = #(GetEntityCoords(PlayerPedId()) - DOOR)
        if dist <= REACH then
            if IsControlJustReleased(0, 38) then -- 38 = E
                TriggerServerEvent('mybank:openDoor')
            end
            Wait(0)
        else
            -- far away: do not check every frame
            Wait(dist > 20.0 and 1000 or 250)
        end
    end
end)
```

Pressing the key fills the prompt with the main color and fades it. It shows again once the player stays in reach and closes the menu (or leaves its reach and comes back). Same in both modes.

Common control ids: `38` E, `47` G, `74` H, `23` F, `246` Y, `311` K.

With several points close together, the prompt goes to the nearest one. Pick the nearest in your script too.

***

## 3D mode: screen prompts like graffiti

In 3D mode a normal prompt is not drawn on the screen. When a script shows it, it is **pinned in the world in front of the player** (`SelfOffset`, default 0.35 m right, 0.6 m forward, 0.3 m up) and stays there:

* **Its key** fills the whole prompt with the main color (theme / HUD color), then it fades out.
* **Walking away** `SelfDistance` (default 3 m) hides it.
* **Otherwise it stays** in place while the player is near, hidden while behind a wall.
* **It comes back** at the same spot when the player is still there (within 3 m of the pinned spot) and no menu is open (NUI focus or pause menu, at least 1 s later).
* **A new text** while it is open shows in the same place. A new spot is picked on the first show after it closed.
* **The script's `Hide`** closes it for good, in both modes.
* **What the key does** is still up to the script. Text UI only plays the animation and brings the prompt back.

Keys that play the animation: `E G H F Y K X Z R Q C B M ENTER SPACE LMB RMB F1 F2 F3 F5 F6 F7`. A prompt without a key has no animation.

Scripts need no change: `Core.showTextUI`, `exports['striga-text-ui']:Show`, qb `DrawText`, ox `lib.showTextUI` and `ESX.TextUI` work as they are.

{% hint style="info" %}
The key animation works the same in Flat (2D) mode: the prompt comes back when the player is within 2 m of where the key was pressed and no menu is open.
{% endhint %}

***

## Placing points

* **Height:** about **1.0 m above** the ground is eye level. Door handle about 1.0 m, counter about 1.1 m.
* **Walls and doors:** put the point about **0.2 m in front of** the surface, not inside it. A point on the surface can show through a thin wall to someone on the other side. A point inside the surface can be hidden by its own wall.
* **Entities:** measured from the entity origin. Vehicle back `-2.0` / `-2.5`, front `+2.0`. Above a ped's head `vec3(0, 0, 1.0)` to `1.1`.
* **Getting coordinates:** `print(GetEntityCoords(PlayerPedId()))` in F8, or the txAdmin / vMenu coordinates display.

***

## Performance

* No points: one light check per second.
* Points exist: a range scan every 250 ms. It only looks at points within about 100 m (a 50 m grid), so hundreds of far points cost nothing.
* A point in range: its screen position is sent to the UI **only when it changed**. The wall check is an async ray test that does not stall the game, at most one every 120 ms per visible point.
* Adding hundreds of points once with `Add3D` is fine. Do **not** call `Add3D` / `Update3D` every frame: use `entity` for moving points.

***

## Test it

Set `Config.TestCommand = 'textuitest'` and type `/textuitest` in game. The screen types show for 3 s each and a **15 s 3D prompt is placed 2 m in front of you**. Walk behind a wall to see it hide, walk away to see only the key.

{% content-ref url="/pages/J7UhcES1kZDSYVTdjdu0" %}
[Exports & Events](/docs/striga-text-ui/exports-and-events.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://striga.gitbook.io/docs/striga-text-ui/3d-prompts.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
