MAATRIX GAMES / Блог / Оптимизация FPS на больших RP-картах

Оптимизация FPS на больших RP-картах

MAATRIX GAMES

Тикрейт на FXServer стабильный, ресурсы в resmon не жрут ничего сверхъестественного, а игроки всё равно пишут в тикеты «у мэрии фризит», «зашёл в новый МЛО — секунда фриза», «в центре 15 кадров, за городом — 60». Знакомая картина для любого RP-проекта, который вырос из пары кастомных зданий в полноценную карту с полусотней MLO, кастомными районами и толпой NPC. Дело почти никогда не в сервере — тикрейт и клиентский FPS живут по разным законам. Разберём, что конкретно грузит видеокарту и процессор игрока на такой карте и что с этим реально можно сделать, не выпиливая половину контента.

Почему проседает именно клиентский FPS, а не тикрейт

Тикрейт FXServer — это про то, как часто сервер пересчитывает игровую логику и рассылает синхронизацию клиентам. FPS в клиенте GTA V/RAGE — это отдельная история: сколько кадров успевает отрисовать видеокарта игрока с учётом всего, что реально загружено в память и попадает в кадр. Эти метрики почти не связаны напрямую: можно держать 40+ тикрейт при полном онлайне и при этом сажать игроков на 20 FPS, если карта собрана без оглядки на клиентскую нагрузку.

На вэнильной карте Лос-Сантоса движок RAGE стримит контент по расстоянию: чем дальше объект, тем больше шансов, что его модель ещё не подгружена или уже выгружена. Проблема больших RP-карт в том, что кастомные MLO и районы часто собираются и подключаются как единый пак «всё сразу» — без деления на зоны, без правильных content-флагов в ytyp, иногда с ошибками экспорта из CodeWalker или Map Builder, из-за которых движок считает объект «обязательным к прорисовке» независимо от дистанции. В итоге у игрока в памяти постоянно висят десятки интерьеров и районов, которые он физически не видит — и GPU честно пытается их всё равно учитывать при построении сцены.

Если у вас уже стоят кастомные карты и вы не разбирались, как их правильно собирать со стримингом с нуля — сначала стоит посмотреть материал про установку карт и MLO на FiveM, там разобран базовый fxmanifest и структура stream-файлов. Здесь же — что делать, когда карта уже большая, живая, и переписывать её с нуля не вариант.

Стриминг по зонам вместо all-at-once

Главная ошибка объёмных RP-карт — собирать весь новый район или пачку MLO в один ресурс, который стартует целиком через ensure и держит все свои data_file в памяти клиента постоянно, вне зависимости от того, где игрок физически находится. Правильная модель — грузить контент по зонам, аналогично тому, как классический GTA V годами использовал IPL-группы для переключения целых кусков карты (например, открытие/закрытие туннелей или сезонных локаций).

Два рабочих подхода для RP-карт:

  1. Деление на модульные ресурсы. Вместо одного maps/gorod-rp с двадцатью MLO — двадцать отдельных ресурсов, каждый со своим fxmanifest.lua, data_file 'DLC_ITYP_REQUEST' и data_file 'DLC_YMAP_REQUEST'. Сам движок GTA стримит ytyp/ymap по расстоянию и content-флагам, если они выставлены корректно при экспорте — дробление на ресурсы облегчает и отладку (проще найти виновника через resmon), и позволяет позже управлять запуском конкретных районов отдельно.
  2. Ручное переключение зон через RequestIpl/RemoveIpl. Для больших статичных перестроек (не MLO-интерьеров, а целых уличных кварталов) можно держать альтернативную геометрию как отдельную IPL-группу и переключать её нативами RequestIpl('nazvanie_gruppy') / RemoveIpl('nazvanie_gruppy') по координатам игрока — это штатный, давно существующий в движке механизм, а не самописный костыль.
-- пример: подгружаем тяжёлый район, только когда игрок рядом
CreateThread(function()
    local loaded = false
    while true do
        local coords = GetEntityCoords(PlayerPedId())
        local dist = #(coords - vector3(215.0, -810.0, 30.0)) -- центр района

        if dist < 300.0 and not loaded then
            RequestIpl('custom_district_alt')
            loaded = true
        elseif dist >= 300.0 and loaded then
            RemoveIpl('custom_district_alt')
            loaded = false
        end
        Wait(2000)
    end
end)

