Як агенти ШІ отримують доступ до інформації про своє оточення?


9

Це може бути дещо тривіальним питанням, але у мене виникають проблеми з розумінням цього. Буду дуже вдячний за вашу допомогу.

У розробці ігор, використовуючи об’єктно-орієнтований дизайн, я хочу зрозуміти, як AI-агенти отримують необхідну інформацію з ігрового світу для виконання своїх дій.

Як ми всі знаємо, в іграх дуже часто агентам ШІ потрібно «сприймати своє оточення» та діяти відповідно до того, що відбувається навколо них. Наприклад, агент може бути запрограмований переслідувати гравця, якщо він / вона наблизиться досить близько, уникати перешкод під час руху (використовуючи рульове поведінку уникнення перешкод) тощо.

Моя проблема в тому, що я не впевнений, як це зробити. Як агент AI може отримати доступ до потрібної інформації про ігровий світ?

Один з можливих підходів полягає в тому, що агенти просто вимагають потрібну інформацію безпосередньо з ігрового світу.

Існує клас під назвою GameWorld. Він обробляє важливу логіку гри (цикл гри, виявлення зіткнень тощо), а також містить посилання на всі сутності в грі.

Я міг би зробити цей клас одинаковим. Коли агенту потрібна інформація з ігрового світу, він просто отримує її безпосередньо з екземпляра GameWorld.

Наприклад, агент може бути запрограмований на Seekгравця, коли він / вона поруч. Для цього агент повинен отримати позицію гравця. Таким чином, можна просто запросити його безпосередньо: GameWorld.instance().getPlayerPosition().

