Lego или счастливое детство
Когда наши дети были маленькими, каждую неделю мы собирались на семейные игры: разворачивали поле из деталей Lego, расставляли миниатюры, кидали кубики — играли в настольные игры.
О том, как мы попытались воспроизвести ту атмосферу, но уже в виде компьютерной игры и добавили в игру голоса семьи, применяли искусственный интеллект и проходили азы игростроения — расскажу в этой статье.
Первый релиз: уроки indie-разработки
Одной из самых наших любимых игр была Heroica. Это настольная игра, где необходимо выполнять задания, собирать ключи, драться с врагами.

После того, как сын Воля уже в школе прошёл курсы Unity, решили закрепить знания и создать небольшую игру. Выбор пал на старую добрую Heroica.
Мы работали по Agile: разбивали задачи на спринты, проводили дейли-митинги и ретроспективы — как настоящая команда, и на первую версию ушёл примерно год.
Сталкивались с задачами, с которыми сталкивается любой инди-разработчик:
создали мини-движок для быстрого прототипирования уровней
реализовали выбор оптимального пути для ботов, для этого использовали алгоритма коммивояжёра
прорабатывали баланс уровней
оттачивали игровой процесс и другое
Вся графика была создана сыном. Нас вдохновляла оригинальная Heroica, многие элементы которой мы скопировали.
В 2022 году опубликовали игру в AppStore и Google Play, но кроме наших знакомых и несколько десятков китайцев (для которых мы даже выпускали новогодние патчи) она никого не заинтересовала. Хотя мы получили интересный и полезный опыт.

От настолки к AI: как мы оживили игру голосом
Прошло еще несколько лет, и в этом году дети поступают в университеты - и пока было свободное время после ЕГЭ, решили вернуться к игре: исправить небольшие ошибки, улучшить графику, добавить новые фичи и перевыпустить игру еще раз.
При создании игры мы придерживались миссии - перенести атмосферу «ламповой» настольной игры в электронный вид, воссоздать ту атмосферу, когда мы играли все вместе, переговаривались и шутили.
«Значит надо добавить в игру голос» - решили мы. С развитием LLM стало возможным «на лету» генерировать осмысленные комментарии и тут же их озвучивать. Начали размышлять, как это можно реализовать.

Вариант 1. Разместить небольшую LLM и модуль TTS внутрь самой игры. Однако в таком случае игра увеличится в объёме, на некоторых устройствах может плохо функционировать, так как будет недостаточно мощности телефона. Этот вариант признали далеко не идеальным.
Вариант 2. Запустить веб-сервис, к которому будет обращаться игра. На сервере разместить LLM и модули озвучивания. Мы оценили, что задержка составит примерно 4–5 секунд (в лучшем случае), что сделает реакцию на события игры слишком медленной. К тому же такая реализация требует постоянного подключения к интернету, поэтому отказались и от этого варианта.
Вариант 3. Использовать упрощённый подход. Игра достаточно однообразна, число возможных ситуаций и выражений ограничено («рыцарь вступил в бой», «маг открыл дверь»). А что, если заранее сгенерировать все возможные комментарии и выбирать их по игровой ситуации? Эта идея показалась нам наиболее простой и привлекательной.
И мы приступили к реализации
Сначала добавили формирование описания игровой ситуации в виде тегов.
Событие: «Ход Рыцаря, он кинул кубик - и выпало 3, рыцарь походил», формируем тэги: [Рыцарь, Выпало 3, Походил]
Событие: «Ход Мага, он находится в режиме боя, кидает кубик - выпало 4, Победа» - формируем тэги: [Маг, Режим боя, Выпало 4, Победа]
Таких ключевых событий в игре не больше 30: ход игрока, бой с противником, взял ключ, открыл дверь и так далее. Потом мы просто играли в игру и формировали последовательность тегов, даже на одном уровне кубик выпадает по-разному и последовательность тегов различается, а на разных уровнях тем более. В результате мы получили разнообразие игровых ситуаций, выраженных в тэгах.
Для того, чтобы получить текстовые комментарии всю эту последовательность тегов подали на вход в LLM: нам помогали ChatGPT, DeepSeek, Qwen. В промте просили менять акценты: например, сгенерировать веселую фразу, обидную шутку, философскую фразу и так далее.
Большое количество фраз формировали сами, добавляли наши семейные моменты, например, про нашего кота Сырника (вот он на картинке, думает: «Зачем и меня вплели в эту затею»).

