Добро пожаловать на FIVE MULTIPLAYER

Собирайтесь с другими игроками, создавайте собственный сервер, формируйте сообщество или придумывайте новый игровой режим. В FIVE Multiplayer вас ждут бесконечные возможности и непревзойденный контроль. Пусть ваше воображение станет вашим единственным пределом.

  • Сайт ragem.ru/ragem.pro временно не доступен, проводятся технические работы до 20.07.2026 включительно.

JS Создание скриншотов для редактора внешности

  • Автор темы Автор темы fReeed0m
  • Дата начала Дата начала

fReeed0m

Новый участник
👶 Новенький
UID
65
Активность
Офлайн
Регистрация
5 Июн 2026
Сообщения
3
Реакции
4
Баллы
3
Автор темы
Накидал небольшой ресурс, который делает фото волос, бровей и бороды (предварительно создав отдельного педа и подняв его в небо) в определенных ракурсах и сохраняет в папку с ресурсом в формате .png. Если есть желание, то можно докрутить чтоб он фоткал торс, ноги и обувь.
Примеры фото:
1780824488084.png
1780824511953.png
1780824533353.png

Работает при помощи ресурса от CitizenFX screenshot-basic - https://github.com/citizenfx/screenshot-basic
Установка:
1. Склонировать репозиторий
2. Подключить его в server.cfg
3. Создать отдельный ресурс с удобным названием (например images)
4. Внутри images создать три файла: fxmanifest.lua, client.js и server.js (еще можно папку /ui/shots)
server.js:
JavaScript:
const fs = require('fs');
const path = require('path');

const RESOURCE = GetCurrentResourceName();
const RESOURCE_PATH = GetResourcePath(RESOURCE);

const takeShot = (src, relativePath) => new Promise((resolve) => {
    const absPath = path.join(RESOURCE_PATH, 'ui', 'shots', relativePath);
    const dir = path.dirname(absPath);

    try {
        fs.mkdirSync(dir, { recursive: true });
    } catch (e) {
        console.log(`^1[IMAGES]^7 не удалось создать папку ${dir}: ${e.message}`);
        resolve(false);
        return;
    }

    exports['screenshot-basic'].requestClientScreenshot(src, {
        fileName: absPath,
        encoding: 'png',
        quality: 0.95
    }, (err, data) => {
        if (err) {
            console.log(`^1[IMAGES]^7 ошибка скриншота ${relativePath}: ${err}`);
            resolve(false);
        } else {
            resolve(true);
        }
    });
});

onNet('identity-images:server:shot', async (relativePath) => {
    const src = global.source;
    const ok = await takeShot(src, relativePath);
    emitNet('identity-images:client:shotDone', src, ok);
});

onNet('identity-images:server:genComplete', (gender) => {
    const src = global.source;
    console.log(`^2[IMAGES]^7 ${GetPlayerName(src)} завершил генерацию превью (${gender})`);
    console.log(`^2[IMAGES]^7 картинки сохранены в: ${path.join(RESOURCE_PATH, 'ui', 'shots')}`);
});

client.js:
JavaScript:
let genActive = false;
let genPed = null;
let genCam = null;
let shotResolve = null;

const RANGES = {
    hair: { type: 'component', id: 2, max: 36, cam: { dist: 1.3, height: 0.25, pitch: 12.0, fov: 28.0 } },
    eyebrows: { type: 'overlay', id: 2, max: 33, cam: { dist: 0.6, height: 0.05, pitch: 0.0, fov: 20.0 } },
    beard: { type: 'overlay', id: 1, max: 28, cam: { dist: 0.7, height: -0.05, pitch: -8.0, fov: 22.0 } }
};

const SKY = { x: 0.0, y: 0.0, z: 1500.0, h: 180.0 };

const wait = (ms) => new Promise((r) => setTimeout(r, ms));

const loadGenModel = async (model) => {
    const hash = GetHashKey(model);
    if (!IsModelInCdimage(hash)) return null;
    RequestModel(hash);
    let t = 0;
    while (!HasModelLoaded(hash) && t < 100) { await wait(50); t++; }
    return HasModelLoaded(hash) ? hash : null;
};

const requestShot = (relativePath) => new Promise((resolve) => {
    shotResolve = resolve;
    emitNet('identity-images:server:shot', relativePath);
});

onNet('identity-images:client:shotDone', (ok) => {
    if (shotResolve) { shotResolve(ok); shotResolve = null; }
});

const setupStudio = async (model) => {
    const hash = await loadGenModel(model);
    if (!hash) return false;

    const s = SKY;
    SetFocusArea(s.x, s.y, s.z, 0.0, 0.0, 0.0);

    if (genPed && DoesEntityExist(genPed)) DeleteEntity(genPed);
    genPed = CreatePed(4, hash, s.x, s.y, s.z, s.h, false, false);
    SetEntityAsMissionEntity(genPed, true, true);
    FreezeEntityPosition(genPed, true);
    SetEntityInvincible(genPed, true);
    SetEntityVisible(genPed, true, false);
    SetEntityCoordsNoOffset(genPed, s.x, s.y, s.z, false, false, false);
    SetPedDefaultComponentVariation(genPed);
    SetPedHeadBlendData(genPed, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.0, false);
    SetEntityHeading(genPed, s.h);
    ClearPedTasksImmediately(genPed);
    SetBlockingOfNonTemporaryEvents(genPed, true);
    SetPedCanRagdoll(genPed, false);
    FreezeEntityPosition(genPed, true);
    SetModelAsNoLongerNeeded(hash);

    NetworkOverrideClockTime(12, 0, 0);
    SetWeatherTypeNowPersist('EXTRASUNNY');

    genCam = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA', s.x, s.y + 1.0, s.z, 0.0, 0.0, 0.0, 22.0, true, 2);
    SetCamActive(genCam, true);
    RenderScriptCams(true, false, 0, true, true);

    await wait(500);

    return true;
};

