Guide 2026-05-07

FiveM Raycasting & Targeting System Guide

TDYSKY

TDYSKY

Founder & Lead Developer at Agency Scripts

What is Raycasting in FiveM?

Raycasting is the technique of shooting an invisible line (ray) from one point in 3D space to another and checking what it hits along the way. In FiveM, raycasting is used for targeting systems, object interaction, line-of-sight checks, surface detection, and custom aim mechanics. The game engine provides several raycast natives that let you detect entities, surfaces, and world geometry with precision. Understanding raycasting unlocks advanced gameplay mechanics like custom targeting, smart placement systems, laser pointers, and context-sensitive interactions that respond to what the player is looking at.

Basic Raycasting with StartShapeTestRay

The simplest form of raycasting uses StartShapeTestRay which casts a ray between two coordinates and returns information about the first thing it hits. The result includes the hit status, the exact coordinates of the impact point, the surface normal vector, and the entity handle if an entity was hit. You must call GetShapeTestResult on the next frame to retrieve the results.

-- client/raycast_basic.lua
local function Raycast(origin, direction, maxDistance, flags, ignoreEntity)
    local destination = origin + direction * maxDistance

    local shapeTest = StartShapeTestRay(
        origin.x, origin.y, origin.z,
        destination.x, destination.y, destination.z,
        flags or -1,      -- flags: -1 = everything
        ignoreEntity or 0, -- entity to ignore
        0
    )

    local _, hit, endCoords, surfaceNormal, entityHit = GetShapeTestResult(shapeTest)

    return {
        hit = hit == 1,
        coords = endCoords,
        normal = surfaceNormal,
        entity = entityHit,
    }
end

-- Raycast from camera to world (what the player is looking at)
local function GetPlayerAimTarget(maxDist)
    local camCoords = GetGameplayCamCoord()
    local camRot = GetGameplayCamRot(2)

    -- Convert rotation to direction vector
    local radX = math.rad(camRot.x)
    local radZ = math.rad(camRot.z)

    local direction = vector3(
        -math.sin(radZ) * math.abs(math.cos(radX)),
         math.cos(radZ) * math.abs(math.cos(radX)),
         math.sin(radX)
    )

    local playerPed = PlayerPedId()
    return Raycast(camCoords, direction, maxDist or 50.0, -1, playerPed)
end

Raycast Flags and Filtering

The flags parameter in StartShapeTestRay controls what types of objects the ray can hit. Using the correct flags is crucial for performance and accuracy. Checking against everything (-1) is expensive and often returns unwanted results like invisible collision boundaries. Use specific flags to target only what you need.

-- client/raycast_flags.lua
-- Common flag values for StartShapeTestRay
local RayFlags = {
    WORLD        = 1,      -- Static world geometry (buildings, terrain)
    VEHICLES     = 2,      -- Vehicles
    PEDS         = 4,      -- Pedestrians and players (on foot)
    OBJECTS      = 16,     -- Props and objects
    WATER        = 32,     -- Water surfaces
    VEGETATION   = 256,    -- Trees and bushes

    -- Common combinations
    WORLD_AND_VEHICLES = 3,
    WORLD_AND_OBJECTS  = 17,
    ENTITIES_ONLY      = 22, -- Vehicles + Peds + Objects
    ALL                = -1, -- Everything
}

-- Example: Only detect vehicles (for a speed camera script)
local function DetectVehicleAhead(origin, direction)
    return Raycast(origin, direction, 100.0, RayFlags.VEHICLES, PlayerPedId())
end

-- Example: Detect ground position (for object placement)
local function GetGroundPosition(x, y, z)
    local result = Raycast(
        vector3(x, y, z + 50.0),
        vector3(0.0, 0.0, -1.0),
        100.0,
        RayFlags.WORLD
    )

    if result.hit then
        return result.coords
    end
    return nil
end

Building a Targeting System

A targeting system combines raycasting with entity filtering to let players select and interact with specific entities in the game world. This is the foundation for interaction systems like ox_target and qb-target. The system casts a ray from the camera each frame, checks if the hit entity matches any registered targets, and displays interaction prompts.

-- client/targeting.lua
local TargetSystem = {
    targets = {},
    currentTarget = nil,
    enabled = true,
}

function TargetSystem.AddTarget(entity, options)
    TargetSystem.targets[entity] = {
        label = options.label or 'Interact',
        icon = options.icon or 'fas fa-hand',
        distance = options.distance or 3.0,
        canInteract = options.canInteract,
        onSelect = options.onSelect,
    }
end

function TargetSystem.RemoveTarget(entity)
    TargetSystem.targets[entity] = nil
end

-- Main targeting loop
CreateThread(function()
    while true do
        if TargetSystem.enabled then
            local aimResult = GetPlayerAimTarget(10.0)

            if aimResult.hit and aimResult.entity ~= 0 then
                local entity = aimResult.entity
                local target = TargetSystem.targets[entity]

                if target then
                    local playerCoords = GetEntityCoords(PlayerPedId())
                    local entityCoords = GetEntityCoords(entity)
                    local dist = #(playerCoords - entityCoords)

                    if dist <= target.distance then
                        local canInteract = true
                        if target.canInteract then
                            canInteract = target.canInteract(entity)
                        end

                        if canInteract then
                            TargetSystem.currentTarget = entity

                            -- Draw interaction prompt
                            DrawText3D(entityCoords.x, entityCoords.y, entityCoords.z + 1.0,
                                target.label)

                            -- Handle interaction key press
                            if IsControlJustPressed(0, 38) then -- E key
                                target.onSelect(entity)
                            end
                        end
                    end
                end
            else
                TargetSystem.currentTarget = nil
            end
        end

        Wait(0)
    end
end)

