Menu emotek i animacji FiveM: Skrypty gotowe na RP
Dodaj rozbudowane animacje i emotki do swojego serwera FiveM. RPEmotes, Dpemotes, customowe style chodzenia i najlepsze menu animacji dla pełnej immersji roleplay.
Agency Scripts
Założyciel i główny deweloper w Agency Scripts
Zrozumienie animation dictionaries w GTA V
Animacje w GTA V są zorganizowane w słowniki animacji (animation dictionaries), czyli kolekcje powiązanych klipów animacji. Każda animacja jest identyfikowana przez dwa ciągi znaków: nazwę słownika oraz nazwę animacji w tym słowniku. Na przykład słownik amb@world_human_hang_out_street@female_hold_arm@idle_a zawiera animacje stania bezczynnie dla pedów żeńskich. Zanim odtworzysz jakąkolwiek animację, musisz zażądać słownika za pomocą RequestAnimDict i poczekaj na zakończenie ładowania za pomocą HasAnimDictLoaded. Brak wcześniejszego zażądania słownika to jeden z najczęstszych błędów popełnianych przez nowych programistów, powodujący, że animacje po prostu się nie odtwarzają. GTA V zawiera tysiące słowników animacji, od postaw bojowych po pozycje jogi, a znalezienie przydatnych to połowa wyzwania przy tworzeniu rozbudowanego menu emotek.
Odtwarzanie animacji i zarządzanie nimi
Główną funkcją do odtwarzania animacji jest TaskPlayAnim, która przyjmuje ped gracza, słownik, nazwę animacji, prędkość blend-in, prędkość blend-out, czas trwania, flagi oraz tempo odtwarzania. Flagi animacji kontrolują kluczowe zachowania: flaga 1 zapętla animację, flaga 2 zatrzymuje animację na ostatniej klatce, flaga 16 odtwarza tylko górną część ciała, pozwalając graczowi chodzić, a flaga 32 umożliwia obrót podczas animacji. Łączenie flag za pomocą bitowego OR pozwala tworzyć złożone zachowania. Emotka siedzenia wymaga flagi 1 do zapętlania oraz flagi 2 do zatrzymania ostatniej klatki w razie przerwania. Emotka machania działa najlepiej z flagą 49, która łączy wyłącznie górną część ciała z zapętlaniem i obrotem, pozwalając graczowi machać podczas chodzenia.
-- 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)
Organizowanie emotek w kategorie
Dobrze zorganizowane menu animacji grupuje animacje w intuicyjne kategorie, które gracze mogą szybko przeglądać. Typowe kategorie to Tańce, Pozdrowienia, Reakcje, Pozy, Sport, Scenariusze (takie jak siedzenie, opieranie się lub leżenie), Animacje z propami (obejmujące obiekty takie jak papierosy lub telefony), Style chodzenia, oraz Mimika twarzy. Każdy wpis emotki powinien zawierać słownik, nazwę animacji, flagi, informację o użyciu propa, hash modelu propa (jeśli dotyczy), indeks kości do przyczepienia propa oraz wartości przesunięcia i rotacji do prawidłowego umieszczenia propa. Przechowywanie tego wszystkiego w ustrukturyzowanej tabeli konfiguracyjnej ułatwia dodawanie nowych emotek bez modyfikowania logiki odtwarzania.
-- 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},
},
}
Animacje z propami i dołączanie obiektów
Animacje z propami polegają na zespawnowaniu obiektu gry i dołączeniu go do konkretnej kości na pedzie gracza. GTA V posiada szkieletowy system kości, w którym każda kość ma unikalny indeks. Do najczęściej wykorzystywanych kości należą 57005 dla prawej ręki, 18905 dla lewej ręki, 28422 dla obszaru prawego palca (idealne dla papierosów), a 31086 dla głowy. Po zespawnowaniu propa za pomocą CreateObject, przypnij go za pomocą AttachEntityToEntity co wymaga prop entity, ped entity, indeksu kości, offsetów pozycji, offsetów rotacji oraz kilku flag boolean dla kolizji i fizyki. Prawidłowe ustawienie offsetów pozycji i rotacji wymaga metody prób i błędów, a różne modele pedów mogą wymagać drobnych korekt. Zawsze usuwaj propy po anulowaniu emotki, śledząc uchwyt zrespionego entity i usuwając go.
-- 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
Style chodzenia i klipy ruchu
Style chodzenia zmieniają sposób poruszania się postaci gracza i są nakładane za pomocą SetPedMovementClipset. W przeciwieństwie do zwykłych animacji, które nadpisują akcje, zestawy movement clipsets zachowują się we wszystkich stanach poruszania się, w tym podczas chodzenia, biegania i sprintu. GTA V zawiera dziesiątki wbudowanych movement clipsets, takich jak MOVE_M@DRUNK@MODERATEDRUNK dla pijanego chodu, MOVE_M@GANGSTER@NG dla gangsterskiego kroku, MOVE_M@CONFIDENT dla pewnego kroku, a MOVE_F@HEELS@C do chodzenia na wysokich obcasach. Podobnie jak słowniki animacji, clipsety muszą zostać zażądane i załadowane przed zastosowaniem. Zapewnij opcję resetu, aby gracze mogli wrócić do domyślnego chodu. Zapisuj preferowany styl chodu gracza w danych jego postaci, aby zachowywał się między sesjami i był automatycznie przywracany po zalogowaniu.
-- 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)
Mimika twarzy
Mimika twarzy dodaje subtelnej głębi emocjonalnej do interakcji roleplay. GTA V obsługuje animacje twarzy poprzez SetFacialIdleAnimOverride, co zmienia domyślny wyraz twarzy peda. Dostępne wyrazy twarzy to m.in. mood_normal_1, mood_happy_1, mood_angry_1, mood_injured_1, mood_stressed_1, mood_smug_1, oraz mood_sulk_1 między innymi. Te wyrażenia utrzymują się do momentu ich wyczyszczenia za pomocą ClearFacialIdleAnimOverride. Połączenie mimiki twarzy z animacją ciała i stylem chodzenia daje graczom pełną kontrolę nad prezentacją ich postaci. Postać oparta o ścianę z aroganckim wyrazem twarzy i pewnym krokiem opowiada zupełnie inną historię niż postać kulejąca z powodu ran i ze zestresowaną twarzą. Te drobne detale podnoszą roleplay z poziomu podstawowych interakcji do bogatego kreowania postaci.
-- 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)
Tworzenie interfejsu menu emotek
Menu animacji można zaimplementować jako menu kołowe oparte na NUI lub menu w grze przy użyciu biblioteki takiej jak ox_lib lub qb-menu. Menu kołowe jest naturalne dla animacji, ponieważ gracze mogą szybko przeglądać kategorie i wybierać animację bez czytania długich list tekstowych. Przypisz menu do klawisza takiego jak F4 lub konfigurowalny keybind i zapewnij alternatywną komendę, taką jak /emote [name] do bezpośredniego dostępu. Menu powinno pokazywać nazwę animacji, opcjonalną miniaturę podglądu oraz wskazywać, które animacje używają rekwizytów. Dołącz system ulubionych, który pozwala graczom przypinać najczęściej używane animacje do menu szybkiego wyboru, oszczędzając im przeglądania kategorii za każdym razem, gdy chcą wykonać popularną akcję. Zapisuj ulubione w pamięci KVP gracza za pomocą SetResourceKvp dzięki czemu przetrwają restarty serwera bez konieczności zapisów do bazy danych. Dobrze zbudowane menu animacji staje się jedną z najczęściej używanych funkcji na każdym serwerze roleplay, więc poświęcenie czasu na interfejs procentuje zadowoleniem graczy.