>
Tutoriel 2026-02-03

Menu d'emotes et animations pour FiveM

OntelMonke

OntelMonke

Administrateur et développeur chez Agency Scripts

Comprendre les dictionnaires d'animation GTA V

Les animations du GTA V sont organisées en dictionnaires d'animation, qui sont des collections de clips d'animation associés. Chaque animation est identifiée par deux chaînes : le nom du dictionnaire et le nom de l'animation dans ce dictionnaire. Par exemple, le dictionnaire amb@world_human_hang_out_street@female_hold_arm@idle_a contient des animations inactives pour les pédiatres féminines. Avant de pouvoir lire une animation, tu dois demander le dictionnaire en utilisant RequestAnimDict et attendez qu'il ait fini de charger avec HasAnimDictLoaded. Ne pas demander le dictionnaire en premier est l'une des erreurs les plus courantes commises par les nouveaux développeurs, ce qui entraîne des animations dont la lecture échoue silencieusement. Le GTA V est livré avec des milliers de dictionnaires d'animation couvrant tout, des positions de combat aux poses de yoga, et découvrir ceux qui sont utiles représente la moitié du défi que représente la création d'un menu d'émoticônes complet.

Jouer et gérer des animations

La fonction principale pour lire des animations est TaskPlayAnim, qui accepte le lecteur, le dictionnaire, le nom de l'animation, la vitesse de fusion, la vitesse de fusion, la durée, les indicateurs et la vitesse de lecture. Les drapeaux d'animation contrôlent le comportement critique : le drapeau 1 boucle l'animation, le drapeau 2 arrête l'animation sur la dernière image, le drapeau 16 joue uniquement le haut du corps permettant au joueur de marcher et le drapeau 32 permet la rotation pendant l'animation. La combinaison d'indicateurs avec OU au niveau du bit tu permet de créer des comportements nuancés. Une emote assise a besoin du drapeau 1 pour la boucle plus du drapeau 2 pour conserver la dernière image en cas d'interruption. Une emote agitant fonctionne mieux avec le drapeau 49 qui combine uniquement le haut du corps avec une boucle et une rotation, permettant au joueur de saluer tout en se promenant.

-- Core animation helper functions
local function LoadAnimDict(dict)
    if HasAnimDictLoaded(dict) then return true end
    RequestAnimDict(dict)
    local timeout = GetGameTimer() + 5000
    while not HasAnimDictLoaded(dict) do
        Wait(10)
        if GetGameTimer() > timeout then
            return false
        end
    end
    return true
end

local function PlayEmote(dict, anim, flags, duration)
    local ped = PlayerPedId()
    if IsPedInAnyVehicle(ped, false) then return end
    if not LoadAnimDict(dict) then return end

    flags = flags or 1      -- default: loop
    duration = duration or -1 -- -1 = indefinite

    TaskPlayAnim(ped, dict, anim, 2.0, 2.0, duration, flags, 0, false, false, false)
end

local function StopCurrentEmote()
    local ped = PlayerPedId()
    ClearPedTasks(ped)
    -- Also remove any attached props
    ClearPedSecondaryTask(ped)
end

-- Export for other resources
exports('PlayEmote', PlayEmote)
exports('StopEmote', StopCurrentEmote)

Organiser les émoticônes en catégories

Un menu d'émotes bien organisé regroupe les animations en catégories intuitives que les joueurs peuvent parcourir rapidement. Les catégories courantes incluent Danses, Salutations, Réactions, Poses, Sportif, Scénarios (comme s'asseoir, se pencher ou s'allonger), Émotes d’accessoires (impliquant des objets comme des cigarettes ou des téléphones), Styles de marche, et Expressions faciales. Chaque entrée d'émote doit stocker le dictionnaire, le nom de l'animation, les drapeaux, si elle utilise un accessoire, le hachage du modèle d'accessoire le cas échéant, l'index d'os pour la fixation de l'accessoire et les valeurs de décalage et de rotation pour un placement correct de l'accessoire. Stocker tout cela dans un tableau de configuration structuré permet d'ajouter facilement de nouvelles emotes sans modifier la logique de lecture.

