[CS:GO] Создание начального AI

Vit_ amin

Участник
Сообщения
1,302
Реакции
495
Всем привет.
В данной статье вы узнаете:
  1. Древо поведения (BT - Behavior Tree)
  2. Базовые принципы и концепты создания своего (кастомного) AI (искуственного интелекта) ботов в CS:GO
  • Введение:
В одном из свобих обновлений Valve добавила возможность кастомизировать ИИ ботов для игры, для начала они добавили этот функционал в режим DeatchMatch и свои Co-Op миссии, но со временем они также добавили возможность картоделам вшивать свои скрипты BT для организации своего ИИ игроков под управлением компьютера, и вообще, если посмотреть, то изредка добавляются новые элементы для написания своего ИИ
  • Что такое древо поведения (BT):
Если очень просто и не усугубляться в дебри этого вопроса, это граф (в тексотовом или графическом виде - в зависимости от того, какой инструментарий дают вам разработичик, портировавшие/добавившие его), узлы которого являются возможные варианты поведения AI (в нашем случае это боты в CS:GO), но древо поведения это очень популярная система для создания AI, взять тот же игровой движок Unreal Engine (сам я лично в нём не работал, но судя по всему древо поведения - едиственный вариант создания ИИ в данном игровом движке)


1609440239915.png
Рисунок 1 - Типичный шаблон древо поведения​

Если рассмотреть рис. 1, то в нём есть несколько концептуальных правил для создания древа:
  1. Древа поведения всегда начинаются с его корня (то есть с английского root), данное правило относиться ко всем древам (в нашем случае скриптовый файл будет начинаться именно с данного ключевого слова)
  2. Conditional - в нашем случае как вы поняли это условие, которое будет выполнено/отклонено в зависимости от того, что разработчик скрипта хочет от игрока под управлением компьютера
  3. Behavior A или B это как раз таки само поведение бота, который будет действовать в той или иной степени (в нашем случае поведение напрямую зависит от условия conditional, которое находиться выше)
Внимание: В древах поведения все действия выполняются всегда слева -> направо (в нашем случае и в случае с игрой CS:GO, все действия будут идти сверху вниз)

Сегодня я затрону именно игру CS:GO и добавленный разработичками функционал для создания кастомного AI

Внимание: В теме я затрону только базовую концепцию (идею) создание своего ИИ

  • Создание ИИ
Любое создание кастомного ИИ начинается с папки по пути (также в данной папке вы можете посмотреть уже готовые скрипты от разработчиков)

В нашем случае создадим папку под название test и переёдем туда
Чтобы движок понимал наши кастомные файлы ИИ, нам необходимо создать два файла:
  1. Конфигурационный файл ботов, который содержит то, как их Aim ведет себя при наведении на врага и осмотре игрового мира (плавность поврота/частота поворота камеры/на сколько градусов может поворачиваться камера и другие вещи), также в этом файле указано время реакции бота на врага, какова вероятность того, присядет при стрельбе бот или нет и так далее
  2. Сам скриптовый файл, где прописан сценарий
Создаём два файла (про которых я описал выше):
  1. bt_config.kv3 (конфигурация ботов)
  2. bt_script.kv3 (скриптовый сценарий ботов)
Внимание: Игра CS:GO использует Key-values структуру (расширение kv3) для создания своего древа поведения, поэтому постановка скобок очень важна в скрипте, чтобы вам было удобочитам код.

