Zurück zum Blog
Tutorial10 Min. Lesezeit

FiveM Tattoo- und Friseursystem: Anpassungsskripte

Ergänze deine FiveM-Stadt um Tattoo- und Friseursalons. Entdecke Skripte mit Kamerawinkeln, Benutzeroberflächen und Terminabläufen.

Agency Scripts

Gründer und leitender Entwickler bei Agency Scripts

Ped-Appearance-Komponenten verstehen

Für ein Tattoo- und Friseursystem in FiveM musst du verstehen, wie GTA V das Aussehen eines Peds (Charakters) verarbeitet. Das Appearance-System ist in mehrere Komponenten-Kategorien unterteilt: Kopf-Overlays für Merkmale wie Hautunreinheiten, Gesichtsbehaarung, Augenbrauen und Make-up, Drawable-Komponenten für Kleidung und Frisuren sowie Tattoo-Dekorationen, die über dem Ped-Modell liegen. Jeder Komponententyp verwendet andere Native-Funktionen und ein eigenes Indexsystem. Kopf-Overlays verwenden die Indizes 0-12 für Merkmale wie Hautunreinheiten (0), Gesichtsbehaarung (1), Augenbrauen (2), Alterung (3), Make-up (4), Rouge (5) und Lippenstift (8). Frisuren werden über das Drawable-Komponentensystem am Index 2 für Kopfhaare festgelegt. Tattoos verwenden ein separates Dekorationssystem mit Collection-Namen und Hash-Werten. Bevor du die UI erstellst, musst du alle verfügbaren Optionen für jede Komponente erfassen, einschließlich der Anzahl der Varianten für das aktuelle Ped-Modell, da männliche und weibliche Charaktere unterschiedliche verfügbare Styles haben. Verwende GetNumHeadOverlayValues(overlayIndex) um zur Laufzeit abzufragen, wie viele Optionen für jeden Overlay-Typ verfügbar sind.

Kamerasystem für die Charaktervorschau

Ein gutes Friseur- und Tattoo-Erlebnis hängt davon ab, dass Spieler ihre Auswahl in Echtzeit sehen können. Erstelle ein eigenes Kamerasystem, das beim Durchsehen der Optionen den jeweiligen Körperbereich in den Fokus rückt. Beim Bearbeiten der Haare zoomst du auf den Kopf. Bei Tattoos am Oberkörper ziehst du die Kamera zurück, damit der obere Körper sichtbar ist. Bei Tattoos an den Beinen richtest du die Kamera auf den unteren Körperbereich aus. Verwende Scripted-Camera-Natives, um beim Wechsel zwischen Kategorien sanfte Übergänge zwischen den Perspektiven zu erzeugen. Hier ist ein Kamerasystem, das mehrere Zoomstufen und Körperbereiche abdeckt:

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

Füge Unterstützung für die Rotation per Maus oder Analogstick hinzu, damit Spieler ihren Charakter bei der Vorschau drehen können. Erfasse Mausbewegungen, wenn die NUI sie nicht verarbeitet, und wende sie als Heading-Rotation auf den Spieler-Ped an. So können Spieler ihr Tattoo vor dem Kauf aus mehreren Blickwinkeln ansehen, was das Einkaufserlebnis deutlich verbessert. Fixiere den Spieler-Ped während der Anpassung und spiele eine Idle-Animation ab, damit der Charakter beim Durchsehen der Optionen natürlich wirkt.

Anpassung von Haaren und Bart

Die Anpassung von Haaren und Bart verwendet jeweils das Drawable-Komponenten- und das Kopf-Overlay-System. Frisuren werden mit SetPedComponentVariation über den Komponentenindex 2 (Haare) und Index 1 (Textur-/Farbvariante) festgelegt. Die Haarfarbe wird separat mit SetPedHairColor angewendet. Die Funktion erwartet einen Index für die Grundfarbe und einen für die Strähnenfarbe. Bartstyles verwenden das Kopf-Overlay-System am Index 1. Ein Deckkraftwert steuert, wie dicht oder dünn die Gesichtsbehaarung wirkt. Gestalte die NUI so, dass sie Vorschaubilder oder Namen für die verfügbaren Styles zeigt und den Ped in Echtzeit aktualisiert, während der Spieler die Optionen durchgeht. Hier ist die Lua-Logik, um Änderungen an Haaren und Bart mit einer Echtzeitvorschau anzuwenden:

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