-- Helper: Draw 3D text at world coordinates
function DrawText3D(x, y, z, text)
    SetTextScale(0.35, 0.35)
    SetTextFont(4)
    SetTextProportional(true)
    SetTextColour(255, 255, 255, 215)
    SetTextEntry('STRING')
    SetTextCentre(true)
    AddTextComponentString(text)

    SetDrawOrigin(x, y, z, 0)
    DrawText(0.0, 0.0)
    ClearDrawOrigin()
end

Surface Detection and Object Placement

Raycasting is essential for object placement systems where the player points at a surface and an object preview follows their aim. The surface normal returned by the raycast tells you the orientation of the surface, allowing you to align placed objects correctly on slopes, walls, and ceilings.

-- client/placement.lua
local Placement = {
    active = false,
    previewEntity = nil,
    modelName = nil,
}

function Placement.Start(modelName)
    Placement.modelName = modelName
    Placement.active = true

    -- Create preview prop
    local model = joaat(modelName)
    RequestModel(model)
    while not HasModelLoaded(model) do Wait(10) end

    Placement.previewEntity = CreateObject(model, 0.0, 0.0, 0.0, false, true, false)
    SetEntityAlpha(Placement.previewEntity, 150, false)
    SetEntityCollision(Placement.previewEntity, false, false)
    FreezeEntityPosition(Placement.previewEntity, true)
    SetModelAsNoLongerNeeded(model)

    CreateThread(function()
        while Placement.active do
            local result = GetPlayerAimTarget(15.0)

            if result.hit then
                local coords = result.coords
                SetEntityCoords(Placement.previewEntity,
                    coords.x, coords.y, coords.z, false, false, false, false)

                -- Align to surface normal
                local normal = result.normal
                local pitch = math.deg(math.asin(normal.z)) - 90.0
                SetEntityRotation(Placement.previewEntity, pitch, 0.0,
                    GetEntityHeading(PlayerPedId()), 2, true)

                -- Color: green if valid, red if not
                local valid = IsPlacementValid(coords)
                if valid then
                    SetEntityDrawOutline(Placement.previewEntity, true)
                end

                -- Confirm placement
                if IsControlJustPressed(0, 38) and valid then
                    Placement.Confirm(coords)
                end
            end

            -- Cancel placement
            if IsControlJustPressed(0, 73) then -- X key
                Placement.Cancel()
            end

            Wait(0)
        end
    end)
end

function Placement.Confirm(coords)
    Placement.active = false

    if Placement.previewEntity then
        DeleteEntity(Placement.previewEntity)
        Placement.previewEntity = nil
    end

    TriggerServerEvent('myresource:placeObject', Placement.modelName, coords)
end

function Placement.Cancel()
    Placement.active = false
    if Placement.previewEntity then
        DeleteEntity(Placement.previewEntity)
        Placement.previewEntity = nil
    end
end

function IsPlacementValid(coords)
    local playerCoords = GetEntityCoords(PlayerPedId())
    local dist = #(playerCoords - coords)
    return dist >= 1.0 and dist <= 10.0
end

Line of Sight Checks

Raycasting is the standard way to check if two points can see each other without obstructions. This is used in stealth systems, AI awareness, sniper mechanics, and any gameplay that depends on visibility. Cast a ray from one point to another and check if it hits something before reaching the target.

-- shared/los.lua (can run on both client and server)
local function HasLineOfSight(from, to, ignoreEntity)
    local direction = to - from
    local distance = #direction
    direction = direction / distance -- normalize

    local result = Raycast(from, direction, distance, RayFlags.WORLD, ignoreEntity)

    if not result.hit then
        return true -- nothing blocking the path
    end

    -- Check if the hit point is past the target
    local hitDist = #(from - result.coords)
    return hitDist >= distance * 0.95
end

-- Usage: Check if NPC can see the player
local function CanNPCSeePlayer(npcPed, playerPed)
    local npcCoords = GetEntityCoords(npcPed) + vector3(0, 0, 0.7)
    local playerCoords = GetEntityCoords(playerPed) + vector3(0, 0, 0.7)

    return HasLineOfSight(npcCoords, playerCoords, npcPed)
end

Performance Optimization for Raycasts

  • Limit raycast frequency. Do not cast rays every frame unless absolutely necessary. For interaction checks, every 100ms is usually sufficient. Only use per-frame raycasting for active aiming and placement systems.
  • Use specific flags. Casting against -1 (everything) is significantly slower than targeting specific entity types. Filter to only what you need.
  • Cache results. If the player has not moved or looked in a different direction, the raycast result will be the same. Skip redundant casts by comparing camera position and rotation.
  • Use distance checks first. Before raycasting to check entity interaction, verify the player is within a reasonable radius. Distance checks are orders of magnitude cheaper than raycasts.
  • Prefer StartShapeTestLosProbe for LOS. For simple line-of-sight checks between two known points, StartShapeTestLosProbe is lighter than StartShapeTestRay because it only returns a hit/no-hit boolean.
  • Avoid raycasting through the entire map. Keep maximum distances reasonable. A 50-unit raycast is much cheaper than a 1000-unit one.

Share this article

Ready to upgrade your server?

Check out our premium FiveM scripts in the Agency Scripts store or join our Discord community for support and updates.