ox_target addBoxZone-Beispiel, FiveM-Raycasterkennung mit Lua
ox_target addBoxZone-Beispiel mit Lua-Code zum Kopieren für FiveM-Raycasterkennung. Auch addSpherezone und qb-target. Für QBCore, ESX und standalone.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Was ist Raycasting in FiveM?
Raycasting ist eine Technik, bei der eine unsichtbare Linie (ein Ray) von einem Punkt im 3D-Raum zu einem anderen geschossen und geprüft wird, was sie auf ihrem Weg trifft. In FiveM wird Raycasting für Zielsysteme, Objektinteraktionen, Sichtlinienprüfungen, Oberflächenerkennung und individuelle Zielmechaniken verwendet. Die Game-Engine stellt mehrere Raycast-Natives bereit, mit denen du Entities, Oberflächen und die Geometrie der Spielwelt präzise erkennen kannst. Wenn du Raycasting verstehst, kannst du fortgeschrittene Gameplay-Mechaniken umsetzen, etwa individuelle Zielsysteme, intelligente Platzierungssysteme, Laserpointer und kontextabhängige Interaktionen, die darauf reagieren, was der Spieler gerade ansieht.
Einfaches Raycasting mit StartShapeTestRay
Die einfachste Form des Raycastings verwendet StartShapeTestRay Damit wird ein Ray zwischen zwei Koordinaten geschossen und es werden Informationen über das erste getroffene Objekt zurückgegeben. Das Ergebnis enthält den Trefferstatus, die genauen Koordinaten des Auftreffpunkts, den Normalenvektor der Oberfläche und den Entity-Handle, falls ein Entity getroffen wurde. Du musst im nächsten Frame GetShapeTestResult aufrufen, um die Ergebnisse abzurufen.
-- 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 und Filterung
Der Flags-Parameter in StartShapeTestRay legt fest, welche Objekttypen der Ray treffen kann. Die richtigen Flags sind entscheidend für Performance und Genauigkeit. Alles zu prüfen (-1) ist rechenintensiv und führt oft zu unerwünschten Ergebnissen, etwa unsichtbaren Kollisionsgrenzen. Verwende gezielte Flags, um nur das zu erfassen, was du brauchst.
-- 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
Ein Zielsystem erstellen
Ein Zielsystem kombiniert Raycasting mit Entity-Filterung, damit Spieler bestimmte Entities in der Spielwelt auswählen und mit ihnen interagieren können. Das ist die Grundlage für Interaktionssysteme wie ox_target und qb-target. Das System schießt in jedem Frame einen Ray von der Kamera, prüft, ob das getroffene Entity einem registrierten Ziel entspricht, und zeigt Interaktionshinweise an.
-- 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
Oberflächenerkennung und Objektplatzierung
Raycasting ist unverzichtbar für Objektplatzierungssysteme, bei denen der Spieler auf eine Oberfläche zielt und eine Objektvorschau seiner Zielrichtung folgt. Der vom Raycast zurückgegebene Oberflächennormalenvektor gibt dir die Ausrichtung der Oberfläche an. So kannst du platzierte Objekte korrekt an Hängen, Wänden und Decken ausrichten.
-- 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
Sichtlinienprüfungen
Raycasting ist die gängige Methode, um zu prüfen, ob zwei Punkte ohne Hindernisse eine Sichtverbindung haben. Das wird in Schleichsystemen, für KI-Wahrnehmung, Scharfschützenmechaniken und überall dort eingesetzt, wo das Gameplay von der Sichtbarkeit abhängt. Schieße einen Ray von einem Punkt zum anderen und prüfe, ob er etwas trifft, bevor er das Ziel erreicht.
-- 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-Optimierung für Raycasts
- Begrenze die Häufigkeit von Raycasts. Schieße nicht in jedem Frame Rays, außer es ist unbedingt erforderlich. Für Interaktionsprüfungen reicht normalerweise ein Intervall von 100ms. Verwende Raycasting in jedem Frame nur für aktive Ziel- und Platzierungssysteme.
- Verwende gezielte Flags. Das Prüfen von
-1(alles) ist deutlich langsamer, als bestimmte Entity-Typen zu erfassen. Filtere nach dem, was du brauchst. - Ergebnisse zwischenspeichern. Wenn sich der Spieler nicht bewegt oder in eine andere Richtung schaut, bleibt das Raycast-Ergebnis gleich. Überspringe unnötige Casts, indem du Kameraposition und -rotation vergleichst.
- Prüfe zuerst die Entfernung. Bevor du mit Raycasting die Interaktion mit einem Entity prüfst, kontrolliere, ob sich der Spieler in einem angemessenen Radius befindet. Entfernungsprüfungen sind um Größenordnungen effizienter als Raycasts.
- Verwende für LOS bevorzugt StartShapeTestLosProbe. Für einfache Sichtlinienprüfungen zwischen zwei bekannten Punkten ist
StartShapeTestLosProberessourcenschonender alsStartShapeTestRayda es nur einen Treffer-/Kein-Treffer-Boolean zurückgibt. - Vermeide Raycasts durch die gesamte Karte. Halte maximale Entfernungen angemessen. Ein Raycast über 50 Einheiten ist deutlich günstiger als einer über 1000 Einheiten.