Die NUI sollte Haarfarben als visuelle Palette statt als nummerierte Indizes anzeigen und die internen Farbindizes von GTA ihren tatsächlichen RGB-Werten zuordnen. Füge einen Regler für die Bartdeckkraft hinzu, mit dem Spieler die Dichte ihrer Gesichtsbehaarung fein abstimmen können, von leichter Stoppel bis zu einem vollen, dichten Bart. Speichere das vorherige Aussehen des Spielers, bevor du das Friseurmenü öffnest, damit du alle Änderungen rückgängig machen kannst, wenn er auf Abbrechen klickt. So kosten versehentliche Änderungen den Spieler kein Geld.

Tattoo-Overlay-System

Tattoos in GTA V verwenden das Dekorationssystem, das Textur-Overlays mithilfe von Collection- und Overlay-Hash-Paaren auf das Ped-Modell legt. Jedes Tattoo wird durch einen Collection-Namen (wie "mpairraces_overlays") und einen Overlay-Namen (wie "MP_Airraces_Tattoo_000_M") definiert, die gemeinsam das jeweilige Tattoo-Asset identifizieren. Wende Tattoos mit AddPedDecorationFromHashes an und entferne alle Tattoos mit ClearPedDecorations. Der schwierige Teil ist, dass es keine Native-Funktion zum Entfernen eines einzelnen Tattoos gibt. Wenn ein Spieler ein Tattoo entfernen möchte, musst du daher alle Dekorationen löschen und alles außer dem entfernten Tattoo erneut anwenden. Das bedeutet, dass du in deinem Datenmodell eine vollständige Liste der aktuellen Tattoos des Spielers führen musst. So verwaltest du die Tattoo-Collection und wendest sie an:

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

Der Tattoo-Katalog in GTA V enthält Hunderte von Einträgen aus verschiedenen DLC-Sammlungen. Ordne sie in deiner Konfiguration nach Körperbereichen, damit die NUI nach Bereichen filtern kann. Prüfe für jedes Tattoo anhand des Ped-Modells des Spielers, ob die männliche oder weibliche Variante verwendet werden soll, da die meisten Tattoos separate Asset-Namen haben, die auf _M oder _F enden. Zeige eine Vorschau an, wenn der Spieler im Katalog den Mauszeiger über ein Tattoo bewegt, und berechne erst dann Kosten, wenn er die Auswahl bestätigt.

Makeup- und Face-Paint-System

Makeup nutzt das Head-Overlay-System über mehrere Indizes hinweg: Rouge (5), Lippenstift (8) sowie Brustbehaarungs- oder Teint-Overlays für zusätzliche Effekte. Jede Makeup-Art bietet mehrere Stiloptionen und einen Deckkraftregler, der die Intensität steuert. Erstelle im NUI des Friseursalons einen Makeup-Bereich, in dem jede Kategorie mit visuellen Farbfeldern die verfügbaren Farben zeigt. Das Farbsystem für Makeup verwendet SetPedHeadOverlayColor einen Farbtyp-Parameter: Typ 0 für Standardfarben, Typ 1 für Haarfarben (für Bärte und Augenbrauen) und Typ 2 für Makeup-Farben. Makeup verwendet ausdrücklich Farbtyp 2, der eine Palette kosmetischer Farben mit verschiedenen Hauttönen, Rot-, Rosa- und kräftigen Farben bietet. Füge für jede Makeup-Komponente einen Deckkraftregler hinzu, damit Spieler dezente, natürliche Looks oder auffällige, dramatische Styles gestalten können. Speichere alle Makeup-Einstellungen zusammen mit den Aussehensdaten des Spielers, damit sie zwischen Sessions erhalten bleiben, und biete eine Spiegel-Interaktion im Zuhause des Spielers an, über die er sein Makeup anpassen kann, ohne den Salon aufzusuchen.

Zahlungssystem und Preise

