Compreendendo os dicionários de animação do GTA V
As animações no GTA V são organizadas em dicionários de animação, que são coleções de clipes de animação relacionados. Cada animação é identificada por duas strings: o nome do dicionário e o nome da animação dentro desse dicionário. Por exemplo, o dicionário amb@world_human_hang_out_street@female_hold_arm@idle_a contém animações ociosas para pedólogas. Antes de poder reproduzir qualquer animação, você deve solicitar o dicionário usando RequestAnimDict e esperar até que ele termine de carregar com HasAnimDictLoaded. Deixar de solicitar o dicionário primeiro é um dos erros mais comuns cometidos por novos desenvolvedores, resultando em animações que falham silenciosamente na reprodução. GTA V vem com milhares de dicionários de animação que cobrem tudo, desde posturas de combate até poses de ioga, e descobrir alguns úteis é metade do desafio de construir um menu abrangente de emoções.
Reproduzindo e gerenciando animações
A função principal para reproduzir animações é TaskPlayAnim, que aceita o ped do player, dicionário, nome da animação, velocidade de mesclagem, velocidade de mesclagem, duração, sinalizadores e taxa de reprodução. Os sinalizadores de animação controlam o comportamento crítico: o sinalizador 1 faz um loop na animação, o sinalizador 2 interrompe a animação no último quadro, o sinalizador 16 reproduz apenas a parte superior do corpo permitindo que o jogador ande e o sinalizador 32 permite a rotação durante a animação. Combinar sinalizadores com OR bit a bit permite criar comportamentos diferenciados. Um emote sentado precisa do sinalizador 1 para loop e do sinalizador 2 para manter o último quadro se for interrompido. Um emote de ondulação funciona melhor com a bandeira 49, que combina a parte superior do corpo apenas com looping e rotação, permitindo que o jogador acene enquanto caminha.
-- 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)Organizando Emotes em Categorias
Um menu de gestos bem organizado agrupa animações em categorias intuitivas que os jogadores podem navegar rapidamente. Categorias comuns incluemDanças, Saudações, Reações, Poses, Esportes, Cenários(como sentar, inclinar-se ou deitar-se),Emotes de acessórios(envolvendo objetos como cigarros ou telefones),Estilos de caminhada, eExpressões Faciais. Cada entrada de emote deve armazenar o dicionário, o nome da animação, os sinalizadores, se ela usa um adereço, o hash do modelo do adereço, se aplicável, o índice ósseo para fixação do adereço e os valores de deslocamento e rotação para o posicionamento adequado do adereço. Armazenar tudo isso em uma tabela de configuração estruturada facilita a adição de novos emotes sem modificar a lógica de reprodução.
-- 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 de acessórios e anexos de objetos
Os emotes de suporte envolvem gerar um objeto de jogo e anexá-lo a um osso específico no ped do jogador. GTA V possui um sistema ósseo esquelético onde cada osso possui um índice único. Ossos de inserção comuns incluem 57005 para a mão direita, 18905 para a mão esquerda, 28422 para a área do dedo direito (ideal para cigarros) e 31086 para a cabeça. Depois de gerar o suporte com CreateObject, anexe-o usando AttachEntityToEntity que requer a entidade prop, a entidade ped, o índice ósseo, deslocamentos de posição, deslocamentos de rotação e vários sinalizadores booleanos para colisão e comportamento físico. Obter os deslocamentos corretos de posição e rotação requer tentativa e erro, e diferentes modelos de ped podem precisar de pequenos ajustes. Sempre limpe os adereços quando o emote for cancelado, rastreando o identificador da entidade gerada e excluindo-o.
-- 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
endEstilos de caminhada e clipes de movimento
Os estilos de caminhada mudam a forma como o personagem do jogador se move e são aplicados usando SetPedMovementClipset. Ao contrário das animações regulares que substituem as ações, os clipes de movimento persistem em todos os estados de locomoção, incluindo caminhada, corrida e corrida. GTA V inclui dezenas de clipes de movimento integrados, como MOVE_M@DRUNK@MODERATEDRUNK para um tropeço bêbado, MOVE_M@GANGSTER@NG para uma arrogância de gangster, MOVE_M@CONFIDENT para um passo confiante e MOVE_F@HEELS@C para andar de salto alto. Assim como os dicionários de animação, os clipsets devem ser solicitados e carregados antes da aplicação. Fornece uma opção de redefinição para que os jogadores possam retornar à caminhada padrão. Armazene o estilo de caminhada preferido do jogador nos dados do personagem para que ele persista durante as sessões, reaplicando-o automaticamente quando ele fizer login.
-- 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)Expressões Faciais
As expressões faciais adicionam profundidade emocional sutil às interações de roleplay. GTA V suporta animações faciais através de SetFacialIdleAnimOverride, que altera a expressão facial em repouso do ped. As expressões disponíveis incluem mood_normal_1, mood_happy_1, mood_angry_1, mood_injured_1, mood_stressed_1, mood_smug_1 e mood_sulk_1 entre outras. Estas expressões persistem até serem apagadas com ClearFacialIdleAnimOverride. Combinar uma expressão facial com uma animação corporal e um estilo de andar dá aos jogadores controle total sobre como seu personagem se apresenta. Um personagem encostado em uma parede com uma expressão presunçosa e um andar confiante conta uma história completamente diferente de alguém que manca ferido e com o rosto estressado. Esses pequenos detalhes elevam o roleplay de interações básicas a uma rica representação de personagens.
-- 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)Construindo a interface do menu Emote
O menu de emote pode ser implementado como um menu radial baseado em NUI ou como um menu do jogo usando uma biblioteca como ox_lib ou qb-menu. Um menu radial parece natural para emotes porque os jogadores podem navegar rapidamente pelas categorias e selecionar um emote sem ler longas listas de texto. Vincule o menu a uma tecla como F4 ou um atalho de teclado configurável e forneça um comando substituto como /emote [name] para acesso direto. O menu deve mostrar o nome do emote, uma miniatura de visualização opcional e indicar quais emotes usam acessórios. Inclui um sistema de favoritos que permite aos jogadores fixar seus emotes mais usados em um anel de acesso rápido, evitando que eles naveguem nas categorias sempre que quiserem realizar uma ação comum. Persista os favoritos no armazenamento KVP do jogador usando SetResourceKvp para que eles sobrevivam às reinicializações do servidor sem a necessidade de gravações no banco de dados. Um menu de emotes bem construído se torna um dos recursos mais usados em qualquer servidor de roleplay, portanto, investir tempo na interface rende dividendos na satisfação do jogador.

