Menús de gestos y animaciones para FiveM: scripts listos para el roleplay
Añade gestos y animaciones variados a tu servidor FiveM. RPEmotes, Dpemotes, estilos de caminar y menús para una experiencia de roleplay inmersiva.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
Cómo funcionan los diccionarios de animaciones de GTA V
Las animaciones de GTA V están organizadas en diccionarios de animaciones, que son colecciones de clips relacionados. Cada animación se identifica mediante dos cadenas: el nombre del diccionario y el nombre de la animación dentro de ese diccionario. Por ejemplo, el diccionario amb@world_human_hang_out_street@female_hold_arm@idle_a contiene animaciones de personajes femeninos en posición de reposo de pie. Antes de reproducir cualquier animación, debes solicitar el diccionario con RequestAnimDict y esperar a que termine de cargarse usando HasAnimDictLoaded. No solicitar primero el diccionario es uno de los errores más comunes entre los desarrolladores principiantes, y hace que las animaciones no se reproduzcan sin mostrar ningún error. GTA V incluye miles de diccionarios de animaciones que abarcan desde posturas de combate hasta poses de yoga, y descubrir cuáles son útiles es la mitad del reto al crear un menú de emotes completo.
Reproducir y gestionar animaciones
La función principal para reproducir animaciones es TaskPlayAnim, que acepta el ped del jugador, el diccionario, el nombre de la animación, la velocidad de entrada gradual, la velocidad de salida gradual, la duración, las flags y la velocidad de reproducción. Las flags de animación controlan comportamientos importantes: la flag 1 hace que la animación se repita, la flag 2 detiene la animación en el último fotograma, la flag 16 reproduce solo la parte superior del cuerpo y permite que el jugador camine, y la flag 32 permite girar durante la animación. Combinar flags con OR bit a bit permite crear comportamientos detallados. Un emote de sentarse necesita la flag 1 para repetirse y la flag 2 para mantener el último fotograma si se interrumpe. Un emote de saludar funciona mejor con la flag 49, que combina solo la parte superior del cuerpo con repetición y rotación, lo que permite que el jugador salude mientras camina.
-- 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)
Organizar emotes en categorías
Un menú de emotes bien organizado agrupa las animaciones en categorías intuitivas que los jugadores pueden explorar rápidamente. Entre las categorías comunes se incluyen Bailes, Saludos, Reacciones, Poses, Deportes, Escenarios (como sentarse, apoyarse o tumbarse), Emotes con objetos (que incluyen objetos como cigarrillos o teléfonos), Estilos de caminata, y Expresiones faciales. Cada entrada de emote debe guardar el diccionario, el nombre de la animación, las flags, si usa un objeto, el hash del modelo del objeto si corresponde, el índice del hueso al que se adjunta el objeto, y los valores de desplazamiento y rotación para colocarlo correctamente. Guardar todo esto en una tabla de configuración estructurada facilita añadir nuevos emotes sin modificar la lógica de reproducción.
-- 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 con objetos y adjunción de objetos
Los emotes con objetos implican generar un objeto del juego y adjuntarlo a un hueso específico del ped del jugador. GTA V tiene un sistema de huesos esqueléticos en el que cada hueso tiene un índice único. Entre los huesos de adjunción más comunes se incluyen 57005 para la mano derecha, 18905 para la mano izquierda, 28422 para la zona del dedo derecho (ideal para cigarrillos), y 31086 para la cabeza. Después de generar el objeto con CreateObject, adjúntalo usando AttachEntityToEntity que requiere la entidad del objeto, la entidad del ped, el índice del hueso, los desplazamientos de posición, los desplazamientos de rotación y varias flags booleanas para el comportamiento de colisión y física. Encontrar los desplazamientos de posición y rotación adecuados requiere prueba y error, y puede que haya que hacer pequeños ajustes según el modelo del ped. Limpia siempre los objetos cuando se cancele el emote, llevando un registro del identificador de la entidad generada y eliminándola.
-- 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
Estilos de caminata y clips de movimiento
Los estilos de caminata cambian la forma en que se mueve el personaje del jugador y se aplican usando SetPedMovementClipset. A diferencia de las animaciones normales, que reemplazan acciones, los clipsets de movimiento persisten en todos los estados de locomoción, incluidos caminar, correr y esprintar. GTA V incluye docenas de clipsets de movimiento integrados, como MOVE_M@DRUNK@MODERATEDRUNK para caminar tambaleándose como un borracho, MOVE_M@GANGSTER@NG para caminar con actitud de gánster, MOVE_M@CONFIDENT para caminar con confianza, y MOVE_F@HEELS@C para caminar con tacones altos. Al igual que los diccionarios de animaciones, los clipsets deben solicitarse y cargarse antes de aplicarlos. Ofrece una opción para restablecer el estilo y permitir que los jugadores vuelvan a la forma de caminar predeterminada. Guarda el estilo de caminar preferido del jugador en los datos de su personaje para que persista entre sesiones y se vuelva a aplicar automáticamente cuando inicie sesión.
-- 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)
Expresiones faciales
Las expresiones faciales añaden una sutil profundidad emocional a las interacciones de roleplay. GTA V permite usar animaciones faciales mediante SetFacialIdleAnimOverride; esta función cambia la expresión facial de reposo del ped. Entre las expresiones disponibles se incluyen mood_normal_1, mood_happy_1, mood_angry_1, mood_injured_1, mood_stressed_1, mood_smug_1, y mood_sulk_1 , entre otras. Estas expresiones se mantienen hasta que se borran con ClearFacialIdleAnimOverride. Combinar una expresión facial con una animación corporal y un estilo de caminar permite a los jugadores controlar por completo cómo se presenta su personaje. Un personaje apoyado contra una pared, con una expresión arrogante y un caminar seguro, cuenta una historia muy distinta a la de uno que cojea por una lesión y tiene el rostro tenso. Estos pequeños detalles elevan el roleplay, de interacciones básicas a una representación rica del personaje.
-- 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)
Crear la interfaz del menú de emotes
El menú de emotes puede implementarse como un menú radial basado en NUI o como un menú dentro del juego con una biblioteca como ox_lib o qb-menu. Un menú radial resulta natural para los emotes, porque los jugadores pueden recorrer las categorías rápidamente y seleccionar uno sin tener que leer largas listas de texto. Asigna el menú a una tecla como F4 o a una tecla configurable, y ofrece una alternativa mediante un comando, como /emote [name] para acceder directamente. El menú debe mostrar el nombre del emote, una miniatura de vista previa opcional e indicar qué emotes usan objetos. Incluye un sistema de favoritos que permita a los jugadores fijar sus emotes más usados en un anillo de acceso rápido, para que no tengan que recorrer las categorías cada vez que quieran realizar una acción habitual. Guarda los favoritos en el almacenamiento KVP del jugador mediante SetResourceKvp para que se conserven al reiniciar el servidor sin tener que escribir en la base de datos. Un menú de emotes bien diseñado se convierte en una de las funciones más usadas de cualquier servidor de roleplay, así que dedicar tiempo a la interfaz mejora la satisfacción de los jugadores.