Агент також може просто отримати список усіх сутностей у грі та проаналізувати його на потреби (щоб з'ясувати, які сутності знаходяться поруч чи що-небудь ще): GameWorld.instance().getEntityList()

Це найпростіший підхід: агенти безпосередньо звертаються до класу GameWorld і отримують необхідну інформацію. Однак це єдиний мені підхід. є кращий?

Як би спроектував це досвідчений розробник ігор? Чи підходить "отримати список усіх сутностей і шукати все, що вам потрібно"? Які існують підходи та механізми, щоб дозволити агентам ШІ отримати доступ до потрібної інформації для виконання своїх дій?


Якщо у вас є доступ до підписки на GDCVault, у 2013 році відбулася чудова розмова під назвою "Створення AI для живого, дихаючого світу абсолютності Гітмена", яка детально входить у їхню модель знань AI.
DMGregory

Відповіді:


5

Те, що ви описуєте, - це класична "тягача" модель запитів у світі. Здебільшого це працює досить добре, особливо для ігор з базовим AI (що найбільше). Однак є кілька моментів, які слід врахувати, що можуть бути і мінусами:

  • Можливо, ви хочете подвоїти буфер. Дивіться схеми програмування ігор з цього питання . Завжди запитуючи дані безпосередньо від світу, ви можете отримати дивні умови гонки, за яких результат залежить від того, в якому порядку викликається ШІ. Чи важливо це для вашої гри, вам належить визначити. Один з можливих результатів полягає в тому, що він упереджує гру тому, хто йде "першим" чи "останнім", що робить багатокористувацьку несправедливою.

  • Часто це може бути набагато ефективнішим для групового запиту, особливо для певних структур даних. Тут ви можете змусити кожного агента AI, який хоче шукати перешкоди, створити "об'єкт запиту" та зареєструвати його за допомогою центрального синглету перешкоди. Потім перед основним циклом AI всі запити запускаються проти структури даних, яка зберігає структуру даних перешкод більше в кеші. Потім під час AI частини кожен агент обробляє результати запиту, але не може робити більше жодних результатів. Наприкінці кадру об’єкти AI оновлюють запити на нове місцеположення або додають або видаляють їх. Це схоже на дизайн, орієнтований на дані .

    Зауважте, що це в основному робить подвійне буферизація, зберігаючи результат запитів у буфері. Це також вимагає, щоб ви передбачили, чи потрібно робити запит на кадр раніше. Це "push" модель, тому що агенти заявляють, які саме оновлення цікавлять (роблячи відповідний об’єкт запиту), і ці оновлення підштовхуються до них. Зауважте, ви також можете об'єкт запиту містити зворотний виклик, а не зберігати всі результати для кадру.

  • Нарешті, ви, мабуть, хочете використовувати інтерфейси або компоненти для пошукових об'єктів, а не для успадкування, що добре документально підтверджено в інших місцях. Перебір списку Entitiesперевірки instanceOf, ймовірно , рецепт досить крихкий код, хвилина , Ви хочете як StaticObjectі MovingObjectбути Healable. (якщо не instanceOfпрацює інтерфейс на вашій мові.)


5

ШІ дорога, продуктивність часто є рушійним фактором в архітектурі.

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

(Кожен приклад передбачає єдине глобальне оновлення логіки.)

  • * Найкоротший шляхКожен AI обчислює стан карти для цілей прокладання маршрутів. A * вимагає, щоб кожен AI вже знав про все (локальне) середовище, в якому він повинен знаходити шлях, тому ми повинні передавати йому інформацію про перешкоди на карті та простір (часто 2D булевий масив). A * - спеціалізована форма алгоритму Діккстри, алгоритму пошуку відкритого графа з найкоротшим шляхом; такі підходи повертають списки, що представляють шлях, і на кожному кроці AI просто вибирає наступний вузол у цьому списку для переходу до тих пір, поки не досягне своєї мети або не потрібен перерахунок (наприклад, через зміну перешкоди на карті). Без цих знань неможливо знайти жодного реалістичного найкоротшого шляху. A * тому AI в іграх RTS завжди знають, як дістатися з точки A до точки B - якщо шлях існує. Він перерахує найкоротший шлях для кожного окремого ШІ, тому що дійсність контуру базується на положенні тих ШІ, які раніше рухалися (і потенційно блокували певні шляхи). Ітераційний процес, за допомогою якого A * обчислює значення комірок під час конвертування, є математичним зближенням. Можна сказати, що кінцевий результат нагадує нюх, змішаний із відчуттям зору, але в цілому він дещо чужий нашому розуму.

  • Дифузія спільної роботи Також в іграх це найбільше нагадує нюх, заснований на дифузії газів і твердих часток. CD вирішує проблему дорогої повторної обробки, знайденої в A *: Натомість один стан карти зберігається, обробляється один раз за оновлення для всіх AI, а результати отримують доступ до кожного AI по черзі, щоб зробити відповідний хід . Більше не є єдиний шлях (список комірок), що повертається алгоритмом пошуку; швидше, кожен AI буде перевіряти карту після її обробки та переміщуватись до тієї сусідньої комірки, яка має найвище значення. Це називається альпінізмом . Тим не менш, етап обробки карти вже повиненмати попередній доступ до інформації на карті, яка також містить місця розташування всіх органів ІС. Отже, карта посилається на II, а потім на AI посилаються на карту.

  • Комп'ютерне бачення та радіомовлення + найкоротший шлях У робототехніці rover & drone це стає нормою для визначення ступеня простору, по якому робот пересувається. Це дозволяє роботам побудувати повну об'ємну модель свого оточення так само, як ми би бачили або навіть звучали чи торкалися (для сліпих або глухих), які робот може потім зменшити до мінімальної топографічної графіки (трохи як графік маршрутної точки використовується в іграх з A *), на яких потім можуть бути застосовані найкоротші алгоритми. У цьому випадку, хоча "бачення" може дати підказку для безпосереднього оточення, це все одно призводить допошук графіків, який в кінцевому підсумку забезпечує шлях до мети. Це близьке до людської думки: Щоб дістатися до кухні зі спальні, я повинен пройти через вітальню. Те, що я вже бачив їх та знаю їхні простори та їхні портали, - це те, що дає змогу прорахувати цей крок. Це графічна топологія, до якої застосовується алгоритм найкоротшого шляху, хоч і вбудований у м'який білок, а не у твердий кремній.

Тож ви можете бачити, що перші два покладаються на те, що вони вже знають навколишнє середовище повністю. Це з міркувань витрат на оцінку середовища з нуля часто зустрічається в іграх. Ясна річ, що останній є найпотужнішим. Робот, обладнаний таким чином (або, наприклад, гра AI, яка читає буфер глибини кожного кадру), могла достатньо орієнтуватися в будь-якому середовищі без попереднього знання про це. Як ви, напевно, здогадалися, це також є найдорожчим із вищезазначених трьох підходів, і в іграх ми зазвичай не можемо дозволити собі це робити на основі AI. Звичайно, це в 2D набагато дешевше, ніж у 3D.

Архітектурні пункти

Вище стає зрозуміло, що ми не можемо припустити лише одну правильну схему доступу до даних AI; вибір залежить від того, що ви намагаєтеся досягти. Доступ до GameWorldкласу безпосередньо є абсолютно стандартним: він просто надає вам світову інформацію. По суті це ваша модель даних, і саме для цього використовуються моделі даних. Сінглтон за це добре.

"отримати список усіх організацій та шукати все, що вам потрібно"

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


Дякую за відповідь. Оскільки я новачок у грі Dev, я думаю, що зараз дотримуюся простого підходу "отримати список із ігрового світу" :) Одне питання: у своєму запитанні я описав GameWorldклас як клас, який містить посилання на всі ігрові сутності, а також містить більшість важливих логік «двигуна»: основний цикл гри, виявлення зіткнень тощо. В основному це «основний клас» гри. Моє запитання: Чи поширений такий підхід в іграх? У вас є "основний клас"? Або я повинен розділити його на менші класи та мати один клас, оскільки об'єкти "бази даних" можуть опитуватись?
Авів Кон