Открыв файл bt_config.kv3
Пропишите туда следующий код (без вникания, если необходимы какие то уточнения, пишите в комментариях)
C-подобный:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
    default =
    {
        aim_target_acquisition_lerp_time = 0.0
        aim_target_acquisition_lerp_time_deviation = 0.0
        aim_target_acquisition_angle_penalty = 0.0
        aim_target_acquisition_angle_penalty_deviation = 0.0
        aim_target_acquisition_angle_penalty_reduction_ratio = 0.0
        aim_target_acquisition_angle_tolerance = 0.0
        aim_target_acquisition_angle_lerp_bias = 0.0
        aim_target_tracking_lerp_time = 0.0
        aim_target_tracking_lerp_time_deviation = 0.0
        aim_target_tracking_focus_interval = 0.0
        aim_target_tracking_focus_interval_deviation = 0.0
        aim_target_tracking_angle_lerp_bias = 0.0
        aim_ready_angle_tolerance = 0
        aim_new_target_angle_tolerance = 0
        aim_max_duration = 0
        aim_max_duration_deviation = 0.0
        aim_punch_angle_reaction_chance = 0.0

        look_around_awareness_yaw_range = 360
        look_around_awareness_pitch_range = 45
        look_around_focus_interval = 1
        look_around_focus_interval_deviation = .1
        look_around_lerp_time = .4
        look_around_lerp_time_deviation = .1
        look_around_lerp_bias = .25

        reaction_time = 0.3

        combat_crouch_chance = 0
        combat_dodge_command_duration = 0
        combat_dodge_command_duration_deviation = 0

        attack =
        [
            [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ 0.95, 0.25, 0.2, 0.05, 0.0      ], [ 0.425, 0.075, 0.0, 0.125, 0.025 ],
            [ 0.155, 0.035, 0.0, 0.15, 0.05], [ 0.3, 0.0, 0.0, 0.25, 0.05     ], [ 0.22, 0.07, 0.0, 2.0, 0.0       ],
            [ 0.9, 0.3, 0.0, 0.15, 0.05    ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0       ],
            [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0       ],
            [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0       ],
            [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0       ],
            [ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ]
        ]
    }
}
Сохраняем и далее переходим к файлу bt_script.kv3

Для начала нам необходимо сказать скрипту, чтобы он подхватыл конфигурационный файл настройки ботов, это делается следующим образом
C-подобный:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
    // путь начинается с папки scripts и до файла (расширение указывать необходимо!)
    config = "scripts/ai/test/bt_config.kv3" // В kv3 файлах, вы можете оставлять свои записи и комментарии через привычный символ //
}
Как мы с вами ранее узнали, то любое древо поведения начинается с корня, так давайте и начнём с него:
C-подобный:
    root =
    {
    }