-- Emote configuration (config.lua)
Config = {}

Config.Emotes = {
    dances = {
        {label = 'Dance 1',    dict = 'anim@amb@nightclub@dancers@club_dance_bar_1',    anim = 'high_center',   flags = 1},
        {label = 'Dance 2',    dict = 'anim@amb@nightclub@mini@dance@dance_solo@female@var_a@',  anim = 'high_center',   flags = 1},
        {label = 'Dance 3',    dict = 'anim@amb@nightclub@dancers@crowddance_facedj@',  anim = 'hi_dance_facedj_09_v2_female^1', flags = 1},
        {label = 'Flamenco',   dict = 'special_ped@jessie@idle_latina',                 anim = 'idle_latina',   flags = 1},
    },
    greetings = {
        {label = 'Wave',       dict = 'friends@frj@ig_1',    anim = 'wave_A',         flags = 49, duration = 3000},
        {label = 'Salute',     dict = 'anim@mp_player_intuppersalute',  anim = 'idle_a',  flags = 49, duration = 3000},
        {label = 'Handshake',  dict = 'mp_ped_interaction',  anim = 'handshake_guy_a', flags = 49, duration = 4000},
        {label = 'Fist Bump',  dict = 'mp_ped_interaction',  anim = 'fist_bump',      flags = 49, duration = 3000},
    },
    props = {
        {label = 'Cigarette',  dict = 'amb@world_human_smoking@male@male_a@idle_a', anim = 'idle_c', flags = 49,
            prop = 'prop_cs_ciggy_01', bone = 28422, pos = {0.0, 0.0, 0.0}, rot = {0.0, 0.0, 0.0}},
        {label = 'Coffee',     dict = 'amb@world_human_drinking@coffee@male@idle_a', anim = 'idle_c', flags = 49,
            prop = 'p_amb_coffeecup_01', bone = 28422, pos = {0.0, 0.0, 0.0}, rot = {0.0, 0.0, 0.0}},
        {label = 'Clipboard',  dict = 'amb@world_human_clipboard@male@idle_a', anim = 'idle_c', flags = 49,
            prop = 'p_amb_clipboard_01', bone = 36029, pos = {0.16, 0.08, 0.03}, rot = {-100.0, 0.0, 0.0}},
    },
    scenarios = {
        {label = 'Sit Chair',  scenario = 'PROP_HUMAN_SEAT_CHAIR', isScenario = true},
        {label = 'Lean Wall',  scenario = 'WORLD_HUMAN_LEANING',   isScenario = true},
        {label = 'Guard',      scenario = 'WORLD_HUMAN_GUARD_STAND', isScenario = true},
        {label = 'Jog',        scenario = 'WORLD_HUMAN_JOG_STANDING', isScenario = true},
    },
}

Emotes d'accessoires et pièces jointes d'objets

Les émoticônes d'accessoires impliquent de générer un objet de jeu et de l'attacher à un os spécifique sur le pied du joueur. GTA V possède un système osseux squelettique où chaque os possède un index unique. Les os d'attache courants comprennent 57005 pour la main droite, 18905 pour la main gauche, 28422 pour la zone du doigt droit (idéal pour les cigarettes), et 31086 pour la tête. Après avoir généré l'accessoire avec CreateObject, attachez-le à l'aide AttachEntityToEntity qui nécessite l'entité prop, l'entité ped, l'index osseux, les décalages de position, les décalages de rotation et plusieurs indicateurs booléens pour le comportement de collision et physique. Obtenir les bons décalages de position et de rotation nécessite des essais et des erreurs, et différents modèles de pédales peuvent nécessiter de légers ajustements. Nettoyez toujours les accessoires lorsque l'emote est annulée en suivant le descripteur d'entité généré et en le supprimant.

-- Prop emote system
local currentProp = nil