const positionCamera = (cfg) => {
    if (!genPed || !genCam) return;
    const headBone = GetPedBoneCoords(genPed, 31086, 0.0, 0.0, 0.0);
    const hx = headBone[0], hy = headBone[1], hz = headBone[2];

    const rad = SKY.h * Math.PI / 180.0;
    const camX = hx + Math.sin(rad) * cfg.dist;
    const camY = hy + Math.cos(rad) * cfg.dist;
    const camZ = hz + cfg.height;

    SetCamCoord(genCam, camX, camY, camZ);
    PointCamAtCoord(genCam, hx, hy, hz + cfg.height * 0.4);
    SetCamFov(genCam, cfg.fov);
};

const teardownStudio = () => {
    if (genCam) { RenderScriptCams(false, false, 0, true, true); DestroyCam(genCam, false); genCam = null; }
    if (genPed && DoesEntityExist(genPed)) { DeleteEntity(genPed); genPed = null; }
    NetworkClearClockTimeOverride();
    ClearWeatherTypePersist();
    ClearFocus();
};

const applyAndShoot = async (gender, name, range, i) => {
    if (range.type === 'component') {
        SetPedComponentVariation(genPed, range.id, i, 0, 0);
        await wait(60);
    } else {
        if (i === 0) {
            SetPedHeadOverlay(genPed, range.id, 255, 0.0);
        } else {
            SetPedHeadOverlay(genPed, range.id, i - 1, 1.0);
            SetPedHeadOverlayColor(genPed, range.id, 1, 1, 1);
        }
        await wait(150);
    }
    await requestShot(`${gender}/${name}/${i}.png`);
};

const generateForGender = async (gender, limit) => {
    const model = gender === 'female' ? 'mp_f_freemode_01' : 'mp_m_freemode_01';
    const ok = await setupStudio(model);
    if (!ok) { console.log('^1[IMAGES]^7 не удалось создать студию'); return; }

    for (const name in RANGES) {
        const range = RANGES[name];
        positionCamera(range.cam);
        await wait(150);
        const top = (typeof limit === 'number') ? Math.min(limit, range.max) : range.max;
        console.log(`^3[IMAGES]^7 ${gender}/${name}: 0..${top}`);
        for (let i = 0; i <= top; i++) {
            await applyAndShoot(gender, name, range, i);
        }
    }

    teardownStudio();
    emitNet('identity-images:server:genComplete', gender);
    console.log(`^2[IMAGES]^7 генерация ${gender} завершена`);
};

const runGeneration = async (gender, limit) => {
    if (genActive) { console.log('^1[IMAGES]^7 уже идёт генерация'); return; }
    genActive = true;

    const ped = PlayerPedId();
    const savedCoords = GetEntityCoords(ped);
    SetEntityVisible(ped, false, false);
    FreezeEntityPosition(ped, true);

    await wait(300);

    if (gender === 'both') {
        await generateForGender('male', limit);
        await generateForGender('female', limit);
    } else {
        await generateForGender(gender, limit);
    }

    SetEntityCoordsNoOffset(ped, savedCoords[0], savedCoords[1], savedCoords[2], false, false, false);
    SetEntityVisible(ped, true, false);
    FreezeEntityPosition(ped, false);

    genActive = false;
    console.log('^2[IMAGES]^7 ВСЁ ГОТОВО. Картинки в ui/shots/');
};

setTick(() => {
    if (!genActive) return;
    HideHudAndRadarThisFrame();
    if (genPed && DoesEntityExist(genPed)) {
        SetEntityCoordsNoOffset(genPed, SKY.x, SKY.y, SKY.z, false, false, false);
        SetEntityHeading(genPed, SKY.h);
        FreezeEntityPosition(genPed, true);
    }
});

RegisterCommand('genshots', (source, args) => {
    const gender = args[0] || 'both';
    if (!['male', 'female', 'both'].includes(gender)) {
        console.log('^1[IMAGES]^7 использование: /genshots [male|female|both] [test|число]');
        return;
    }
    let limit = null;
    if (args[1] === 'test') limit = 2;
    else if (args[1] && !isNaN(parseInt(args[1], 10))) limit = parseInt(args[1], 10);

    console.log(`^3[IMAGES]^7 старт генерации: ${gender}${limit !== null ? ' (тест, до ' + limit + ')' : ''}`);
    runGeneration(gender, limit);
}, false);

fxmanifest.lua:
Код:
fx_version 'cerulean'
game 'gta5'

author 'fReeed0m'
description 'Генератор превью внешности (волосы, борода, брови)'
version '1.0.0'

dependency 'screenshot-basic'

client_script 'client.js'
server_script 'server.js'

Команды пишутся в консоли F8:
genshots male - скрины мужского пола
genshots female - скрины женского пола
genshots both - скрины обоих полов
genshots test - фотает по 2 всего (в нашем случае 3 - потому что 0 это лысая голова, например)
genshots "число" - определенное кол-во

Далее фотки достаются из папки с ресурсом /ui/shots/. Ну, а потом, как вариант загнать фото в какую-нибудь нейронку чтоб она удалила фон и прочее - как вам удобно
 
Назад
Верх