AI Core
Справочник APILib: клиент

Камера и время

ScriptCamera, TickLoop, AreaFollower и ServerClock - скриптовые камеры, покадровые задачи, которые работают только когда нужны, сообщение серверу об области игрока и часы сервера на клиенте.

Скриптовая камера для витрин и сцен, покадровая задача, которую вы включаете только когда она нужна, клиентская половина потоковой передачи областей сервера и часы сервера для обратных отсчётов. Периодическая работа в целом это every(ms, fn) (см. Lib: общее и fivem); никогда не setInterval и не голый setTick.

ScriptCamera

new ScriptCamera

Скриптовая камера: вид витрины магазина, редактор, сцена, бинокль. start создаёт и показывает её, остальные методы двигают и меняют её, stop плавно возвращает к игровой камере и уничтожает её. Камера, которая ещё работает, когда ваш ресурс останавливается, сразу возвращается, поэтому игрок никогда в ней не застрянет.

Сторона: Клиент · Ждёт: нет

Сигнатура
new ScriptCamera(type?: string)

Lua-аналога нет: ресурсы на Lua используют CreateCam, RenderScriptCams и DestroyCam.

Параметры

Prop

Type

CameraView

Куда смотрит камера, используется в start, move и glideTo:

Prop

Type

Члены

Prop

Type

Пример

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

Создаёт камеру (если её ещё нет), ставит её в точку и делает её видом, плавно переходя от игровой камеры.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.start(at: Vec3, view?: CameraView, easeMs?: number): void

Параметры

Prop

Type

Возвращает

void. См. пример класса.

camera.move

Двигает камеру сразу или плавно (интерполирует игра). Ничего не делает, пока камера остановлена.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.move(at: Vec3, view?: CameraView, ms?: number): void

Параметры

Prop

Type

Возвращает

void.

Пример

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

camera.glideTo

Переходит к новому кадру: вторая камера в новом месте принимает управление у этой, а старая уничтожается после перехода. Тряска и размытие переносятся. Запускает камеру, если она остановлена (или если ms равно 0).

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.glideTo(at: Vec3, view: CameraView, ms: number): void

Параметры

Prop

Type

Возвращает

void. См. пример класса.

camera.attach

Заставляет камеру следовать за сущностью со смещением в собственном пространстве сущности.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.attach(entity: number, offset: Vec3, relative?: boolean): void

Параметры

Prop

Type

Возвращает

void.

Пример

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

Заставляет камеру продолжать смотреть на точку.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.pointAt(target: Vec3): void

Параметры

Prop

Type

Возвращает

void.

camera.setFov

Меняет поле зрения (зум).

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.setFov(fov: number): void

Параметры

Prop

Type

Возвращает

void.

Пример

// Бинокль: зум колёсиком мыши.
let fov = 40;
export function zoom(step: number): void {
  fov = Math.min(70, Math.max(5, fov + step));
  camera.setFov(fov);
}

camera.shake

Добавляет игровую тряску камеры. Амплитуда 0 останавливает её.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.shake(type: string, amplitude: number): void

Параметры

Prop

Type

Возвращает

void.

Пример

camera.shake("HAND_SHAKE", 0.3);

camera.blur

Глубина резкости: резко между двумя расстояниями, размыто за их пределами. Передайте null, чтобы убрать. Нужен покадровый натив, который работает, только пока задано размытие.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.blur(dof: CameraBlur | null): void

Параметры

Prop

Type

Возвращает

void.

Пример

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

camera.stop

Возвращает вид игровой камере (с плавным переходом за easeMs) и уничтожает камеру. Тряска и размытие тоже заканчиваются. Ничего не делает, пока камера остановлена.

Сторона: Клиент · Ждёт: нет

Сигнатура
camera.stop(easeMs?: number): void

Параметры

Prop

Type

Возвращает

void. См. пример класса.

TickLoop

new TickLoop

Покадровая задача (every(0, fn) в едином планировщике вашего ресурса), которая выключена, пока вы её не включите. Используйте для всего, что должно работать каждый кадр лишь иногда: рисование маркера поблизости, предпросмотр размещения, отключённая стрельба при прицеливании в друга.

Сторона: Клиент · Ждёт: нет

Сигнатура
new TickLoop(onFrame: () => void, name?: string)

Lua-аналога нет: aiLib.every(0, fn, { active = false, name = name }) и job:set(active).

Параметры

Prop

Type

Члены

Prop

Type

Пример

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),
});

Каждая задача вашего ресурса выполняется из одного setTick, который существует, только пока включена какая-либо задача. Создайте цикл один раз в начале файла и переключайте его, а не создавайте новый при каждом запуске.

AreaFollower

new AreaFollower

Клиентская половина серверного AreaStream (Потоковая передача областей): сообщает серверу, в какой ячейке сетки стоит игрок, каждый раз, когда она меняется (проверка каждые 2 секунды, пока запущен), поэтому сервер отправляет только объекты мира (выброшенные предметы, растения, поставленные пропы) вокруг игрока. Создайте один в начале файла.

Сторона: Клиент · Ждёт: нет

Сигнатура
new AreaFollower(event: string, cellSize?: number)

Lua-аналога нет.

Параметры

Prop

Type

Члены

Prop

Type

Пример

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>();

// Поток отправляет (reset, puts, removes): один снимок с reset true, затем только изменения.
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);
  },
);

Сервер сверяет сообщённую ячейку с реальной позицией игрока (followReported принимает только ячейки в пределах одной ячейки от педа), поэтому читер не может запросить далёкую область.

ServerClock

new ServerClock

Часы сервера на клиенте. Часы клиента и сервера могут различаться, поэтому обратный отсчёт до серверного времени (кулдаун ограбления, конец аукциона) нужно сначала перевести в локальное время. Вызывайте sync с серверным Date.now() каждый раз, когда приходит снимок или сообщение о старте.

Сторона: Клиент (из @aicore-fivem/lib/shared) · Ждёт: нет

Сигнатура
new ServerClock()

Lua-аналога нет.

Члены

Prop

Type

Пример

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; // локальное время

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 });
});

Сервер отправляет время окончания один раз; клиент ведёт обратный отсчёт по своим часам. Никогда не отправляйте оставшееся время каждую секунду.

На этой странице