Understanding Ped Appearance Components
Building a tattoo and barber shop system in FiveM requires a solid understanding of how GTA V handles ped (character) appearance. The appearance system is divided into several component categories: head overlays for features like blemishes, facial hair, eyebrows, and makeup, drawable components for clothing and hair styles, and tattoo decorations that layer on top of the ped model. Each component type uses different native functions and follows its own indexing system. Head overlays use indices 0-12 to control features like blemishes (0), facial hair (1), eyebrows (2), ageing (3), makeup (4), blush (5), and lipstick (8). Hair styles are set through the drawable component system at index 2 for head hair. Tattoos use a completely separate decoration system with collection names and hash values. Before building the UI, you need to map out all available options for each component, including the number of variations available for the current ped model, because male and female characters have different sets of available styles. Use GetNumHeadOverlayValues(overlayIndex) to query how many options exist for each overlay type at runtime.
Camera System for Character Preview
A great barber and tattoo experience depends on the player being able to see what they are selecting in real time. Build a custom camera system that focuses on the relevant body part as the player browses through options. When editing hair, zoom the camera to the head. When selecting tattoos for the torso, pull the camera back to show the upper body. When working on leg tattoos, position the camera to frame the lower body. Use scripted camera natives to create smooth transitions between viewpoints as the player switches between categories. Here is a camera management system that handles multiple zoom levels and body focus areas:
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
Add mouse or analog stick rotation support so players can spin their character while previewing changes. Capture mouse movement when the NUI is not consuming it and apply it as heading rotation to the player ped. This lets players examine their tattoo from multiple angles before committing to the purchase, significantly improving the shopping experience. Freeze the player ped in place during the customization session and play an idle animation to keep the character looking natural while the player browses.
Hair and Beard Customization
Hair and beard customization uses the drawable component and head overlay systems respectively. Hair styles are set with SetPedComponentVariation using component index 2 (hair) and index 1 (texture/color variation). Hair color is applied separately using SetPedHairColor which takes a primary color index and a highlight color index. Beard styles use the head overlay system at index 1 with an opacity value that controls how thick or thin the facial hair appears. Build the NUI to show thumbnails or names for each available style, updating the ped in real time as the player clicks through options. Here is the Lua logic for applying hair and beard changes with real-time preview:
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
The NUI interface should present hair colors as a visual palette rather than numbered indices, mapping GTA's internal color indices to their actual RGB representations. Include a slider for beard opacity that lets players fine-tune the thickness of their facial hair from a light stubble to a full thick beard. Store the player's previous appearance before opening the barber menu so you can revert all changes if they click cancel, preventing accidental changes from costing the player money.
Tattoo Overlay System
Tattoos in GTA V use the decoration system, which layers texture overlays onto the ped model using collection and overlay hash pairs. Each tattoo is defined by a collection name (like "mpairraces_overlays") and an overlay name (like "MP_Airraces_Tattoo_000_M") that together identify the specific tattoo asset. Apply tattoos with AddPedDecorationFromHashes and remove all tattoos with ClearPedDecorations. The tricky part is that there is no native to remove a single tattoo, so when a player wants to remove one tattoo, you need to clear all decorations and reapply everything except the removed one. This means you must maintain a complete list of the player's current tattoos in your data model. Here is how to manage the tattoo collection and application:
-- 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
The tattoo catalog in GTA V contains hundreds of entries spread across multiple DLC collections. Organize them by body zone in your configuration so the NUI can filter by area. For each tattoo, check whether the male or female variant should be used based on the player's ped model, as most tattoos have separate asset names ending in _M or _F. Show a preview when the player hovers over a tattoo in the catalog, and only charge them when they confirm the selection.
Makeup and Face Paint System
Makeup uses the head overlay system across several indices: blush (5), lipstick (8), and chest hair or complexion overlays for additional effects. Each makeup type has multiple style options and an opacity slider that controls intensity. Build a makeup section in the barber shop NUI that presents each category with visual swatches showing the available colors. The color system for makeup uses SetPedHeadOverlayColor with a color type parameter: type 0 for default colors, type 1 for hair colors (used for beards and eyebrows), and type 2 for makeup colors. Makeup specifically uses color type 2 which provides a palette of cosmetic colors including various skin tones, reds, pinks, and dramatic colors. Add an opacity slider for each makeup component so players can achieve subtle natural looks or bold dramatic styles. Store all makeup settings alongside the player's appearance data so they persist between sessions, and offer a mirror interaction in the player's home where they can adjust makeup without visiting the shop.
Payment System and Pricing
The payment system needs to track what the player changed during their session and calculate the total cost based on the services rendered. When a player opens the shop menu, snapshot their current appearance. As they make changes, track each modification in a session cart that accumulates the total. When they confirm and exit, charge them the total for all changes made. If they cancel, revert to the snapshot. Implement tiered pricing where basic haircuts cost less than premium styles, and where tattoo prices vary based on size and complexity. Here is a session tracking and payment system:
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
On the server side, validate that the player has sufficient funds before committing the appearance changes. If payment fails, notify the client to revert the changes. Consider offering package deals where getting a haircut and beard trim together costs less than the sum of individual services, incentivizing players to use the full range of services in a single visit.
Outfit Saving and Wardrobe Integration
Extend the barber and tattoo system with an outfit saving feature that lets players store complete appearance configurations as named presets. Each preset captures the full state of the player's appearance including hair style and color, facial hair, makeup, and all active tattoos. Players can then switch between saved looks at their home wardrobe or by revisiting the shop. Store outfits in the database linked to the player's citizen ID with a JSON blob containing all appearance data. Limit the number of saved outfits per player or offer additional slots as a premium feature. The wardrobe interaction at the player's home should present a simple NUI panel listing saved outfits with a preview function that temporarily applies each look so the player can see it before committing. This ties the personal appearance system into the housing system, making both features more valuable together than they are separately.
Shop Locations and NPC Barber Integration
Define multiple shop locations across the map, each potentially with different available services and price multipliers. A budget barber in the south side might offer basic cuts at low prices, while a high-end salon in Vinewood charges premium rates but offers exclusive styles and colors. Use the target system or proximity markers to trigger the shop menu when a player approaches the barber chair or tattoo station. Seat the player in the chair using TaskStartScenarioInPlace with a sitting scenario for barber shops, or have them stand on a specific spot for tattoo sessions. Spawn NPC barbers or tattoo artists at each location using appropriate ped models and assign them idle animations that make them look like they are working. When a player interacts with the shop, the NPC can play a working animation near the player to simulate the service being performed, adding visual immersion to the experience. After the session, the NPC returns to their idle position. Track per-shop revenue in the database to support player-owned shop gameplay where entrepreneurs purchase and manage their own barber shops or tattoo parlors, setting prices and hiring NPC staff or even other players as employees.