Skip to content

14 — Архітектура та продуктивність

🇬🇧 English | 🇺🇦 Українська

Модульні межі

FuzzyLogic є runtime-модулем без Slate та UMG. Він містить модель даних, парсер, inference engine, компонент, підсистему, Data Asset і JSON.

FuzzyLogicUMG залежить від runtime та додає лише малювання. FuzzyLogicEditor і FuzzyLogicTests мають тип Editor і не потрапляють у game runtime.

FuzzyLogicDemo — модуль типу Runtime з акторами демо-карт і панеллю fire control. Ядро від нього не залежить, але як runtime-модуль він таки лінкується в запаковану гру, а CDO його акторів жорстко посилаються на меші /Engine/BasicShapes. Як його виключити — див. 16 — Demo Content.

Компіляція один раз

Текст правил зручний для автора, але не для гарячого циклу. FFuzzyInferenceEngine::Compile парсить правила та зв’язує імена з індексами. Evaluate працює на скомпільованій формі й не повторює парсинг рядків.

UFuzzyLogicComponent компілює ліниво та кешує engine. Кеш скидається після завантаження, редакторської зміни або MarkSystemDirty.

Вартість обчислення

Головні множники:

  • кількість агентів і частота Evaluate;
  • кількість правил та умов;
  • кількість виходів;
  • Sample Count для методів із поверхнею.

Weighted Average не будує дискретизовану поверхню та зазвичай найдешевший. Вибирайте його через характер моделі, а не лише заради швидкості. Єдиний виняток — заперечення в наслідку (THEN X IS NOT A): доповнена форма не має аналітичного представницького значення, тож рушій інтегрує її чисельно, і таке правило дешевим бути перестає.

Пам’ять на одного агента

Що саме дублюється

Легко припустити, що коли сотня акторів вказує на один UFuzzySystemAsset, у пам’яті лежить одна система. Це правда щодо авторських даних — сам асет існує в одному екземплярі, — але не щодо рантаймової форми.

Коли компонент обчислює вперше, він викликає FFuzzyInferenceEngine::Compile, а Compile робить повну копію FFuzzySystem: кожну змінну, кожну множину з її формою належності, кожен рядок правила, налаштування. Кожен UFuzzyLogicComponent володіє власним FFuzzyInferenceEngine, тому:

1 асет  ×  100 компонентів  =  100 копій системи  +  100 окремих компіляцій

Кожен компонент також тримає між викликами свій останній FFuzzyInferenceResult — три мапи й два масиви, — щоб відповідати на Get Last Result і Get Last Output Surface.

Для жменьки акторів усе це не має значення, і компонент — правильний інструмент. Замислитися варто десь у межах сотень або коли ту саму систему щокадру обчислює багато агентів одночасно.

Спільний скомпільований рушій

Плагін постачає готову відповідь на це: UFuzzyLogicSubsystem — підсистема game instance, яка компілює кожен асет один раз і віддає скомпільовану форму всім, хто просить. Сотня агентів на одному асеті поведінки має одну копію системи й одну компіляцію, а кожен тримає лише власні вхідні значення.

Налаштовувати нічого не треба — Unreal створює підсистему разом із game instance. Повний опис використання — у 09 — Blueprint Integration та 10 — C++ Integration.

Коротко, у Blueprint:

Get Game Instance Subsystem (Fuzzy Logic)
  → Evaluate Asset Output (Asset = BehaviorAsset, Inputs, Output Variable Name = "Aggression")

і в C++, тримаючи рушій на весь час життя агента:

void AMyAgent::BeginPlay()
{
    Super::BeginPlay();

    if (UGameInstance* GameInstance = GetGameInstance())
    {
        Brain = GameInstance->GetSubsystem<UFuzzyLogicSubsystem>()->GetEngine(BehaviorAsset);
    }
}

void AMyAgent::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    if (!Brain.IsValid())
    {
        return;
    }

    // One small map per agent instead of one whole system per agent.
    Inputs.Add(TEXT("Distance"), DistanceToTarget);
    Inputs.Add(TEXT("Health"), CurrentHealth);

    Aggression = Brain->Evaluate(Inputs).GetOutput(TEXT("Aggression"), 0.5f);
}

Компонент чи підсистема?

Одне не є заміною іншого.

UFuzzyLogicComponent UFuzzyLogicSubsystem
Пам’ять Одна копія системи на актора Одна копія на асет, на всю гру
Компіляції Одна на актора Одна на асет
Входи Запам’ятовуються між викликами; оновлюйте лише змінене Передаються заново щоразу
Останній результат Get Last Result, Get Last Output Surface Не зберігається — тримайте самі
Налаштування Додати компонент, призначити асет Нічого
Налаштування на актора Кожен актор може мати власну Inline System Одна спільна система, без варіацій
Потоки Ігровий потік Evaluate можна викликати з робочих потоків

Компонент лишається кращим за замовчуванням і правильною відповіддю для жменьки акторів, для всього, що потребує власних систем на актора, і для всього, що малює власне рішення. По підсистему тягніться тоді, коли багато агентів ділять одну поведінку або коли обчислення має йти поза ігровим потоком.

Вимірюйте, перш ніж переходити. Дюжина агентів, що рахує за таймером, ніколи не проявиться в профайлі.

Практична оптимізація

  • Спільний UFuzzySystemAsset зменшує дублювання авторських даних (див. примітку про пам’ять у рантаймі вище).
  • Не викликайте одноразову Evaluate Fuzzy System кожен кадр: вона компілює систему на кожен виклик.
  • Для повторних рішень використовуйте компонент або збережений FFuzzyInferenceEngine.
  • Оцінюйте повільні AI-рішення таймером, а не обов’язково в Tick.
  • Оновлюйте лише входи, що змінилися.
  • Зменшуйте Sample Count лише після вимірювання похибки й продуктивності.

Потокобезпечність

Окремі екземпляри FFuzzyInferenceEngine незалежні. Незмінний engine після компіляції допускає паралельні Evaluate, оскільки метод є const. Не компілюйте той самий екземпляр одночасно з оцінюванням. UObject та ActorComponent підпорядковуються звичайним правилам Game Thread Unreal.

Дані й пакування

Runtime не потребує зовнішнього JSON, якщо система збережена у Data Asset. FuzzyLogicEditor та FuzzyLogicTests не завантажуються у shipping build. FuzzyLogicUMG потрібен лише проєктам із runtime-графіками.

Розширення

Нові функції належності додаються як похідні USTRUCT від FFuzzyMembershipFunction. Реєстр, JSON і графіки використовують рефлексію, тому центральний switch для кожного нового типу не потрібен.