Guia 2026-05-07

Guia do Sistema de Raycasting e Targeting em FiveM

TDYSKY

TDYSKY

Fundador & Lead Developer na Agency Scripts

O que é Raycasting no FiveM?

Raycasting é a técnica de disparar uma linha invisível (raio) de um ponto a outro no espaço 3D e verificar o que ela atinge ao longo do caminho. No FiveM, o raycasting é usado para sistemas de mira, interação de objetos, verificações de linha de visão, detecção de superfície e mecânica de mira personalizada. O mecanismo de jogo fornece vários nativos de raycast que permitem detectar entidades, superfícies e geometria mundial com precisão. Compreender o raycasting desbloqueia mecânicas de jogo avançadas, como segmentação personalizada, sistemas de posicionamento inteligentes, ponteiros laser e interações sensíveis ao contexto que respondem ao que o jogador está olhando.

Raycasting básico com StartShapeTestRay

A forma mais simples de raycasting usa StartShapeTestRay que lança um raio entre duas coordenadas e retorna informações sobre a primeira coisa que atinge. O resultado inclui o status do acerto, as coordenadas exatas do ponto de impacto, o vetor normal da superfície e o identificador da entidade se uma entidade foi atingida. Você deve chamar GetShapeTestResult no próximo quadro para recuperar os resultados.

-- 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

Sinalizações e filtragem do Raycast

O parâmetro flags em StartShapeTestRay controla quais tipos de objetos o raio pode atingir. Usar os sinalizadores corretos é crucial para desempenho e precisão. Verificar tudo (-1) é caro e geralmente retorna resultados indesejados, como limites de colisão invisíveis. Use sinalizações específicas para segmentar apenas o que você precisa.

-- 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

Construindo um sistema de segmentação

Um sistema de segmentação combina raycasting com filtragem de entidades para permitir que os jogadores selecionem e interajam com entidades específicas no mundo do jogo. Esta é a base para sistemas de interação como ox_target e qb-target. O sistema emite um raio da câmera a cada quadro, verifica se a entidade atingida corresponde a algum alvo registrado e exibe avisos de interação.

-- 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

Detecção de superfície e posicionamento de objetos

Raycasting é essencial para sistemas de posicionamento de objetos onde o jogador aponta para uma superfície e uma visualização do objeto segue sua mira. A normal da superfície retornada pelo raycast informa a orientação da superfície, permitindo alinhar objetos colocados corretamente em encostas, paredes e tetos.

-- 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

Verificações da linha de visão

Raycasting é a forma padrão de verificar se dois pontos podem se ver sem obstruções. Isso é usado em sistemas furtivos, reconhecimento de IA, mecânica de atiradores e qualquer jogabilidade que dependa de visibilidade. Lance um raio de um ponto a outro e verifique se ele atinge algo antes de atingir o alvo.

-- 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

Otimização de desempenho para Raycasts

  • Limite a frequência de transmissão de raios. Não lance raios em todos os quadros, a menos que seja absolutamente necessário. Para verificações de interação, cada 100 ms geralmente é suficiente. Use raycasting por quadro apenas para sistemas ativos de mira e posicionamento.
  • Use sinalizações específicas. A transmissão em -1 (tudo) é significativamente mais lenta do que a segmentação de tipos de entidade específicos. Filtre apenas o que você precisa.
  • Resultados do cache. Se o jogador não se moveu ou olhou em uma direção diferente, o resultado do raycast será o mesmo. Evite transmissões redundantes comparando a posição e a rotação da câmera.
  • Use primeiro verificações de distância. Antes de lançar raios para verificar a interação da entidade, verifique se o jogador está dentro de um raio razoável. As verificações de distância são muito mais baratas que os raycasts.
  • Prefira StartShapeTestLosProbe para LOS. Para verificações simples de linha de visão entre dois pontos conhecidos, StartShapeTestLosProbe é mais leve que StartShapeTestRay porque retorna apenas um booleano de acerto/não acerto.
  • Evite a projeção de raios em todo o mapa. Mantenha distâncias máximas razoáveis. Um raycast de 50 unidades é muito mais barato que um de 1.000 unidades.

Partilhar este artigo

Pronto para melhorar o teu servidor?

Explora os nossos scripts FiveM premium na loja Agency Scripts ou junta-te à nossa comunidade no Discord para suporte e atualizações.