local function PlayPropEmote(emoteData)
    local ped = PlayerPedId()
    if IsPedInAnyVehicle(ped, false) then return end

    -- Clean up any existing prop
    if currentProp and DoesEntityExist(currentProp) then
        DeleteEntity(currentProp)
        currentProp = nil
    end

    if not LoadAnimDict(emoteData.dict) then return end

    -- Spawn and attach prop
    local model = GetHashKey(emoteData.prop)
    RequestModel(model)
    while not HasModelLoaded(model) do Wait(10) end

    local pos = GetEntityCoords(ped)
    currentProp = CreateObject(model, pos.x, pos.y, pos.z, true, true, false)
    SetModelAsNoLongerNeeded(model)

    local boneIndex = GetPedBoneIndex(ped, emoteData.bone)
    AttachEntityToEntity(
        currentProp, ped, boneIndex,
        emoteData.pos[1], emoteData.pos[2], emoteData.pos[3],
        emoteData.rot[1], emoteData.rot[2], emoteData.rot[3],
        true, true, false, true, 1, true
    )

    -- Play animation
    TaskPlayAnim(ped, emoteData.dict, emoteData.anim,
        2.0, 2.0, -1, emoteData.flags or 49, 0, false, false, false)
end

-- Cleanup on emote cancel
local function CleanupProps()
    if currentProp and DoesEntityExist(currentProp) then
        DeleteEntity(currentProp)
        currentProp = nil
    end
end

Styles de marche et clips de mouvement

Les styles de marche modifient la façon dont le personnage du joueur se déplace et sont appliqués à l'aide de SetPedMovementClipset. Contrairement aux animations classiques qui remplacent les actions, les jeux de clips de mouvement persistent dans tous les états de locomotion, y compris la marche, la course et le sprint. GTA V comprend des dizaines de jeux de clips de mouvement intégrés tels que MOVE_M@DRUNK@MODERATEDRUNK pour un trébuchement ivre, MOVE_M@GANGSTER@NG pour un fanfaronnade de gangster, MOVE_M@CONFIDENT pour une foulée confiante, et MOVE_F@HEELS@C pour marcher avec des talons hauts. Comme les dictionnaires d'animation, les jeux de clips doivent être demandés et chargés avant l'application. Fournissez une option de réinitialisation afin que les joueurs puissent revenir à la marche par défaut. Stockez le style de marche préféré du joueur dans les données de son personnage afin qu'il persiste au fil des sessions, en le réappliquant automatiquement lorsqu'il se connecte.

-- Walking style system
local currentWalkStyle = nil

Config.WalkStyles = {
    {label = 'Default',     clipset = nil},
    {label = 'Drunk',       clipset = 'MOVE_M@DRUNK@MODERATEDRUNK'},
    {label = 'Gangster',    clipset = 'MOVE_M@GANGSTER@NG'},
    {label = 'Confident',   clipset = 'MOVE_M@CONFIDENT'},
    {label = 'Tough',       clipset = 'MOVE_M@TOUGH_GUY@'},
    {label = 'Femme',       clipset = 'MOVE_F@FEMME@'},
    {label = 'Heels',       clipset = 'MOVE_F@HEELS@C'},
    {label = 'Injured',     clipset = 'move_m@injured'},
    {label = 'Hobo',        clipset = 'MOVE_M@HOBO@A'},
    {label = 'Brave',       clipset = 'MOVE_M@BRAVE@A'},
}

local function SetWalkStyle(clipset)
    local ped = PlayerPedId()
    ResetPedMovementClipset(ped, 0.2)

    if not clipset then
        currentWalkStyle = nil
        return
    end

    RequestClipSet(clipset)
    local timeout = GetGameTimer() + 3000
    while not HasClipSetLoaded(clipset) do
        Wait(10)
        if GetGameTimer() > timeout then return end
    end

    SetPedMovementClipset(ped, clipset, 0.2)
    currentWalkStyle = clipset
end

exports('SetWalkStyle', SetWalkStyle)
exports('GetWalkStyle', function() return currentWalkStyle end)

Expressions faciales

