AI Core
API-ReferenzLib: Client

Kamera und Timing

ScriptCamera, TickLoop, AreaFollower und ServerClock - geskriptete Kameras, Jobs pro Frame, die nur bei Bedarf laufen, den Bereich des Spielers an den Server melden und die Server-Uhr auf dem Client.

Eine geskriptete Kamera für Ausstellungsräume und Szenen, ein Job pro Frame, den du nur einschaltest, solange er gebraucht wird, die Client-Hälfte des Bereichs-Streamings des Servers und die Server-Uhr für Countdowns. Periodische Arbeit allgemein ist every(ms, fn) (siehe Lib: Shared und fivem); nie setInterval oder ein nacktes setTick.

ScriptCamera

new ScriptCamera

Eine geskriptete Kamera: die Ausstellungsansicht eines Shops, ein Editor, eine Szene, ein Fernglas. start erstellt und zeigt sie, die anderen Methoden bewegen und verändern sie, stop blendet weich zur Spielkamera zurück und zerstört sie. Eine Kamera, die noch läuft, wenn deine Ressource stoppt, wird sofort zurückgegeben, sodass der Spieler nie darin festhängt.

Seite: Client · Wartet: nein

Signatur
new ScriptCamera(type?: string)

Kein Lua-Zwilling: Lua-Ressourcen nutzen CreateCam, RenderScriptCams und DestroyCam.

Parameter

Prop

Type

CameraView

Wohin eine Kamera schaut, genutzt von start, move und glideTo:

Prop

Type

Mitglieder

Prop

Type

Beispiel

import { ScriptCamera } from "@aicore-fivem/lib/client";

const camera = new ScriptCamera();
const CAR = { x: -44.4, y: -1097.1, z: 26.4 };

export function openShowroom(): void {
  camera.start({ x: -40.2, y: -1100.5, z: 27.6 }, { pointAt: CAR, fov: 50 }, 800);
}

export function frontView(): void {
  camera.glideTo({ x: -47.5, y: -1093.8, z: 27.2 }, { pointAt: CAR, fov: 45 }, 1200);
}

export function closeShowroom(): void {
  camera.stop(800);
}

camera.start

Erstellt die Kamera (wenn sie noch nicht existiert), setzt sie an einen Punkt und macht sie zur Ansicht, mit weicher Überblendung von der Spielkamera.

Seite: Client · Wartet: nein

Signatur
camera.start(at: Vec3, view?: CameraView, easeMs?: number): void

Parameter

Prop

Type

Rückgabewert

void. Siehe das Klassenbeispiel.

camera.move

Bewegt die Kamera, sofort oder gleitend (das Spiel interpoliert). Tut nichts, solange gestoppt.

Seite: Client · Wartet: nein

Signatur
camera.move(at: Vec3, view?: CameraView, ms?: number): void

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

camera.move({ x: -42.0, y: -1099.0, z: 28.5 }, { pointAt: CAR }, 600);

camera.glideTo

Blendet zu einer neuen Einstellung über: Eine zweite Kamera am neuen Ort übernimmt von dieser, und die alte wird nach der Überblendung zerstört. Wackeln und Unschärfe werden übernommen. Startet die Kamera, wenn sie gestoppt ist (oder wenn ms 0 ist).

Seite: Client · Wartet: nein

Signatur
camera.glideTo(at: Vec3, view: CameraView, ms: number): void

Parameter

Prop

Type

Rückgabewert

void. Siehe das Klassenbeispiel.

camera.attach

Lässt die Kamera einer Entity mit einem Versatz im eigenen Raum der Entity folgen.

Seite: Client · Wartet: nein

Signatur
camera.attach(entity: number, offset: Vec3, relative?: boolean): void

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

import { cache, ScriptCamera } from "@aicore-fivem/lib/client";

const chase = new ScriptCamera();
if (cache.vehicle !== false) {
  chase.start({ x: 0, y: 0, z: 0 });
  chase.attach(cache.vehicle, { x: 0, y: -6, z: 2 });
}

camera.pointAt

Lässt die Kamera weiter auf einen Punkt schauen.

Seite: Client · Wartet: nein

Signatur
camera.pointAt(target: Vec3): void

Parameter

Prop

Type

Rückgabewert

void.

camera.setFov

Ändert das Sichtfeld (Zoom).

Seite: Client · Wartet: nein

Signatur
camera.setFov(fov: number): void

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

// Fernglas: mit dem Mausrad zoomen.
let fov = 40;
export function zoom(step: number): void {
  fov = Math.min(70, Math.max(5, fov + step));
  camera.setFov(fov);
}

camera.shake

Fügt das Kamerawackeln des Spiels hinzu. Amplitude 0 stoppt es.

Seite: Client · Wartet: nein

Signatur
camera.shake(type: string, amplitude: number): void

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

camera.shake("HAND_SHAKE", 0.3);

camera.blur

Schärfentiefe: scharf zwischen zwei Entfernungen, unscharf außerhalb. Übergib null für keine. Sie braucht ein Native pro Frame, das nur läuft, solange eine Unschärfe gesetzt ist.

Seite: Client · Wartet: nein

Signatur
camera.blur(dof: CameraBlur | null): void