В своем примере я буду показывать создание ИИ для бота, который будет бежать до рандомной точки на карте, при этом скрипт будет считать количество попыток, которое у бота прошли успешно, когда он достиг этих рандомных точек, а когда этих точек накопилось 3 штуки, тогда он будет ждать 1 секунду и говорить в рацию "Чисто (или сектор чист)", потом счетчик сбрасывается и все по новой:
Я скрину сразу готовый результат с полным комментированием каждой строки
C-подобный:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
    config = "scripts/ai/test/bt_config.kv3"    // подгружаем конфигурационный файл
    root =    // "корень" скрипта
    {
        // Декораторы - это условные функции в системе древа поведения
        // В случае с игрой CS:GO она также выполняет условные функции в нашем случае
        // decorator_repeat - это условная функция которая исполняет роль цикла (for/while/foreach и так далее)
        // для любого декоратора необходимо наличие ребенка - child - то есть тело цикла (если говорить с точки зрения программирования)
        // child -  ребенок должен открываться и закрываться кавычками (как указано ранее было, по системе key-values)
        type = "decorator_repeat"
        child =
        {
            // selector - это функция (или еще его называют комбинатор), которая выбирает в теле функции
            // (в нашем случае тело функции) - это цикл, который именовался decorator_repeat
            // selector - выбирает только TRUE функции и обрывает дальнейшее выполнение
            // если сравнивать с программирование - это операция continue
            // например если написать for (условие)
            // то слово selector выполняет функцию условия
            // if (true) then - прервать и начать заного
            type = "selector"
            // children - дети
            // когда выбирается любой из комбинаторов (а их в случае с CS:GO - 3)
            // selector
            // sequencer
            // parallel
            // то он (комбинатор) должен иметь ряд функций для своего выполнения
            // комбинатор использует для своего тела функции квадратные скобки
            children =
            [
                {
                    // decorator_run_once - это условная функция которая исполняет функцию только один раз
                    type = "decorator_run_once"
                    child =
                    {
                        // action_set_global_counter - это функция действия, которая создает внутри скрипта глобальную переменную
                        // Тип переменной Integer
                        // Имя переменной указывается в input_name (обязательно значало двойные, а затем одинарные кавычки)
                        // Величина переменной указывается в iinput_value
                        // В нашем случае имя переменной Test = 1
                        // Если рассматривать программирование то это будет
                        // int Test = 1 (SourcePawn)
                        // То есть мы прописали глобальную переменную, которая создается с инициализацией один раз
                        // Так как мы работаем в цикле, то данная операция (благодаря декоратору decorator_run_once
                        // будет выполнена только один раз
                        type = "action_set_global_counter"
                        input_name = "'Test'"
                        input_value = 1
                    // Функцию закончилась, закрываем кавычки
                    }
                // Функцию закончилась, закрываем кавычки
                // Так как у нас комбинатор selector и ниже еще есть функции для выполнения
                // то ставим запятую, чтобы скрипт понимал, что эти функции соединены в комбинаторе selector
                },
                {
                    // sequencer - это комбинатор, который выполняется до тех пор, пока не сработало ложное условие
                    // например если написать for (условие)
                    // то слово sequencer выполняет функцию условия
                    // if (false) then - прервать и начать заного
                    type = "sequencer"
                    children =
                    [
                        // decorator_random_approach_point - это условная функция
                        // которая выбирает рандомную точку (которая указано на навигационной местности карты)
                        // и записывает в переменную ApproachPoint
                        // в нашем случае слово output это то, что возвращает функция, когда она выполнила свое действие
                        // То есть если переписать на программирование
                        // function decorator_random_approach_point()
                        // {
                        //        return ApproachPoint;
                        // }
                        // Не забываем ставить запятую, так как у нас ниже еще идет код
                        {
                            type = "decorator_random_approach_point"
                            output = "ApproachPoint"
                        },
                        // action_move_to - это функция действия, которая дает команду боту, который выполняет этот скрипт двигать в точку
                        // destination - Это то, куда бот будет следовать
                        // в нашем случае, мы используем переменную ApproachPoint
                        // Также мы можем указать константные координаты по пример (X Y Z)
                        // то есть (в нашем случае это получается Origin)
                        // destination = "154.32 212.12 0.12"
                        // movement_type - указывает, что до точки бот будет бежать
                        // route_type - указывает, что бот будет использовать безопасный по возможности маршрут следования
                        // то есть, где остсутствовали звуки выстрелов, взрыва гранат, шаги врагов
                        {
                            type = "action_move_to"
                            destination = "ApproachPoint"
                            movement_type = "BT_ACTION_MOVETO_RUN"
                            route_type = "BT_ACTION_MOVETO_FASTEST_ROUTE"
                        },
                        // Прописываем selector
                        // так как нам надо выбрать из определенных вариантов верный и закончить тело функции
                        // в нашем случае как я говорил выше (бот должен 3 раза дойти до точки
                        // и потом сказать в рацию "Чисто"
                        // Этим (перебором) мы и будем сейчас заниматься
                        {
                            type = "selector"
                            children =
                            [
                                // прописывание sequencer - то есть последовательно читаем код
                                {
                                    type = "sequencer"
                                    children =
                                    [
                                        // action_compare_global_counter - это функция действия, которая сравнивает
                                        // то есть сравнивает переменные типа Integer
                                        // if (a == b)
                                        // данная функция может сравнивать только переменные
                                        // которые уже созданы через функцию action_set_global_counter (что мы выше и сделали)
                                        // в данном случае мы сравниваем равна ли переменная Test числу 1
                                        {
                                            type = "action_compare_global_counter"
                                            input_name = "'Test'"
                                            input_value = 1
                                        },
                                        // Так как у нас комбинатор sequencer, то если бы переменная была не равна 1, то мы бы вышли из тела функции
                                        // и бот бы снова выбрал случайную точку и пошел к ней
                                        // но так как у нас переменная равна 1
                                        // поэтому мы присваиваем её значение 2
                                        {
                                            type = "action_set_global_counter"
                                            input_name = "'Test'"
                                            input_value = 2
                                        }
                                    ]
                                },
                                // по аналогии сравниваем (выше расписано)
                                // только сравниваем уже число 2 и если оно равно,
                                // тогда присваиваем переменной Test значение 3
                                {
                                    type = "sequencer"
                                    children =
                                    [
                                        {
                                            type = "action_compare_global_counter"
                                            input_name = "'Test'"
                                            input_value = 2
                                        },
                                        {
                                            type = "action_set_global_counter"
                                            input_name = "'Test'"
                                            input_value = 3
                                        }
                                    ]
                                },
                                // по аналогии сравниваем (выше расписано)
                                {
                                    type = "sequencer"
                                    children =
                                    [
                                        {
                                            type = "action_compare_global_counter"
                                            input_name = "'Test'"
                                            input_value = 3
                                        },
                                        {
                                            type = "action_set_global_counter"
                                            input_name = "'Test'"
                                            input_value = 4
                                        }
                                    ]
                                }
                            ]
                        },
                        // Если наша переменная Test равна 4, позволяем скрипту дальше выолнить код
                        {
                            type = "action_compare_global_counter"
                            input_name = "'Test'"
                            input_value = 4
                        },
                        // action_wait - это функция действия, которая заставляет бота замереть на одном месту
                        // wait_time_min - время минимальное, которое бот будет "заморожен" (в нашем случае 1 секунда)
                        // wait_time_max - время максимальное, которое бот будет "заморожен" (в гашем случае 1 секунда)
                        // Также данная функция замораживает не только бота, но и заставляет повиснуть скрипт на время
                        {
                            type = "action_wait"
                            wait_time_min = 1
                            wait_time_max = 1
                        },
                        // action_say - это функция действия, которая заставляет бота сказать что-то в радиопереговоры
                        // phrase - Фраза, которую бот скажет в рацию
                        // Список фраз находиться в файле botchatter.db
                        {
                            type = "action_say"
                            phrase = "Clear"
                        },
                        // Устанавливаем переменной action_set_global_counter значение 1 и начинаем скрипт заного по циклу
                        // благодаря декоратору decorator_repeat
                        {
                            type = "action_set_global_counter"
                            input_name = "'Test'"
                            input_value = 1
                        }
                    ]
                }
            ]
        }
    }
}