Сначала предполагали получить список фраз и использовать TTS (Text-to-Speech), но дочь Алена предложила записать наши «живые» голоса, как мы играли в детстве.
«Супер! Так и сделаем» — решили мы.
Записать большое количество фраз в домашних условиях — задача непростая, пришлось сократить их количество и выбрать наиболее повторяющиеся, таким образом, у нас получилось 280 уникальных фраз которые необходимо озвучить.
Как мы записывали голос
Мы создали JSON-базу, где каждая фраза связана с комбинацией из 5 тегов.
Фрагмент JSON файла ниже.
{
"tags": ["текущий ход", "Knight", "бросил кубик", "Knight", "выпало3"],
"text": "Ну Knight, бросок — как обычно, тройка. Снова не везёт!",
"audioClipName": "vl_knight_001.wav"
},
{
"tags": ["ход", "Knight", "сделал ход", "Knight", "текущий ход"],
"text": "Knight снова на подъёме! Ход сделан, цель найдена.",
"audioClipName": "vl_knight_003.wav"
},
{
"tags": ["бросил кубик", "Knight", "выпало2", "режим боя", "Knight"],
"text": "Выпало 2... Видимо, сегодня не день для атаки.",
"audioClipName": "vl_knight_004.wav"
},
{
"tags": ["False", "режим восстановления", "Knight", "ход", "Knight"],
"text": "Knight в порядке, но предпочитает пока ходить без риска.",
"audioClipName": "vl_knight_005.wav"
},
Дополнительно добавили промежуточные комментарии:
Начало боя: «Давай!», «Вперед!», «Мы в тебя верим!»
Проигрыш в бою: «Ну вот», «Плохо», «Не расстраивайся».
Выигрыш в бою: «Ура», «Победа!», «Молодец!»
Подгонялки: если игрок задерживается дольше 10 секунд, голос напоминает: «Ну давай, не спи»
Одну и ту же фразу озвучивал каждый член семьи: я, жена, дочь и сын. Записывали голоса на iPhone, микрофон у него хороший, конечно, не обошлось без ошибок, но это только добавляет «ламповости».
Подготовить 280 фраз непросто, важно, чтобы имя файла соответствовало фразе и соответствующим тегам. Человеку сложно проговорить 280 фраз и ни разу не ошибиться, поэтому записывали отрезками по 50 фраз и скриптом Python автоматически нарезали на отдельные файлы, определяя границу фразы по паузе между ними. Далее добавили файлы в Unity.

Как работает система подбора реплик
Алгоритм подбора фраз реализован следующим образом.
Генерация тегов. Система по ключевым событиям игры генерирует теги, например: [Маг, Битва, Проигрыш]. Все теги собираются в единую последовательность в разрезе одного уровня.
Контекст из 5 тегов. Для анализа мы используем 3 тега текущего хода + 2 тега с предыдущего, чтобы получить связь с прошлым ходом.
Реакция на каждое пятое событие. Четыре из пяти событий мы пропускаем, иначе боты были излишне «болтливыми».
Сопоставление с базой. На каждое пятое событие система ищет точное совпадение с одной из 280 комбинаций по пяти тэгам.
Случайный выбор. Для наиболее распространенных последовательностей тегов создали разные варианты фраз. Например, одна из популярных комбинаций выглядит так: [Маг, Выпало 4, Походил], и использование одной единственной фразы приводит к однообразию, поэтому на такие комбинации записали несколько вариантов, из которых система случайным образом выбирает один. Выбор персонажа, произносящего фразу, также осуществляется случайно.
-
Ограничение на повторы. Фраза, однажды произнесённая, повторно на данном уровне не повторяется.
Таким образом система описывает игровую ситуацию через теги и подбирает соответствующую реплику, еще между делом вставляем промежуточные комментарии. Результат нас вполне удовлетворил.

Итог
В итоге у нас получилась электронная копия нашей семейной игры. Много лет назад мы играли в нее все вместе, а теперь можно поиграть на телефоне и услышать наши голоса.
Игра называется Magic Crystal Board Game
Если у вас будет минутка, скачайте игру, оставьте отзыв - и вы окажитесь вместе с нами за игровым столом.
Jijiki
прикольно выглядит, посмотрев на крайний скриншот, успехов