Wróć do bloga
Tutorial8 min czytania

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.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.