Parameter

Prop

Type

Rückgabewert

void.

Beispiel

camera.blur({ near: 2, far: 8, strength: 0.8 });

camera.stop

Gibt die Ansicht an die Spielkamera zurück (weich über easeMs) und zerstört die Kamera. Wackeln und Unschärfe enden ebenfalls. Tut nichts, solange gestoppt.

Seite: Client · Wartet: nein

Signatur
camera.stop(easeMs?: number): void

Parameter

Prop

Type

Rückgabewert

void. Siehe das Klassenbeispiel.

TickLoop

new TickLoop

Ein Job pro Frame (every(0, fn) auf dem einen Scheduler deiner Ressource), der aus ist, bis du ihn einschaltest. Nutze ihn für alles, was nur manchmal in jedem Frame laufen muss: einen Marker in der Nähe zeichnen, eine Platzierungsvorschau, deaktiviertes Schießen beim Zielen auf einen Freund.

Seite: Client · Wartet: nein

Signatur
new TickLoop(onFrame: () => void, name?: string)

Kein Lua-Zwilling: aiLib.every(0, fn, { active = false, name = name }) und job:set(active).

Parameter

Prop

Type

Mitglieder

Prop

Type

Beispiel

import { TickLoop, Zones } from "@aicore-fivem/lib/client";

const SPOT = { x: 215.8, y: -810.1, z: 30.7 };
const light = new TickLoop(() => {
  DrawLightWithRange(SPOT.x, SPOT.y, SPOT.z + 2, 61, 247, 158, 8, 4);
}, "garageLight");

const zones = new Zones();
zones.add("garage", {
  shape: { type: "sphere", center: SPOT, radius: 25 },
  onEnter: () => light.set(true),
  onExit: () => light.set(false),
});

Jeder Job deiner Ressource läuft aus einem setTick, das nur existiert, solange ein Job an ist. Erstelle die Schleife einmal am Anfang der Datei und schalte sie, statt pro Start eine neue zu erstellen.

AreaFollower

new AreaFollower

Die Client-Hälfte eines Server-AreaStream (Bereichs-Streaming): Sie meldet dem Server, in welcher Rasterzelle der Spieler steht, sobald sie sich ändert (alle 2 Sekunden geprüft, solange gestartet), sodass der Server nur die Weltobjekte (Drops, Pflanzen, platzierte Props) um den Spieler sendet. Erstelle eine am Anfang der Datei.

Seite: Client · Wartet: nein

Signatur
new AreaFollower(event: string, cellSize?: number)

Kein Lua-Zwilling.

Parameter

Prop

Type

Mitglieder

Prop

Type

Beispiel

resource/client/index.ts
import { AreaFollower, onServerEvent } from "@aicore-fivem/lib/client";

const follower = new AreaFollower("myplants:cell");
follower.start();

const plants = new Map<number, unknown>();

// Der Stream sendet (reset, puts, removes): einen Snapshot mit reset true, danach nur Änderungen.
onServerEvent(
  "myplants:area",
  {
    args: ([reset, puts, removes]) =>
      typeof reset === "boolean" && Array.isArray(puts) && Array.isArray(removes)
        ? ([reset, puts, removes] as const)
        : null,
  },
  (reset, puts, removes) => {
    if (reset) plants.clear();
    for (const plant of puts as Array<{ id?: unknown } | null>)
      if (typeof plant?.id === "number") plants.set(plant.id, plant);
    for (const id of removes) if (typeof id === "number") plants.delete(id);
  },
);

Der Server prüft eine gemeldete Zelle gegen die echte Position des Spielers (followReported akzeptiert nur Zellen innerhalb einer Zelle um den Ped), sodass ein Cheater keinen weit entfernten Bereich anfordern kann.

ServerClock

new ServerClock

Die Server-Uhr auf einem Client. Client- und Server-Uhr können sich unterscheiden, daher muss ein Countdown bis zu einer Serverzeit (ein Raub-Cooldown, das Ende einer Auktion) zuerst in lokale Zeit umgerechnet werden. Rufe sync mit dem Date.now() des Servers auf, sobald ein Snapshot oder eine Startnachricht ankommt.

Seite: Client (aus @aicore-fivem/lib/shared) · Wartet: nein

Signatur
new ServerClock()

Kein Lua-Zwilling.

Mitglieder

Prop

Type

Beispiel

resource/client/index.ts
import { every, onServerEvent } from "@aicore-fivem/lib/client";
import { ServerClock } from "@aicore-fivem/lib/shared";

const clock = new ServerClock();
let endsAt = 0; // lokale Zeit

onServerEvent(
  "myauction:state",
  { args: ([now, ends]) => (typeof now === "number" && typeof ends === "number" ? [now, ends] : null) },
  (now, ends) => {
    clock.sync(now);
    endsAt = clock.toLocal(ends);
  },
);

every(1000, () => {
  const left = Math.max(0, Math.round((endsAt - Date.now()) / 1000));
  SendNUIMessage({ action: "countdown", data: left });
});

Der Server sendet die Endzeit einmal; der Client zählt auf seiner eigenen Uhr herunter. Sende nie jede Sekunde die verbleibende Zeit.

Auf dieser Seite