3D Text UI

A lightweight, optimized standalone script for rendering 3D interactive text labels in the world. It features a dual-loop automatic culling system for high performance, proximity scaling, and support for "tap" and "hold" interactions with visual progress tracking.

⚙️ Data Structures

Before using the functions, understand the data tables used to configure an interaction point.

The Main Configuration Table

This table defines where the text is and how it behaves.

Property Type Default Description
coords vector3 Required The world coordinates where the text should appear.
options table Required A list of interaction options (see below).
distance float 2.5 The distance at which the UI switches to "Full" mode (interaction enabled).
maxDistance float 5.0 The max distance at which the text is visible at all.
scale float 1.0 Scaling factor for the UI element.
arc boolean false Visual flag passed to UI (optional style preference).

The Options Table

The options list contains the specific inputs available to the player. Note: Callbacks are now defined directly inside each option via the action property.

options = {
    { 
        label = "PICK UP",          -- The action text displayed
        key = 38,                   -- The FiveM Control Index (38 is E)
        keyName = "E",              -- (Optional) The visual key name. Falls back to Lib47.Keys if omitted.
        action = function()         -- The function triggered upon interaction
            print("Item picked up!")
        end,
        isVisible = function()      -- (Optional) Determines if the option shows up at all
            return true 
        end,
        canInteract = function()    -- (Optional) If false, the option is visible but disabled/grayed out
            return true 
        end
    },
    { 
        label = "INSPECT", 
        key = 74,                   -- 74 is H
        keyName = "H",
        hold = 3,                   -- (Optional) Seconds button must be held to trigger
        action = function()
            print("Inspected!")
        end
    }
}

🛠️ API Reference

This script exposes functions and assigns them to the global Lib47 table. It utilizes an automatic slow culling loop (checking within 30.0 units) and a fast interaction loop, meaning you no longer need to check distances manually in your own threads.

1. RegisterTextUi3d (Static Mode)

Best for static, permanent locations (e.g., Shops, Duty points, ATMs). You register it once, and the script's internal culling handles the rest.

  • Usage: Lib47.RegisterTextUi3d(data)
  • Returns: string (The Interaction ID, derived from coordinates).

2. ShowTextUi3d (Dynamic/Immediate Mode)

Best for dynamic entities (like dropping an item on the ground). This registers the point temporarily. If you walk away, it will eventually be culled out unless updated.

  • Usage: Lib47.ShowTextUi3d(data)
  • Returns: string (Interaction ID).

3. HideTextUi3d & RemoveTextUi3d

Hides or completely removes an interaction point.

  • Usage: Lib47.HideTextUi3d(id) or Lib47.RemoveTextUi3d(id)

📝 Code Examples

Example A: Static Location (Register Mode)

Use this for permanent locations. Run this code once (e.g., at resource start). You do not need a loop.

CreateThread(function()
    local position = vector3(373.59, 328.58, 103.68)

    -- Register the point once, the script handles distance and culling automatically
    Lib47.RegisterTextUi3d({
        coords = position,
        distance = 2.5,
        maxDistance = 5.0,
        options = {
            { 
                label = "PICK UP", 
                key = 38,
                keyName = "E",
                action = function()
                    print("Player picked up item") 
                end
            },
            { 
                label = "INSPECT", 
                key = 74,
                keyName = "H",
                hold = 3, -- 3 Second Hold
                action = function()
                    print("Player inspected item") 
                end
            }
        },
    })
end)

Example B: Dynamic State & Conditional Logic

Use the isVisible and canInteract functions to dynamically change how the text behaves based on the player's state (e.g., job, money, or items).

CreateThread(function()
    local atmCoords = vector3(100.0, 100.0, 20.0)

    Lib47.RegisterTextUi3d({
        coords = atmCoords,
        distance = 2.0,
        options = {
            { 
                label = "USE ATM", 
                key = 38, 
                keyName = "E",
                isVisible = function()
                    -- Only show if the player is not in a vehicle
                    return not IsPedInAnyVehicle(PlayerPedId(), false)
                end,
                canInteract = function()
                    -- Visible, but grayed out if the player is dead
                    return not IsEntityDead(PlayerPedId())
                end,
                action = function()
                    print("Accessing ATM...")
                end
            }
        }
    })
end)

Example C: Using Exports directly

If you do not use the Lib47 global wrapper, you can use the resource exports directly via the Interface wrapper.

exports['ak47_lib']:RegisterTextUi3d({
    coords = vector3(100.0, 100.0, 20.0),
    options = {
        { 
            label = "TEST", 
            key = 38,
            keyName = "E",
            action = function()
                print("Clicked")
            end
        }
    }
})