Скрипты кастомизации тату и парикмахерской для FiveM
Добавьте тату-салоны и парикмахерские в ваш город FiveM. Углы камеры, интерфейсы, процесс записи и лучшие скрипты кастомизации для ролевой игры.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Понимание компонентов внешнего вида пешеходов
Создание системы тату-салона и парикмахерской в FiveM требует хорошего понимания того, как GTA V обрабатывает внешний вид персонажа (ped). Система внешнего вида разделена на несколько категорий компонентов: наложения головы для таких особенностей, как пятна, борода, брови и макияж, drawable-компоненты для одежды и причесок, а также татуировки, которые накладываются поверх модели персонажа. Каждый тип компонента использует разные native-функции и имеет собственную систему индексации. Наложения головы используют индексы 0-12 для управления такими особенностями, как пятна (0), борода (1), брови (2), старение (3), макияж (4), румянец (5) и помада (8). Прически задаются через систему drawable-компонентов с индексом 2 для волос головы. Татуировки используют совершенно отдельную систему декораций с именами коллекций и хеш-значениями. Перед созданием UI необходимо составить карту всех доступных опций для каждого компонента, включая количество вариаций для текущей модели персонажа, так как у мужских и женских персонажей разные наборы доступных стилей. Используйте GetNumHeadOverlayValues(overlayIndex) для запроса, сколько опций существует для каждого типа оверлея во время выполнения.
Система камеры для предварительного просмотра персонажа
Отличный опыт у парикмахера и тату-мастера зависит от того, что игрок видит выбранные опции в реальном времени. Постройте кастомную систему камер, которая фокусируется на нужной части тела при просмотре вариантов. При редактировании волос камера приближается к голове. При выборе татуировок на торсе камера отодвигается, показывая верхнюю часть тела. При работе с тату на ногах камера позиционируется так, чтобы охватить нижнюю часть тела. Используйте скриптовые native-функции камеры для плавных переходов между точками обзора при переключении категорий. Вот система управления камерой, которая обрабатывает несколько уровней зума и области фокуса тела:
local customCam = nil
local camActive = false
local CameraPresets = {
head = {offset = vector3(0.0, 0.65, 0.65), fov = 35.0},
face = {offset = vector3(0.0, 0.45, 0.62), fov = 25.0},
torso = {offset = vector3(0.0, 1.2, 0.2), fov = 45.0},
legs = {offset = vector3(0.0, 1.5, -0.5), fov = 50.0},
full = {offset = vector3(0.0, 2.5, 0.2), fov = 50.0},
}
function SetupPreviewCamera(preset)
local ped = PlayerPedId()
local pedCoords = GetEntityCoords(ped)
local pedHeading = GetEntityHeading(ped)
local headingRad = math.rad(pedHeading)
local cam = CameraPresets[preset] or CameraPresets.full
local camX = pedCoords.x + cam.offset.y * math.sin(-headingRad)
local camY = pedCoords.y + cam.offset.y * math.cos(-headingRad)
local camZ = pedCoords.z + cam.offset.z
if customCam then
local newCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(newCam, camX, camY, camZ)
PointCamAtCoord(newCam, pedCoords.x, pedCoords.y, pedCoords.z + cam.offset.z)
SetCamFov(newCam, cam.fov)
SetCamActiveWithInterp(newCam, customCam, 800, 1, 1)
Wait(800)
DestroyCam(customCam, false)
customCam = newCam
else
customCam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(customCam, camX, camY, camZ)
PointCamAtCoord(customCam, pedCoords.x, pedCoords.y, pedCoords.z + cam.offset.z)
SetCamFov(customCam, cam.fov)
SetCamActive(customCam, true)
RenderScriptCams(true, true, 500, true, false)
end
camActive = true
end
function DestroyPreviewCamera()
if customCam then
RenderScriptCams(false, true, 500, true, false)
DestroyCam(customCam, false)
customCam = nil
camActive = false
end
end
Добавьте поддержку вращения мышью или аналоговым стиком, чтобы игроки могли вращать своего персонажа при просмотре изменений. Захватывайте движение мыши, когда NUI его не использует, и применяйте его как поворот направления персонажа. Это позволяет игрокам осмотреть татуировку с разных углов перед покупкой, значительно улучшая опыт шопинга. Заморозьте персонажа на месте во время сессии настройки и воспроизводите анимацию бездействия, чтобы персонаж выглядел естественно, пока игрок просматривает варианты.
Настройка волос и бороды
Настройка волос и бороды использует компоненты drawable и системы head overlay соответственно. Прически задаются с помощью SetPedComponentVariation используя индекс компонента 2 (волосы) и индекс 1 (вариация текстуры/цвета). Цвет волос применяется отдельно с помощью SetPedHairColor который принимает индекс основного цвета и индекс цвета подсветки. Стили бороды используют систему наложений головы с индексом 1 и значением непрозрачности, которое контролирует, насколько густой или тонкой выглядит борода. Создайте NUI для отображения миниатюр или названий каждого доступного стиля, обновляя педа в реальном времени по мере того, как игрок переключается между опциями. Вот Lua-логика для применения изменений волос и бороды с предварительным просмотром в реальном времени:
function ApplyHairStyle(ped, styleIndex, textureIndex, primaryColor, highlightColor)
SetPedComponentVariation(ped, 2, styleIndex, textureIndex or 0, 0)
SetPedHairColor(ped, primaryColor, highlightColor)
end
function ApplyBeardStyle(ped, styleIndex, opacity, color)
SetPedHeadOverlay(ped, 1, styleIndex, opacity or 1.0)
SetPedHeadOverlayColor(ped, 1, 1, color, color)
end
function ApplyEyebrows(ped, styleIndex, opacity, color)
SetPedHeadOverlay(ped, 2, styleIndex, opacity or 1.0)
SetPedHeadOverlayColor(ped, 2, 1, color, color)
end
function GetAvailableHairStyles(ped)
local styles = {}
local numStyles = GetNumberOfPedDrawableVariations(ped, 2)
for i = 0, numStyles - 1 do
local numTextures = GetNumberOfPedTextureVariations(ped, 2, i)
table.insert(styles, {
index = i,
textures = numTextures,
label = 'Style ' .. (i + 1)
})
end
return styles
end
function GetAvailableBeardStyles(ped)
local styles = {}
local numStyles = GetNumHeadOverlayValues(1)
for i = 0, numStyles - 1 do
table.insert(styles, {
index = i,
label = 'Beard ' .. (i + 1)
})
end
return styles
end
Интерфейс NUI должен представлять цвета волос в виде визуальной палитры, а не нумерованных индексов, сопоставляя внутренние индексы цвета GTA с их реальными RGB представлениями. Включите ползунок для непрозрачности бороды, который позволяет игрокам точно настраивать толщину волос на лице от лёгкой щетины до густой бороды. Сохраняйте предыдущий внешний вид игрока перед открытием меню парикмахера, чтобы можно было отменить все изменения при нажатии отмены, предотвращая случайные изменения, которые могут стоить игроку денег.
Система наложения татуировок
Татуировки в GTA V используют систему decoration, которая накладывает текстурные оверлеи на модель педа с помощью пар коллекций и оверлеев. Каждая татуировка определяется именем коллекции (например, "mpairraces_overlays") и именем оверлея (например, "MP_Airraces_Tattoo_000_M"), которые вместе идентифицируют конкретный тату-ассет. Наносите татуировки с помощью AddPedDecorationFromHashes и удалите все татуировки с помощью ClearPedDecorations. Сложность в том, что нет нативной функции для удаления одной татуировки, поэтому при удалении одной татуировки нужно очистить все украшения и заново применить все, кроме удаленной. Это значит, что вы должны хранить полный список текущих татуировок игрока в вашей модели данных. Вот как управлять коллекцией и применением татуировок:
-- Tattoo database structure
local TattooZones = {
head = {label = 'Head', camera = 'face'},
torso = {label = 'Torso', camera = 'torso'},
left_arm = {label = 'Left Arm', camera = 'torso'},
right_arm = {label = 'Right Arm', camera = 'torso'},
left_leg = {label = 'Left Leg', camera = 'legs'},
right_leg = {label = 'Right Leg', camera = 'legs'},
back = {label = 'Back', camera = 'torso'}
}
-- Available tattoos per zone (abbreviated, real list has hundreds)
Config.Tattoos = {
torso = {
{collection = 'mpairraces_overlays', overlay = 'MP_Airraces_Tattoo_000_M',
label = 'Wing Design', price = 500, zone = 'torso'},
{collection = 'mpbiker_overlays', overlay = 'MP_MP_Biker_Tat_000_M',
label = 'Flame Skull', price = 750, zone = 'torso'},
},
left_arm = {
{collection = 'mpchristmas2_overlays', overlay = 'MP_Xmas2_M_Tat_000',
label = 'Sleeve Art', price = 600, zone = 'left_arm'},
}
}
function ApplyAllTattoos(ped, tattooList)
ClearPedDecorations(ped)
for _, tattoo in ipairs(tattooList) do
local collectionHash = GetHashKey(tattoo.collection)
local overlayHash = GetHashKey(tattoo.overlay)
AddPedDecorationFromHashes(ped, collectionHash, overlayHash)
end
end
function PreviewTattoo(ped, tattoo, currentTattoos)
local previewList = {}
for _, t in ipairs(currentTattoos) do
table.insert(previewList, t)
end
table.insert(previewList, tattoo)
ApplyAllTattoos(ped, previewList)
end
function RemoveTattoo(ped, tattooIndex, currentTattoos)
local newList = {}
for i, t in ipairs(currentTattoos) do
if i ~= tattooIndex then
table.insert(newList, t)
end
end
ApplyAllTattoos(ped, newList)
return newList
end
Каталог татуировок в GTA V содержит сотни записей, разбросанных по нескольким коллекциям DLC. Организуйте их по зонам тела в вашей конфигурации, чтобы NUI мог фильтровать по области. Для каждой татуировки проверьте, следует ли использовать мужской или женский вариант в зависимости от модели педа игрока, так как большинство тату имеют отдельные имена ассетов с окончанием _M или _F. Показывайте превью при наведении курсора на тату в каталоге и взимайте плату только после подтверждения выбора игроком.
Система макияжа и раскраски лица
Макияж использует систему наложений головы по нескольким индексам: румяна (5), помада (8) и наложения волос на груди или тона кожи для дополнительных эффектов. Каждый тип макияжа имеет несколько вариантов стилей и ползунок непрозрачности, контролирующий интенсивность. Создайте раздел макияжа в NUI парикмахерской, который показывает каждую категорию с визуальными образцами доступных цветов. Система цветов для макияжа использует SetPedHeadOverlayColor с параметром типа цвета: тип 0 для стандартных цветов, тип 1 для цветов волос (используется для бороды и бровей) и тип 2 для цветов макияжа. Макияж специально использует тип цвета 2, который предоставляет палитру косметических цветов, включая различные оттенки кожи, красные, розовые и драматические цвета. Добавьте ползунок непрозрачности для каждого компонента макияжа, чтобы игроки могли добиться как тонких естественных образов, так и ярких драматических стилей. Сохраняйте все настройки макияжа вместе с данными внешности игрока, чтобы они сохранялись между сессиями, и предложите взаимодействие с зеркалом в доме игрока, где можно корректировать макияж без посещения магазина.
Платежная система и ценообразование
Система оплаты должна отслеживать изменения игрока за сессию и рассчитывать общую стоимость на основе оказанных услуг. При открытии меню магазина снимайте снимок текущего внешнего вида. По мере изменений отслеживайте каждое в корзине сессии, накапливающей итог. При подтверждении и выходе списывайте общую сумму за все изменения. При отмене возвращайте снимок. Реализуйте ступенчатое ценообразование: базовые стрижки дешевле премиальных, а цены на тату зависят от размера и сложности. Вот система отслеживания сессии и оплаты:
local sessionChanges = {}
local originalAppearance = nil
function StartCustomizationSession()
local ped = PlayerPedId()
originalAppearance = CaptureFullAppearance(ped)
sessionChanges = {}
end
function TrackChange(category, item, price)
-- Remove previous change in same category if exists
for i = #sessionChanges, 1, -1 do
if sessionChanges[i].category == category then
table.remove(sessionChanges, i)
end
end
table.insert(sessionChanges, {
category = category,
item = item,
price = price
})
UpdateCartUI()
end
function UpdateCartUI()
local total = 0
local items = {}
for _, change in ipairs(sessionChanges) do
total = total + change.price
table.insert(items, {
label = change.category .. ': ' .. change.item,
price = change.price
})
end
SendNUIMessage({
action = 'updateCart',
items = items,
total = total
})
end
function ConfirmAndPay()
local total = 0
for _, change in ipairs(sessionChanges) do
total = total + change.price
end
TriggerServerEvent('appearance:server:pay', total, sessionChanges)
end
function CancelSession()
if originalAppearance then
RestoreFullAppearance(PlayerPedId(), originalAppearance)
end
sessionChanges = {}
DestroyPreviewCamera()
end
На стороне сервера проверяйте, что у игрока достаточно средств перед применением изменений внешности. Если оплата не проходит, уведомьте клиента об отмене изменений. Рассмотрите возможность пакетных предложений, где стрижка и подравнивание бороды вместе стоят дешевле суммы отдельных услуг, стимулируя игроков использовать полный спектр услуг за один визит.
Сохранение нарядов и интеграция гардероба
Расширьте систему парикмахера и татуировок функцией сохранения образов, позволяющей игрокам хранить полные конфигурации внешнего вида как именованные пресеты. Каждый пресет фиксирует полное состояние внешности игрока, включая причёску и цвет волос, бороду, макияж и все активные татуировки. Игроки могут переключаться между сохранёнными образами у домашнего гардероба или при повторном посещении магазина. Храните образы в базе данных, связанной с ID гражданина игрока, в JSON-блоке, содержащем все данные внешности. Ограничьте количество сохранённых образов на игрока или предложите дополнительные слоты как премиум-функцию. Взаимодействие с гардеробом дома должно представлять простой NUI-панель со списком сохранённых образов и функцией предпросмотра, которая временно применяет каждый образ, чтобы игрок мог увидеть его перед выбором. Это связывает систему персонального внешнего вида с системой жилья, делая обе функции более ценными вместе, чем по отдельности.
Местоположения магазинов и интеграция NPC парикмахера
Определите несколько локаций магазинов по всей карте, каждая из которых может иметь разные доступные услуги и множители цен. Бюджетный парикмахер на южной стороне может предлагать базовые стрижки по низким ценам, в то время как элитный салон в Vinewood взимает премиальные ставки, но предлагает эксклюзивные стили и цвета. Используйте систему target или маркеры близости для вызова меню магазина, когда игрок подходит к парикмахерскому креслу или тату-станции. Посадите игрока в кресло с помощью TaskStartScenarioInPlace с сценарием сидения для парикмахерских или заставляйте их стоять в определённом месте для тату-сессий. Спавните NPC-парикмахеров или тату-мастеров в каждом месте, используя подходящие модели педов, и назначайте им анимации простоя, чтобы они выглядели занятыми работой. Когда игрок взаимодействует с магазином, NPC может проигрывать анимацию работы рядом с игроком, имитируя выполнение услуги, добавляя визуальное погружение в процесс. После сессии NPC возвращается в позицию простоя. Отслеживайте доход каждого магазина в базе данных, чтобы поддерживать игровой процесс с магазинами, принадлежащими игрокам, где предприниматели покупают и управляют собственными парикмахерскими или тату-салонами, устанавливая цены и нанимая NPC или даже других игроков в качестве сотрудников.