Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет, Хабр. История моя берёт начало в январе 2019 года.
Мы с моей тимой геймдевелоперов решили взяться за самый большой проект в нашей истории- 2Д платформер. Нет, мы не делали до этого какие-нибудь FlappyBird'ы или змейки, но объём работы в этом проекте просто сносил нам мозг. Для начала мы отказались от обычных, вертикальных лестниц, а взяли ступенчатые лестницы. Мы написали логику для дверей, которые можно было закрывать «на ключ» и прикрутили разрушаемые блоки. Наступил момент, когда надо было писать Искусственный Интеллект. Как самому опытному из нашей малоопытной команды скриптеру, честь писать ИИ выпала мне.я плакал в подушку, не понимая, что мне делать я был очень горд тем, что именно я напишу одну из самых сложных механик в нашем проекте.
Так как основные локации у нас будут не на открытом воздухе, а в зданиях, то нужно было сделать поиск маршрута среди десятков дверей, лестниц и комнат. Подумав, мы с тимлидом решили, что стоит сделать некую пародию на алгоритм A*, где у нас будут узлы, между которыми будет бегать бот. сделали тестовую сцену, поставили узлы, для наглядности повесили на них SprateRenderer'ы. А что делать дальше?
С этим вопросом в голове я ходил 3 дня. Пока один мой товарищ не предложил интересный алгоритм, когда узлы будут возбуждаться, подобно нейронам головного мозга.
Итак. Есть узел A, около которого стоит ИИ и узел Б, к которому ИИ должен прийти. выдали всем Узлам свой ID и пометили соединенные узлы, к которым они будут отправлять сигнал. У каждого узла была своя булевая переменная «isChecked» и переменная «triggeredBy», в котором хранился ID узла, который его «возбудил». Так, когда затронут узел Б, он пройдёт по цепочке к узлу А, узнавая все ID узлов, которые прошёл сигнал. Так я получал путь из ID узлов, которые должен пройти бот. Если вы вдруг не поняли, как это работает, то я расскажу вам сказку.
Это массив int-переменных, которые означают ID узлов, которые должен пройти юнит.
У меня есть список ID узлов, у меня есть бот. Что дальше? А дальше то, что надо двигать бота по этому пути.
Ну я прикинул такой вариант: дошёл бот до узла, поставил галочку, посмотрел, что там дальше, пошёл к следующему узлу. Сделал. Заработало. Я был рад… Но…
Как говорил один чёрно-белый герой: «Лестницы… мой главный враг..»
Нужно было определить, следующий узел находится над ИИ, под или на уровне. В зависимости от этой информации он будет проходить мимо лестницы(игнорировать коллизию), или забираться на неё(взаимодействовать с коллизией). Ох и много нервных клеток полегло на этой битве с движком… На форумах вычитал, что можно расставить всё по слоям и во вкладке Edit->Physics2D можно настроить игнорирование коллизий одного слоя и другого. Всё заработало!
Осталось только научить его открывать двери. Тут проблем не возникло.
Итог:
Да, мы решили приделать эмоции… И реплики.
Эмоции будут выделяться выражением лица и анимациями действий.
Реплики будут отображаться текстом над головой.
Эмоции я прикрутил на одном дыхании… Для этого я уже сделал переменную «emotionID», которая хранила в себе ID эмоции. А вот реплики…
Для красоты сделал отдельный класс Phrases
Сделал массив этого класса. Дальше просто в зависимости от emotionID ставил любую фразу из списка. Обновлял раз в N секунд.
Но я решил пойти дальше! Для каждого персонажа сделал файл с .phrs расширением, закодировал это с помощью того, что к байтовому числу каждого символа в файле прибавлял X байтов. Получался нечитаемый, не изменяемый текст. сделал что-то типа своей разметки, сделал алгоритм, который берёт и по этой разметке всё переводит в массив класса Phrases.
Отлично! Всё работает!
Хотел на чистом шарпе написать программку для заполнения такого файла, но тут мы переходим к концу истории.
От большой, неоплачиваемой работы мы быстро устали… Присоединение нового кодера не помогло… Команда развалилась… Код всё-ещё лежит на облаке Unity.
Конечно, не так давно начала зарождаться идея продолжить проект, но уже с дальнейшей монетизацией… Если что-то получится, то я, пожалуй, напишу всю историю разработки. Но на этом мой рассказ про начинающего скриптера и ИИ заканчивается.
Мы с моей тимой геймдевелоперов решили взяться за самый большой проект в нашей истории- 2Д платформер. Нет, мы не делали до этого какие-нибудь FlappyBird'ы или змейки, но объём работы в этом проекте просто сносил нам мозг. Для начала мы отказались от обычных, вертикальных лестниц, а взяли ступенчатые лестницы. Мы написали логику для дверей, которые можно было закрывать «на ключ» и прикрутили разрушаемые блоки. Наступил момент, когда надо было писать Искусственный Интеллект. Как самому опытному из нашей малоопытной команды скриптеру, честь писать ИИ выпала мне.
Этап 1: поиск пути и движение по пути
Этап 1.1: Поиск пути
Так как основные локации у нас будут не на открытом воздухе, а в зданиях, то нужно было сделать поиск маршрута среди десятков дверей, лестниц и комнат. Подумав, мы с тимлидом решили, что стоит сделать некую пародию на алгоритм A*, где у нас будут узлы, между которыми будет бегать бот. сделали тестовую сцену, поставили узлы, для наглядности повесили на них SprateRenderer'ы. А что делать дальше?
С этим вопросом в голове я ходил 3 дня. Пока один мой товарищ не предложил интересный алгоритм, когда узлы будут возбуждаться, подобно нейронам головного мозга.
Итак. Есть узел A, около которого стоит ИИ и узел Б, к которому ИИ должен прийти. выдали всем Узлам свой ID и пометили соединенные узлы, к которым они будут отправлять сигнал. У каждого узла была своя булевая переменная «isChecked» и переменная «triggeredBy», в котором хранился ID узла, который его «возбудил». Так, когда затронут узел Б, он пройдёт по цепочке к узлу А, узнавая все ID узлов, которые прошёл сигнал. Так я получал путь из ID узлов, которые должен пройти бот. Если вы вдруг не поняли, как это работает, то я расскажу вам сказку.
Однажды Ивану нечего было делать, и поэтому он решил составить своё фамильное древо. К несчастью у него не хватало информации для воплощения этой идеи в жизнь. Иван был так увлечён этой идеей, что решил, что добравшись до главного прародителя он сможет обнаружить своих неожиданных родственников. Иван знал, где он может встретиться со своим отцом, чтобы поговорить и направился туда. Отец рассказал ему, что деда Ивана звали Iван и рассказал, где его можно найти. Иван нашёл Iвана, и тот рассказал, что прадед Ивана мог знать этого прародителя лично, но он давно помер. Иван посветил половину жизни изучению тёмных искусств, но в итоге смог воскресить своего прадеда. Прадед сказал, что его прародитель является оборотнем и что зарыто его тело на опушке у трёх сосен. Иван пошёл туда и обнаружил, роющего могилу человек. Оказалось, что этот человек – двеннадцатиюродный брат Иван. Иван сильно удивился тому, что они пришли к одному месту, но брат оказался программистом и всё объяснил.Получился вот такой результат:
– Здесь работает принцип навигации из моей любимой игры *название*!
– И в чём же он заключается?
– От каждого наследника можно придти к общему родителю системы, если в этой системе каждый наследник знает лично своего родителя.
Это массив int-переменных, которые означают ID узлов, которые должен пройти юнит.
Этап 1.2.1: движение по пути
У меня есть список ID узлов, у меня есть бот. Что дальше? А дальше то, что надо двигать бота по этому пути.
Ну я прикинул такой вариант: дошёл бот до узла, поставил галочку, посмотрел, что там дальше, пошёл к следующему узлу. Сделал. Заработало. Я был рад… Но…
Этап 1.2.2: лестницы и их взаимодействие с ИИ
Как говорил один чёрно-белый герой: «Лестницы… мой главный враг..»
Нужно было определить, следующий узел находится над ИИ, под или на уровне. В зависимости от этой информации он будет проходить мимо лестницы(игнорировать коллизию), или забираться на неё(взаимодействовать с коллизией). Ох и много нервных клеток полегло на этой битве с движком… На форумах вычитал, что можно расставить всё по слоям и во вкладке Edit->Physics2D можно настроить игнорирование коллизий одного слоя и другого. Всё заработало!
Осталось только научить его открывать двери. Тут проблем не возникло.
Итог:
Этап 2: Эмоции и реплики
Этап 2.1: Эмоции
Да, мы решили приделать эмоции… И реплики.
Эмоции будут выделяться выражением лица и анимациями действий.
Реплики будут отображаться текстом над головой.
Эмоции я прикрутил на одном дыхании… Для этого я уже сделал переменную «emotionID», которая хранила в себе ID эмоции. А вот реплики…
Этап 2.2: Реплики
Для красоты сделал отдельный класс Phrases
[System.Serializable] //сериализуемый для красивого отображения в инспекторе
class Phrases
{
public string Name; //название эмоции, которой соответствуют реплики
public int byEmotionID; //для определения, с какой эмоцией это соединять
public string[] Phrases;//массив самих реплик
}
Сделал массив этого класса. Дальше просто в зависимости от emotionID ставил любую фразу из списка. Обновлял раз в N секунд.
Но я решил пойти дальше! Для каждого персонажа сделал файл с .phrs расширением, закодировал это с помощью того, что к байтовому числу каждого символа в файле прибавлял X байтов. Получался нечитаемый, не изменяемый текст. сделал что-то типа своей разметки, сделал алгоритм, который берёт и по этой разметке всё переводит в массив класса Phrases.
Отлично! Всё работает!
Хотел на чистом шарпе написать программку для заполнения такого файла, но тут мы переходим к концу истории.
Конец..?
От большой, неоплачиваемой работы мы быстро устали… Присоединение нового кодера не помогло… Команда развалилась… Код всё-ещё лежит на облаке Unity.
Конечно, не так давно начала зарождаться идея продолжить проект, но уже с дальнейшей монетизацией… Если что-то получится, то я, пожалуй, напишу всю историю разработки. Но на этом мой рассказ про начинающего скриптера и ИИ заканчивается.