Das Zahlungssystem muss erfassen, was der Spieler während seiner Session geändert hat, und die Gesamtkosten anhand der erbrachten Leistungen berechnen. Erstelle beim Öffnen des Menüs im Salon einen Snapshot des aktuellen Aussehens. Erfasse jede Änderung in einem Session-Warenkorb, der die Gesamtsumme laufend erhöht. Wenn der Spieler bestätigt und den Salon verlässt, berechne ihm die Kosten für alle Änderungen. Bricht er ab, stelle den Snapshot wieder her. Implementiere gestaffelte Preise, bei denen einfache Haarschnitte weniger kosten als Premium-Styles und die Tattoo-Preise je nach Größe und Komplexität variieren. Hier ist ein System zur Session-Erfassung und Bezahlung:

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

Prüfe serverseitig, ob der Spieler über ausreichend Geld verfügt, bevor du die Aussehensänderungen übernimmst. Schlägt die Zahlung fehl, benachrichtige den Client, damit er die Änderungen rückgängig macht. Biete Paketpreise an, bei denen ein gemeinsamer Termin für Haarschnitt und Barttrimmen weniger kostet als die Summe der Einzelleistungen. So motivierst du Spieler, bei einem Besuch das gesamte Serviceangebot zu nutzen.

Outfits speichern und Garderobe integrieren

Erweitere das Friseur- und Tattoosystem um eine Funktion zum Speichern von Outfits, mit der Spieler vollständige Aussehenskonfigurationen als benannte Presets speichern können. Jedes Preset erfasst den gesamten Zustand des Aussehens des Spielers, einschließlich Frisur und Haarfarbe, Gesichtsbehaarung, Makeup und aller aktiven Tattoos. Spieler können dann in der Garderobe ihres Zuhauses oder bei einem erneuten Besuch im Salon zwischen gespeicherten Looks wechseln. Speichere Outfits in der Datenbank, verknüpft mit der Citizen-ID des Spielers, und verwende dafür einen JSON-Block mit allen Aussehensdaten. Begrenze die Anzahl der Outfits, die pro Spieler gespeichert werden können, oder biete zusätzliche Slots als Premium-Funktion an. Die Garderoben-Interaktion im Zuhause des Spielers sollte ein einfaches NUI-Panel mit einer Liste gespeicherter Outfits und einer Vorschaufunktion anzeigen. Diese wendet den jeweiligen Look vorübergehend an, damit der Spieler ihn vor der endgültigen Auswahl sehen kann. So wird das System für das persönliche Aussehen mit dem Housing-System verknüpft und beide Funktionen sind zusammen wertvoller als einzeln.

Salonstandorte und NPC-Friseure integrieren

Lege mehrere Salonstandorte auf der Karte fest, die jeweils unterschiedliche verfügbare Leistungen und Preisaufschläge haben können. Ein günstiger Friseur im Süden könnte einfache Haarschnitte zu niedrigen Preisen anbieten, während ein gehobener Salon in Vinewood Premiumpreise verlangt, dafür aber exklusive Styles und Farben anbietet. Verwende das Target-System oder Proximity-Marker, um das Salonmenü auszulösen, wenn sich ein Spieler dem Friseurstuhl oder der Tattoostation nähert. Lass den Spieler mit TaskStartScenarioInPlace einem Sitz-Szenario in Friseursalons auf dem Stuhl Platz nehmen oder ihn bei Tattoos an einer bestimmten Stelle stehen. Erzeuge an jedem Standort NPC-Friseure oder Tätowierer mit passenden Ped-Modellen und weise ihnen Idle-Animationen zu, die sie bei der Arbeit zeigen. Wenn ein Spieler mit dem Salon interagiert, kann der NPC in der Nähe des Spielers eine Arbeitsanimation abspielen, um die Dienstleistung realistisch darzustellen und das Eintauchen in die Spielwelt zu verstärken. Nach der Session kehrt der NPC an seine Idle-Position zurück. Erfasse die Einnahmen pro Salon in der Datenbank, um Gameplay mit spielergeführten Geschäften zu ermöglichen, bei dem Unternehmer eigene Friseursalons oder Tattoostudios kaufen und verwalten sowie Preise festlegen und NPC-Mitarbeiter oder sogar andere Spieler einstellen können.

Bereit loszulegen?

Hol dir die Scripts in unserem Shop, oder komm auf den Discord für Support, Updates und einen Blick auf das, was kommt.