Wróć do bloga
Tutorial10 min czytania

System tatuaży i fryzjera w FiveM: skrypty personalizacji

Dodaj salony tatuażu i barber shopy do swojego miasta FiveM. Kąty kamery, interfejsy UI, system wizyt i najlepsze skrypty personalizacji dla roleplay.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Zrozumienie komponentów wyglądu pedów

Stworzenie systemu salonu tatuażu i fryzjera w FiveM wymaga solidnego zrozumienia tego, jak GTA V obsługuje wygląd peda (postaci). System wyglądu jest podzielony na kilka kategorii komponentów: head overlays dla cech takich jak niedoskonałości, zarost, brwi i makijaż, komponenty drawable dla ubrań i fryzur oraz dekoracje tatuaży nakładane na model peda. Każdy typ komponentu używa innych funkcji natywnych i posiada własny system indeksowania. Head overlays używają indeksów 0-12 do kontrolowania cech takich jak niedoskonałości (0), zarost (1), brwi (2), starzenie się (3), makijaż (4), róż (5) i szminka (8). Fryzury są ustawiane za pomocą systemu komponentów drawable pod indeksem 2 dla włosów na głowie. Tatuaże używają całkowicie oddzielnego systemu dekoracji z nazwami kolekcji i wartościami hash. Przed zbudowaniem UI musisz zmapować wszystkie dostępne opcje dla każdego komponentu, w tym liczbę wariantów dostępnych dla bieżącego modelu peda, ponieważ postacie męskie i żeńskie mają różne zestawy dostępnych stylów. Użyj GetNumHeadOverlayValues(overlayIndex) aby sprawdzić w czasie działania, ile opcji istnieje dla każdego typu overlay.

System kamery do podglądu postaci

Świetny system fryzjera i tatuaży zależy od tego, czy gracz widzi na bieżąco to, co wybiera. Zbuduj niestandardowy system kamer, który skupia się na odpowiedniej części ciała podczas przeglądania opcji przez gracza. Podczas edycji fryzury przybliż kamerę do głowy. Wybierając tatuaże na klatce piersiowej, oddal kamerę, aby pokazać górną część ciała. Pracując nad tatuażami na nogach, ustaw kamerę tak, aby kadrowała dolną część ciała. Użyj natywnych funkcji kamery, aby stworzyć płynne przejścia między punktami widzenia, gdy gracz przełącza się między kategoriami. Oto system zarządzania kamerami, który obsługuje wiele poziomów przybliżenia i obszarów ciała:

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

Dodaj obsługę obracania myszą lub gałką analogową, aby gracze mogli obracać swoją postać podczas podglądu zmian. Przechwytuj ruch myszy, gdy NUI go nie przetwarza, i stosuj go jako obrót heading dla peda gracza. Pozwala to graczom obejrzeć tatuaż pod różnymi kątami przed sfinalizowaniem zakupu, co znacząco poprawia wrażenia z zakupów. Zamroź peda gracza w miejscu podczas sesji personalizacji i odtwórz animację idle, aby postać wyglądała naturalnie podczas przeglądania.

Personalizacja włosów i brody

Personalizacja włosów i brody korzysta odpowiednio z komponentów drawable oraz systemu head overlay. Fryzury ustawia się za pomocą SetPedComponentVariation przy użyciu indeksu komponentu 2 (włosy) i indeksu 1 (wariant tekstury/koloru). Kolor włosów jest nakładany osobno za pomocą SetPedHairColor który przyjmuje indeks koloru głównego oraz indeks koloru pasemek. Style brody wykorzystują system head overlay o indeksie 1 z wartością przezroczystości kontrolującą gęstość zarostu. Zbuduj NUI tak, aby pokazywało miniaturki lub nazwy dla każdego dostępnego stylu, aktualizując peda w czasie rzeczywistym podczas klikania kolejnych opcji. Oto logika Lua do wprowadzania zmian włosów i brody z podglądem w czasie rzeczywistym:

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