@Prog Запрошуємо вас Знову ж таки, у вищезазначених підходах AI (або будь-який інший, з цього приводу) немає нічого, що підказує, що ваш "отримати список із ігрового світу" є будь-яким способом, формою чи формою архітектурно неправильним. Архітектура AI повинна відповідати потребам AI; але та логіка, як ви пропонуєте, повинна бути модульованою, інкапсульованою (у своєму власному класі) подалі від вашої ширшої архітектури додатків. Так, підсистеми завжди слід враховувати в окремі модулі, як тільки виникають такі питання. Вашим керівним принципом має бути SRP .
Інженер

2

В основному у мене є 2 способи запиту інформації.

  1. коли AIState змінюється через те, що ви виявили зіткнення або інше кеш-посилання на будь-який об'єкт, важливий. Таким чином ви знаєте, яка вам потрібна довідка. Якщо в інших системах потрібно виконувати великі пошуки кожного кадру, я б рекомендував створювати резервну копію, щоб вам не довелося виконувати кілька запитів. Тож «зіткнення», виявлене із зоною, яка робить опонента ворога, надсилає йому повідомлення / подію, яка реєструє його з цим об’єктом, якщо його ще немає, і змінить ігровий стан на такий, який змушує його робити свій бізнес на основі перебування в що іграти. Вам потрібна подія певного типу, яка говорить про внесення змін, я б просто передавав посилання на той зворотній дзвінок, який ви використовуєте для надання цієї інформації. Це більш розтяжно, ніж просто мати справу з гравцем. Можливо, ви хочете, щоб противник переслідував іншого ворога чи якийсь інший об'єкт. Таким чином вам потрібно змінити лише тег, за яким ви його ідентифікуєте.

  2. З цією інформацією ви будете виконувати запит до системи пошуку шляхів, яка використовує A * або якийсь інший алгоритм, щоб дати вам шлях, або ви можете використовувати його з деякою поведінкою в управлінні. Можливо комбінація того чи іншого. В основному з перетворенням обох ви повинні мати можливість запитувати вашу систему вузлів або навмеш, і це дасть вам шлях. У вашому ігровому світі, ймовірно, є багато інших речей, ніж проходження маршрутів. Я надсилаю ваш запит лише до набору маршрутів. Крім того, групування цих речей, мабуть, найкраще, якщо у вас є багато запитів, оскільки це може бути досить інтенсивним, і пакетне сприятиме продуктивності.

    Transform* targetTransform = nullptr;
    EnemyAIState  AIState = EnemyAIState::Idle;
    void OnTriggerEnter(GameObject* go)
    {
       if(go->hasTag(TAG_PLAYER))
       {
       //Cache important information that will be needed during pursuit
       targetTransform = go->getComponent<Transform>();
       AIState = EnemyAIState::Pursue;
       }
    }
    
    void Update()
    {
       switch(AIState)
       {
          case EnemyAIState::Pursue:
           //Find position to move to
           Vector3 nextNode = PathSystem::Seek(
                              transform->position,targetTransform->position);
           /*Update the position towards the target by whatever speed the unit moves
             Depending on how robust your path system is you might want to raycast
             against obstacles it can't take into account or might clip the path.*/
            transform->Move((nextNode - transform->position).unitVector()*speed*Time::deltaTime());
            break;
        }
     }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.