После написания кода сохраните его и пропишите в консоль:
mp_bot_ai_bt <PATH> (в нашем случае) mp_bot_ai_bt scripts/ai/test/bt_script.kv3
После этого добавьте бота и наблюдаёте за его деёствиями

Всем спасибо за внимание.
 
Последнее редактирование:

osTr0ve

Участник
Сообщения
28
Реакции
2
Спасибо за гайд. Очень заинтересовала эта тема. Не можешь подсказать где еще почитать про все эти параметры? Так же вопрос про bt_config.kv3 и про параметры которые там, так в принципе по названию примерно понятно, но хотелось бы увидеть более точные описания этих переменных.
Ну и при загрузке твоего скрипта вот такого типа ошибки получаю в консоле (сам сервер где тестирую не локальный).
Console:
[AI BT]: Error loading behavior tree file 'scripts/ai/test/bt_config.kv3': Unable to determine buffer encoding.[AI BT]: Unable to load behavior tree 'scripts/ai/test/bt_script.kv3', bot skill = 'profile level 0.75'
[AI BT]: Error loading behavior tree file 'scripts/ai/test/bt_config.kv3': Unable to determine buffer encoding.[AI BT]: Unable to load behavior tree 'scripts/ai/test/bt_script.kv3', bot skill = 'profile level 0.6'
[AI BT]: Error loading behavior tree file 'scripts/ai/test/bt_config.kv3': Unable to determine buffer encoding.[AI BT]: Unable to load behavior tree 'scripts/ai/test/bt_script.kv3', bot skill = 'profile level 0.6'
[AI BT]: Error loading behavior tree file 'scripts/ai/test/bt_config.kv3': Unable to determine buffer encoding.[AI BT]: Unable to load behavior tree 'scripts/ai/test/bt_script.kv3', bot skill = 'profile level 0.6'
[AI BT]: Error loading behavior tree file 'scripts/ai/test/bt_config.kv3': Unable to determine buffer encoding.[AI BT]: Unable to load behavior tree 'scripts/ai/test/bt_script.kv3', bot skill = 'profile level 0.6'
 

Vit_ amin

Участник
Сообщения
1,302
Реакции
495
Здравсвуйте, читать об этом больше особо и негде, я сам до всего доходил очень большим количеством тестирований
Насчет bt_config.kv3? вот несколько переменных
C-подобный:
        look_around_awareness_yaw_range = 360    // На какой угол относительно того, куда он смотрит, AI игрок может повернуть камеру
        look_around_awareness_pitch_range = 45    // На какой угол (вниз или вверх) крутиться камера у AI игрока    
        look_around_focus_interval = 0.5            // Как часто AI игрок крутит камеру
        look_around_focus_interval_deviation = .1    // ?
        look_around_lerp_time = .4                    // Скорость поворота камеры у AI игрока
        look_around_lerp_time_deviation = .1
        look_around_lerp_bias = .25                // Плавность поворота камеры у AI игрока
Насчёт загрузки скрипта, не могу что-либо сказать, так как из за нехватки времени, отошел от всех этих дел и возможно какое то обновление сломало что-то в скриптах (либо я что-то забыл)
Сообщения автоматически склеены:

Также у человека имеется видео, где он показывает настройки камеры для AI игрока (ссылка не рекламная, а указывает на настройки в bt_config.kv3)
 
Последнее редактирование:
Сверху