Interfejs NUI powinien prezentować kolory włosów jako paletę wizualną, a nie indeksy numeryczne, mapując wewnętrzne indeksy kolorów GTA na ich rzeczywiste odpowiedniki RGB. Dodaj suwak krycia zarostu, który pozwoli graczom precyzyjnie dostosować gęstość zarostu: od lekkiego zarostu po pełną, gęstą brodę. Zapisz poprzedni wygląd gracza przed otwarciem menu fryzjera, aby móc cofnąć wszystkie zmiany, jeśli kliknie Anuluj, chroniąc go przed niepotrzebną utratą pieniędzy przez pomyłkę.

System nakładania tatuaży

Tatuaże w GTA V korzystają z systemu dekoracji, który nakłada tekstury na model peda za pomocą par hashy kolekcji i nakładki. Każdy tatuaż jest zdefiniowany przez nazwę kolekcji (np. "mpairraces_overlays") oraz nazwę nakładki (np. "MP_Airraces_Tattoo_000_M"), które wspólnie identyfikują konkretny zasób tatuażu. Tatuaże aplikuje się za pomocą AddPedDecorationFromHashes i usuń wszystkie tatuaże za pomocą ClearPedDecorations. Trudność polega na tym, że nie ma funkcji natywnej do usuwania pojedynczego tatuażu, więc gdy gracz chce usunąć jeden tatuaż, musisz wyczyścić wszystkie dekoracje i nałożyć ponownie wszystko oprócz usuniętego. Oznacza to, że musisz utrzymywać pełną listę aktualnych tatuaży gracza w swoim modelu danych. Oto jak zarządzać kolekcją i nakładaniem tatuaży:

-- 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

Katalog tatuaży w GTA V zawiera setki pozycji rozproszonych w wielu kolekcjach DLC. Uporządkuj je według stref ciała w konfiguracji, aby NUI mogło filtrować według obszaru. Dla każdego tatuażu sprawdź, czy należy użyć wariantu męskiego czy damskiego na podstawie modelu ped gracza, ponieważ większość tatuaży ma oddzielne nazwy assetów kończące się na _M lub _F. Pokazuj podgląd, gdy gracz najedzie kursorem na tatuaż w katalogu, i pobieraj opłatę dopiero po potwierdzeniu wyboru.

System makijażu i malowania twarzy

Makijaż korzysta z systemu head overlay w kilku indeksach: róż (5), szminka (8) oraz owłosienie klatki piersiowej lub nakładki cery dla dodatkowych efektów. Każdy typ makijażu oferuje wiele stylów oraz suwak przezroczystości kontrolujący intensywność. Zbuduj sekcję makijażu w NUI barber shopu, która prezentuje każdą kategorię z wizualnymi próbkami dostępnych kolorów. System kolorów dla makijażu wykorzystuje SetPedHeadOverlayColor z parametrem typu koloru: typ 0 dla kolorów domyślnych, typ 1 dla kolorów włosów (używany do brody i brwi) oraz typ 2 dla kolorów makijażu. Makijaż wykorzystuje konkretnie typ koloru 2, który zapewnia paletę kolorów kosmetycznych, w tym różne odcienie skóry, czerwienie, róże i wyraziste barwy. Dodaj suwak przezroczystości dla każdego elementu makijażu, aby gracze mogli uzyskać subtelny, naturalny wygląd lub odważne, wyraziste stylizacje. Przechowuj wszystkie ustawienia makijażu wraz z danymi wyglądu gracza, aby zachowały się między sesjami, oraz zaoferuj interakcję z lustrem w domu gracza, gdzie może poprawić makijaż bez wizyty w sklepie.

System płatności i cennik