Les expressions faciales ajoutent une profondeur émotionnelle subtile aux interactions du jeu de rôle. GTA V prend en charge les animations faciales via SetFacialIdleAnimOverride, ce qui change l'expression du visage au repos du pédagogue. Les expressions disponibles incluent mood_normal_1, mood_happy_1, mood_angry_1, mood_injured_1, mood_stressed_1, mood_smug_1, et mood_sulk_1 entre autres. Ces expressions persistent jusqu'à ce qu'elles soient effacées avec ClearFacialIdleAnimOverride. La combinaison d'une expression faciale avec une animation corporelle et un style de marche donne aux joueurs un contrôle total sur la façon dont leur personnage se présente. Un personnage appuyé contre un mur avec une expression suffisante et une démarche confiante raconte une histoire complètement différente de celui avec une boiterie blessée et un visage stressé. Ces petits détails élèvent le jeu de rôle des interactions de base à une représentation riche des personnages.

-- Facial expression system
local currentExpression = nil

Config.Expressions = {
    {label = 'Normal',    expression = 'mood_Normal_1'},
    {label = 'Happy',     expression = 'mood_Happy_1'},
    {label = 'Angry',     expression = 'mood_Angry_1'},
    {label = 'Injured',   expression = 'mood_Injured_1'},
    {label = 'Stressed',  expression = 'mood_Stressed_1'},
    {label = 'Smug',      expression = 'mood_smug_1'},
    {label = 'Sulking',   expression = 'mood_sulk_1'},
    {label = 'Sleeping',  expression = 'pose_injured_1'},
    {label = 'Scared',    expression = 'mood_scared_1'},
}

local function SetExpression(expression)
    local ped = PlayerPedId()
    ClearFacialIdleAnimOverride(ped)
    if expression then
        SetFacialIdleAnimOverride(ped, expression, nil)
        currentExpression = expression
    else
        currentExpression = nil
    end
end

exports('SetExpression', SetExpression)

-- Shared emotes: sync with nearby players
RegisterNetEvent('emote:syncShared', function(senderId, dict, anim)
    local senderPed = GetPlayerPed(GetPlayerFromServerId(senderId))
    if not DoesEntityExist(senderPed) then return end
    local myPed = PlayerPedId()
    local dist = #(GetEntityCoords(myPed) - GetEntityCoords(senderPed))
    if dist > 3.0 then return end
    if not LoadAnimDict(dict) then return end
    TaskPlayAnim(myPed, dict, anim, 2.0, 2.0, -1, 1, 0, false, false, false)
end)

Construire l'interface du menu Emote

Le menu emote peut être implémenté sous la forme d'un menu radial basé sur NUI ou d'un menu de jeu utilisant une bibliothèque comme ox_lib ou qb-menu. Un menu radial semble naturel pour les émoticônes, car les joueurs peuvent parcourir rapidement les catégories et sélectionner une émoticône sans lire de longues listes de textes. Liez le menu à une clé comme F4 ou un raccourci clavier configurable, et fournissez une commande de secours comme /emote [name] pour un accès direct. Le menu doit afficher le nom de l'émote, une vignette d'aperçu facultative et indiquer quelles émoticônes utilisent des accessoires. Incluez un système de favoris qui permet aux joueurs d'épingler leurs émoticônes les plus utilisées sur un anneau d'accès rapide, les évitant ainsi de parcourir les catégories à chaque fois qu'ils souhaitent effectuer une action courante. Conserver les favoris dans le stockage KVP du joueur en utilisant SetResourceKvp ils survivent donc aux redémarrages du serveur sans avoir besoin d'scripts dans la base de données. Un menu d'émoticônes bien construit devient l'une des fonctionnalités les plus utilisées sur n'importe quel serveur de jeu de rôle, donc investir du temps dans l'interface rapporte des dividendes en termes de satisfaction des joueurs.

Partager cet article

Prêt à améliorer votre serveur ?

Découvrez nos scripts FiveM premium dans la boutique Agency Scripts ou rejoignez notre communauté Discord pour le support et les mises à jour.