Для MLO-интерьеров отдельная тонкость: если игрок физически видит вход в здание снаружи, но интерьер уже висит в памяти постоянно — почти всегда это забытый this_is_a_map 'yes' или неправильно выставленный interior proxy в ymap, из-за чего движок держит содержимое как «всегда обязательное», а не как то, что грузится через дверной портал.

Поднять сервер FiveM (GTA V) за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Culling: убираем лишние объекты из кадра

Второй по частоте источник просадок — избыточная детализация того, что реально не влияет на геймплей. Речь про occlusion (окклюжен-каллинг: объекты за стенами и вне поля зрения камеры не должны участвовать в рендере) и про банальную захламлённость сцены декором.

Что стоит проверить в тяжёлых кастомных зданиях и районах:

  • Interior occlusion boxes. В CodeWalker при сборке MLO задаются occlusion-объёмы — они говорят движку, что за этой стеной ничего не рендерить, пока камера не в комнате. Многие бесплатные и часто перепаковываемые MLO-паки идут без нормальной расстановки occlusion — визуально это незаметно автору при тесте в одиночку, а на живом сервере с десятками одновременно открытых дверей ощутимо бьёт по кадрам.
  • LOD-модели и lodDist. Если у пропов в ytyp не настроены LOD-уровни или дистанция подмены слишком большая, объекты держат полную детализацию дальше, чем нужно. Актуально для декора на улице — фонари, кондиционеры, мелкий мусор, если их натыкали сотнями без LOD.
  • Дублирующиеся источники света и отражающие поверхности. Зеркала, стекло, лишние point-light в декоративных светильниках — недорогие по отдельности, но при плотной застройке кастомного района (клубы, ТЦ, казино) суммарно дают заметную нагрузку на рендер, особенно на видеокартах без аппаратного ray tracing.
  • Плотность пропов ради атмосферы. Захламлённые интерьеры (бар с полной прорисовкой каждой бутылки, гараж с десятками отдельных инструментов как statics) выглядят круто на скриншоте, но каждый статик — это отдельный draw call. Где не критично для атмосферы — часть мелочи можно объединить в один меш ещё на этапе моделирования.

Если раньше не разбирались с видимостью объектов на сервере в целом (не только на кастомных картах, но и с ванильными настройками прорисовки) — там же рядом стоит почитать про view distance и другие настройки анти-лага, многое из этого применимо и здесь.

Лимит одновременно активных ped и vehicle

Толпы NPC и трафик — отдельная и очень весомая статья нагрузки на больших RP-картах, особенно в центре, где физически сходятся кастомные локации, торговые точки и активные игроки. GTA V-движок умеет регулировать плотность population нативами, которые многие RP-фреймворки либо не используют вообще, либо используют с настройками по умолчанию, не подходящими под нагруженный кастомный город:

CreateThread(function()
    while true do
        SetPedDensityMultiplierThisFrame(0.3)
        SetVehicleDensityMultiplierThisFrame(0.3)
        SetScenarioPedDensityMultiplierThisFrame(0.2, 0.2)
        SetRandomTrains(false)
        SetRandomBoats(false)
        SetGarbagetrucks(false)
        Wait(0)
    end
end)

Значения множителей стоит подбирать по своей карте, а не копировать вслепую — на пустой промзоне 0.3 будет выглядеть мёртво, а в кастомном мегаполисе даже 0.2 может быть избыточно плотным. Отдельно проверьте onesync_population в server.cfg: этот конвар управляет тем, синхронизирует ли OneSync ambient-population между клиентами централизованно — на плотных RP-картах с большим онлайном имеет смысл держать его включённым и тестировать оба состояния под нагрузкой, разница по клиентскому FPS может быть заметной именно в местах скопления игроков.

Если у вас на карте несколько «хотспотов» (полицейский участок, центральный рынок, каземат тюрьмы), логично не давить плотность глобально, а зонировать: снижать multiplier сильнее именно в радиусе хотспотов, где и так десятки живых игроков плюс их машины создают визуальную нагрузку без всякого ambient-трафика.

Server.cfg и порядок ресурсов под потоковую карту

Порядок запуска ресурсов в server.cfg важен не только для зависимостей скриптов, но и для стриминга. Несколько практических моментов:

# базовые карты и интерьеры — раньше геймплейных ресурсов
ensure base-maps
ensure custom-interiors-district1
ensure custom-interiors-district2