System płatności musi śledzić zmiany dokonane przez gracza podczas sesji i obliczać łączny koszt na podstawie wyświadczonych usług. Gdy gracz otwiera menu sklepu, zapisz zrzut jego obecnego wyglądu. W miarę wprowadzania zmian rejestruj każdą modyfikację w koszyku sesji, który sumuje łączną kwotę. Gdy gracz zatwierdzi i wyjdzie, pobierz od niego pełną opłatę za wszystkie wprowadzone zmiany. Jeśli anuluje, przywróć stan ze zrzutu. Wprowadź cennik wielopoziomowy, w którym podstawowe fryzury kosztują mniej niż style premium, a ceny tatuaży zależą od rozmiaru i stopnia skomplikowania. Oto system śledzenia sesji i płatności:

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

Po stronie serwera sprawdź, czy gracz ma wystarczające środki przed zatwierdzeniem zmian wyglądu. Jeśli płatność się nie powiedzie, powiadom klienta o konieczności cofnięcia zmian. Rozważ zaoferowanie pakietów, w których strzyżenie włosów i przycięcie brody w pakiecie kosztuje mniej niż suma pojedynczych usług, co zachęci graczy do skorzystania z pełnego zakresu usług podczas jednej wizyty.

Zapisywanie strojów i integracja z garderobą

Rozbuduj system fryzjera i tatuaży o funkcję zapisywania strojów, która pozwala graczom zapisywać kompletne konfiguracje wyglądu jako nazwane presety. Każdy preset zawiera pełny stan wyglądu gracza, w tym styl i kolor włosów, zarost, makijaż oraz wszystkie aktywne tatuaże. Gracze mogą następnie przełączać się między zapisanymi stylizacjami w domowej szafie lub ponownie odwiedzając salon. Przechowuj stroje w bazie danych powiązane z citizen ID gracza za pomocą obiektu JSON zawierającego wszystkie dane wyglądu. Ogranicz liczbę zapisanych strojów na gracza lub zaoferuj dodatkowe sloty jako funkcję premium. Interakcja z szafą w domu gracza powinna wyświetlać prosty panel NUI z listą zapisanych strojów oraz funkcją podglądu, która tymczasowo nakłada dany wygląd, aby gracz mógł go zobaczyć przed zatwierdzeniem. Łączy to system wyglądu postaci z systemem housing, sprawiając, że obie funkcje stają się razem bardziej wartościowe niż osobno.

Lokalizacje sklepów i integracja fryzjera NPC

Zdefiniuj wiele lokalizacji sklepów na mapie, z których każda może oferować inne usługi i mnożniki cen. Budżetowy fryzjer w południowej części miasta może oferować podstawowe strzyżenia w niskich cenach, podczas gdy ekskluzywny salon w Vinewood pobiera wysokie opłaty, ale oferuje unikalne fryzury i kolory. Użyj systemu target lub markerów zbliżeniowych, aby wywołać menu sklepu, gdy gracz zbliży się do fotela fryzjerskiego lub stanowiska tatuażu. Posadź gracza na fotelu za pomocą TaskStartScenarioInPlace ze scenariuszem siedzenia dla salonów fryzjerskich lub ustaw ich w określonym miejscu na czas sesji tatuażu. Spawnuj fryzjerów lub tatuażystów NPC w każdej lokalizacji przy użyciu odpowiednich modeli pedów i przypisz im animacje bezczynności, dzięki którym wyglądają, jakby pracowali. Kiedy gracz wchodzi w interakcję ze sklepem, NPC może odtworzyć animację pracy w pobliżu gracza, aby zasymulować wykonywanie usługi, co dodaje wizualnej immersji. Po sesji NPC wraca do pozycji bezczynności. Zapisuj przychody każdego lokalu w bazie danych, aby wspierać rozgrywkę ze sklepami prowadzonymi przez graczy, gdzie przedsiębiorcy kupują i zarządzają własnymi salonami fryzjerskimi lub studiami tatuażu, ustalając ceny i zatrudniając personel NPC lub innych graczy jako pracowników.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.