# фреймворк и логика — после карт
ensure es_extended
ensure my-population-manager

set onesync on
set onesync_population true
set sv_enforceGameBuild 3095

sv_enforceGameBuild стоит держать зафиксированным и явно прописанным — если карта собиралась под конкретный game build (а большинство современных MLO-паков ориентируются на актуальные билды), несовпадение билда клиента и того, под что готовились ассеты, иногда даёт артефакты стриминга вплоть до пропадающих текстур, которые движок пытается компенсировать лишней подгрузкой.

Отдельно: если карта растёт, а сервер стоит на минимальной конфигурации — рано или поздно вы упрётесь не только в клиентский FPS, но и в серверную память под растущее число ресурсов и синхронизацию population. На это стоит закладываться заранее, увеличивая тариф по RAM, а не только оптимизируя код постфактум.

Диагностика и настройки на стороне игрока

Прежде чем оптимизировать вслепую — найдите реального виновника. Встроенный resmon (открывается в F8-консоли) показывает нагрузку не только серверных, но и клиентских ресурсов — если один конкретный MLO-пак или скрипт population стабильно выделяется по CPU/памяти на клиенте, начинайте с него. Для более глубокой картины в клиенте есть встроенный профайлер: команды profiler record и profiler save в консоли сохраняют трассировку кадра, которую можно разобрать через инструменты CitizenFX — полезно, когда просадка не привязана к одному ресурсу, а копится из мелочей.

Практическая последовательность отладки:

  • Отключайте кастомные районы/MLO по одному через stop, проверяя FPS в той же точке карты — так быстро находится конкретный тяжёлый ресурс.
  • Сравните FPS в той же локации в одиночном тесте (только вы на сервере) и при полном онлайне — если разница огромная, дело в population и синхронизации, а не в статике карты.
  • Проверьте, GPU- или CPU-bound просадка: если при снижении разрешения/MSAA FPS ощутимо растёт — упор в GPU (геометрия, текстуры, освещение), если почти не меняется — узкое место в CPU-логике скриптов или синхронизации population.

Часть нагрузки честно снимается на стороне самого игрока. В настройках FiveM (клиентская вкладка Graphics) стоит проверить и по возможности снизить: Population Density и Extended Population (прямо отвечают за количество ambient NPC/машин, которые рендерит именно ваш клиент поверх серверной логики), MSAA (дорогая антиалиасинг-опция, особенно в плотной городской застройке), дистанцию прорисовки/детализации. Это не заменяет оптимизацию карты сервером, но у игроков со слабым железом даёт быстрый и бесплатный прирост, пока вы разбираетесь с ресурсами.

Поднять сервер FiveM (GTA V) за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Частые вопросы

Тикрейт сервера высокий, а FPS у игроков низкий — это нормально?

Да, это разные метрики. Тикрейт — про частоту серверной логики, FPS — про клиентский рендер. Один хороший показатель не гарантирует другой, особенно на картах с тяжёлым кастомным контентом.

Можно ли просто снизить draw distance глобально и решить проблему?

Частично поможет, но это грубый инструмент — снижает нагрузку заодно и там, где карта уже оптимизирована, при этом не лечит первопричину (плохо собранные MLO без стриминга или occlusion). Лучше сначала найти конкретного виновника через resmon.

Сколько MLO можно повесить на сервер, прежде чем начнутся проблемы?

Универсального числа нет — зависит от того, как каждый MLO собран (со стримингом и occlusion или без), от плотности population рядом и от железа целевой аудитории. Ориентируйтесь не на количество, а на то, растёт ли нагрузка в resmon линейно с каждым новым паком или скачками — скачок обычно означает, что конкретный пак собран криво.

OneSync population точно нужно включать?

В подавляющем большинстве случаев да для серверов с онлайном выше пары десятков — без него синхронизация ambient-population работает менее предсказуемо. Но эффект стоит проверить именно на своей карте и своём фреймворке, а не включать вслепую по совету из интернета.

Нужно ли давать RP-серверу с большой картой больше RAM, чем в базовых рекомендациях под игру?

Как правило да — десятки дополнительных ресурсов под кастомные карты и population-менеджеры сами по себе занимают память сверх стандартного минимума под FiveM, и экономия на тарифе быстро упирается в потолок при росте карты.