
Вы когда-нибудь заполняли json-конфигов на 20 000 - 25 0000 строк вручную перед выпуском релиза? А боль в глазах геймдизайнеров от этого процесса видели?
Вы TechLead/Менеджер и к вам часто подходят дизайнеры с просьбой написать парсер данных, а свободных рук нет? А после изменений в фиче приходится выделять человека для правок схемы парсинга под новую разметку?
Возможно, пора начать использовать тулзу по экспорту данных, которую смогут настроить сами геймдизайнеры? Нам этот инструмент сократил время настройки игровых конфигов с 4 дней до 15 минут, после предварительной настройки.
Привет! Меня зовут Игорь, я занимаюсь разработкой на Unity c 2018 года. В статье рассказываю о тулзе, с помощью которой можно выгружать данные из Google Sheets и Excel в json без привлечения программистов.
P.S. в конце статьи есть видосик с процессом настройки и экспорта одной фичи.
Оглавление
Контекст и требования к инструменту
Имеется мультиплеерный F2P проект. Релизы раз в две недели.
Практически каждый релиз выпускаются новые фичи.
Иногда могут происходить ребалансы уже существующих фич.
Несколько раз был глобальный ребаланс (баланс менялся практически в каждой фиче).
Так вот к чему это?
Представьте сколько человеко-часов на то, чтобы данные просто перенести из таблиц в конфиги.
Представьте кол-во итераций изменений конфигов для внутреннего теста баланса.
Представьте сколько времени нужно, чтобы тестировщики проверили итоговый результат. Представьте кол-во ошибок при переносе.
Эта рутинная работа занимает много времени людей, вместо того чтобы заниматься более важными задачами. Такие длительные процедуры создают риски срыва даты релиза.
Меня, как техлида, это не устраивало, хотелось как-то помочь ребятам.
Так появились мысли о том, что нужен инструмент экспорта\парсинга, который упростит жизнь и сделает это дело более надёжным. Но не хотелось создавать инструмент слишком частным, чтобы его постоянно приходилось допиливать и постоянно выделять под это дело программистов.
К инструменту появились следующие требования:
Геймдизайнеры должны самостоятельно настраивать и перенастраивать инструмент по мере необходимости
Инструмент не должен требовать знаний программирования (актуально для геймдизов)
Инструмент не должен требовать привлечения программистов (Вообще. Это было самое важное условие для меня)
В json конфигах может быть большая вложенность уровней. Инструмент должен быть универсальным, поддерживать неограниченное кол-во уровней вложенности.
Инструмент должен поддерживать возможность использовать ячейки из таблиц гибко, а не требовать таблицу MxN (данные могут быть разбросаны на разных страницах и т.д.)
Инструмент должен иметь возможность заполнять объекты json с плавающим кол-вом полей (например, массив объектов, где один элемент будет иметь 2 поля, а другой 4 поля)
Инструмент, там где это возможно, не должен прекращать парсинг, если что-то неправильно введено. Ошибки должны подсвечиваться как предупреждение.
Открытый исходный код. В компании достаточно строгие правила по безопасности.
Левый софт особо не поиспользуешь (NDA, потенциальные утечки и т.д.).Инструмент должен предоставлять возможность выносить повторяющуюся структуру json‑объектов в аналог математической функции, которая принимает параметры на вход
func(level, amount), и преобразует это в блок текста json
Позднее добавились пожелания от геймдизайнеров:
Каждый новый уровень json - это отдельный Google Sheets лист
Все фичи можно размещать вперемешку на Google Sheets листах с уровнями семантики парсинга (не создавать под каждую фичу свои листы с уровнями, разделять фичи за счёт группировки строк через сворачивание в “+”)
Изначально искали решение на просторах интернета. К сожалению ничего полезного найти не удалось, всё что попадалось решало очень частные задачи.
Решил написать самостоятельно, как пет-проект.
Немного теории о структуре json
Json можно представлять в виде иерархической (древовидной) структуру, где есть верхнее звено и от него расходятся ветви, которые имеют свои звенья. Мы можем обходить это дерево. Каждый раз когда мы переходим по ветви глубже, мы спускаемся на следующий уровень. На следующем уровне может быть как звено, которое ведёт к следующему уровню, любо содержать конечное значение (строку, число, логическое значение).

Идея парсера в том, чтобы описать эти звенья как промежуточные узлы в таком формате, чтобы Google Sheets и Excel возможности могли относительно легко подставлять значения в промежуточные части, если придерживаться определённой семантической структуры.

О парсере
Исходя из идеи парсера и требований к этому парсеру, было получено решение, которое описывает узлы как структуру название(name) - тип данных(type) - значение (value). Т.к. следующий уровень может быть узлом, а не значением, то значение может быть ссылающиеся на другой узел (reference type - ref).
Так получился парсер, который поддерживает следующие типы данных-полей:
str – текстовое поле
num – числовое поле
bool – логическое поле
null – поле со значением null
object – вложенный json объект (именуется как ref)
arr - массив других типов
alias функции – аббревиатура формата математической функции
func(level, amount)для повторяющихся элементов, принимающая изменяющиеся данные как параметр.
Видео с примером использования для Google Sheets
Ниже на видео пример использования парсера с применением формулы FILTER для экспорта данных из Google Sheets.
Что на видео:
В начале видео показана таблица данных, которую нужно экспортировать в json. Показан примерный формат json, который используется для фичи (пример данных для конфига).
Подготовка парсера (настройка alias функций).
Это не полная процедура настройки парсера, а только основной фрагмент.
Остальная часть — это добавление в конфиг парсинга нескольких строк с описанием фичи.Заполнение семантики парсинга в Google Sheets документе
Подготовка исходных данных, для применения формулы FILTER
Заполнение оставшейся семантики с использованием формулы FILTER
Процесс экспорта данных
Если видео не загрузится: Youtube
P.S. В Excel формулы немного другие из-за разной работы формул.
Итог: за 13 минут (видео ускорил, чтобы не смотреть рутинные действия) сделал частичную предварительную настройку парсера и экспортировал 400 строк данных в json файл. На продакшен проекте такая фича является ротируемой(каждые 2 недели смена), обычно имеет 30 stages, около 15 вариантов alias функций, что в среднем даёт 2 000 json-строк.
Дисклеймер
Первичная настройка парсера и семантики может быть долгой. Зависит от Ваших навыков владения Google Sheets/Excel и фантазии как по удобнее располагать данные для скорости интеграции.
Что инструмент умеет?
Поддерживает Excel и Google Sheets источники данных
Гибкая настройка схемы парсинга json с неограниченной вложенностью объектов. Схема переопределяется непосредственно в источнике с данными. Не требует привлечения программистов и не требует изменения исходного кода.
Предварительно нужно понять простою идею как заполняются данные и как происходит разделение на слои.Добавлены два типа Alias функций (функции которые сокращают повторяющиеся структуры за счёт параметров)
TableAliasFunc - настраивается в Google Sheets на отдельных Google Sheets листах (по тем же правилам, что и обычная семантика)
JsonAliasFunc - настраиваются в отдельных json-файлах.Alias-функции поддерживают как именованные параметры (человеко читаемые: amount, level и т.д.), так и анонимные (arg1, arg2, arg3). Читайте инструкцию как какие параметры использовать.
TableAlias-функции могут использовать другие alias функции, включая JsonAlias-функции.
Параметры Alias-функций можно пробрасывать во вложенные Alias-функции
Можно настроить место парсинга фичей, каждая фича в свой файл, по своему пути.
Открытый исходный код. Если нужно сканируйте/изучайте исходники, можно вносить изменения под свой проект.
Mit-лицензия. Можно использовать бесплатно, включая коммерческое использование.
Где найти парсер?
В репозитории с исходным кодом на GitHub написана подробная инструкция, как настроить и использовать. Так же написана и инструкция как подключить Google Sheets. В репозитории Вы найдёте больше примеров семантики парсинга в разных вариации в Excel-файле и по ссылке на такой Google Sheets файл.
GitHub репозиторий: Тык
Кому хочется побыстрее посмотреть семантику парсинга в Google Sheets: Тык
P.S. Документ доступен как пример в режиме только для чтения. Если хотите что-то потыкать, то копируйте на свой диск.
Как рекомендую действовать:
Сделать настройку по инструкции из репозитория
Рекомендую почитать инструкцию, хотя бы по диагонали (инструкция должна снять большинство вопросов)
Потыкать файлы в папке Example (Config.json, JsonAliasFuncs)
Отключить лишние тестовые фичи для парсинга из тестового конфига парсинга и включать их по одной, попутно экспериментируя с редактированием и добавлением полей.
Дополнительно
Если вас тоже интересует улучшение рабочих процессов, то возможно вас заинтересуют другие мои статьи:
Как при помощи ведения дневника событий искать точки роста, оценивать свой результат, проводить ретроспективу и давать конкретную обратную связь:
Статья «Activity Journal. Самоорганизация и анализ рабочего времени. На что уходят рабочие часы?»Как с помощью код-анализа можно ускорить изучение\понимание кодовой базы:
Статья: «Unity, Roslyn и code analysis. Прикладная задача визуализации графа состояний»
Комментарии (16)

HexGrimm
27.05.2026 06:11У вас первоисточником данных теперь являются эксель/гугл файлы? У вас дизайнеры еще не переизобрели гит случаем?
Это тупиковый путь развития, если есть только экспорт, и нет импорта обратно.
Первоисточник может лежать только в паре с исходным кодом, так как настоящая схема данных это и есть код. Любое изменение в дереве связей синхронизировано с кодом, и перестает быть совместимым если ошибиться.
После 30 версий и одновременных хотфиксов старых версий в проде, у вас будут бесчисленные каталоги ссылок на все версии документов, заполненные руками, и ручной мердж и бекпорт всех изменений по этим каталогам в обратную сторону. Так работать нежелательно...
Если есть возможность хранить джейсоны в гите, и только для редактирования временно доставать в таблицы, то это может быть решением.

saigor33 Автор
27.05.2026 06:11Да, у нас первоисточником всегда являются гугл файлы. Там все математические рассуждения, выводы и т.д. Json файлы это скорее слепок результата.
Дизайнеры добавляют json конфиги в проект через гит (когда хотят внести какие-то правки в баланс).
Да, действительно остаётся только экспорт. Когда добавляется/изменяется схема данных фичи, дизайнера уведомляют в pull request и он может скорректировать схему данных в гугл таблице (сразу сейчас или позже. Это не тормозит разработку)
Верное утверждение, что хранение схемы данных вместе с кодом - это гарантия совместимости схемы данных. Тут в первую очередь хотелось добиться гибкости именно на стороне источника данных (гугл таблиц).
Интересная мысль по поводу обратного импорта json только для редактирования. Никогда не задумывался об этом. Звучит немного как создать свою базу данных с описанием семантики восстановления, но идея интересная, спасибо.
Вы импортируете данные обратно в гугл таблицы данные?
HexGrimm
27.05.2026 06:11У нас две системы:
JSON в гите, там где данные очень древовидные и имеют опциональные ветви.
Табличные файлы (в основном в гугле) - Там хранится 2д табличный формат, заранее продуманый программистом для каждого листа, и который не планируется менять. Например, цены в магазинах, календарь эвентов или акций. Там у нас тоже только экспорт.
Но я в нашей компании был против п.2, тк теперь вся безопасность конфигов, их тестирование и бекпорт фиксов легли на сторону нетехнарей. Хотя большую часть вещей можно было бы проверять автоматизировано. Как по мне, проще доразвить вариант из п.1, сохранив надежность, чем делать что-то разваливающееся и чинить потом только руками.

HexGrimm
27.05.2026 06:11Ну и самое главное, путем каталогирования ссылок на эти все гугл документы, дизайнеры, очевидно, изобрели гит и гитмердж, только полностью в ручном режиме, со всеми ручными рисками ошибок.

saigor33 Автор
27.05.2026 06:112. Табличные файлы (в основном в гугле)
Я правильно понял, что в этой системе все данные линейные (т.е. однотипные например колонка
arrayIndex, price, amount). Нет возможности сделать вложенность (точнее она есть, но её разбивает программист на несколько листов)?
И появляются не удобства, что разное кол-во полей для json не сделать?1. JSON в гите
Вопросы:
1) В итоге не совсем понял. Импортируете ли данные из json в гугл таблицы?
2) Если да, то как определяется в какой структуре должны отображаться импортируемые данные?
Полностью линейно их сделать достаточно сложно (возможно, но сложно. Либо на листе куча колонок с пустотами, либо множество листов с уровнями и скорее всего больше, чем уровней) .
3) Как в таком случае геймдизайнеры работают с этими данными? Им скорее всего приходится достаточно сложные манипуляции делать, чтобы соединить их в удобном им виде.
4) И как они работают с возможностями гугл таблиц (построение графиков, расчёты и математическое рассуждения этих расчётов)?
5) Как в случае каких либо изменений в схеме данных сохраняются все применения формул и ссылки на данные (для графиков и т.д.)Для меня обратный импорт сопровождается сложностью определения структуры внутри гугл таблицы. Эта структура одновременно должна быть удобной для геймдизайнеров (чтобы не усложнять им жизнь) и одновременно строго определённой, чтобы импорт смог сработать.
Я так понимаю геймдизайнеры достаточно сильно загнаны в определённые рамки? Как они к этой системе относятся?

VitalyZaborov
27.05.2026 06:11Если у вас игра целиком на Unity, то зачем вам json как промежуточный формат? Почему бы сразу не просить данные в готовые структуры на C#?

saigor33 Автор
27.05.2026 06:11Игра мультиплеерная (есть сервер и клиент).
Вы имете в виду почему мы напрямую не запрашиваем данные из гугл. таблиц при загрузки сервера?

VitalyZaborov
27.05.2026 06:11Нет. У вас есть какие-то игровые параметры где-то в таблице. Данной тулзой вы их конвертирует в json. Но этот json, очевидно, сервер или клиент (или оба) должны прочитать чтобы эти данные использовать. И не просто прочитать, а наверное как-то распарсить по своим структурам. Отсюда вопрос: если json всё равно генерится, то может стоит вместо него генерить сразу исполняемый код на C#, который все эти структуры будет создавать? Если сервер и клиент на разных языках (и даже более чем на двух) - то вопросов нет. Но если на одном - такой подход избавит от необходимости поддерживать парсер, усложнит вытаскивание данных из клиента и даже немного сэкономмт время на загрузке клиента и сервера. В зависимости от договоренностей между ГД и программистами такой подход может оказаться и более гибким в реализации, и простым в работе.

saigor33 Автор
27.05.2026 06:11Не совсем понимаю о чём речь.
Я правильно понял, что вопрос почему мы не парсим таблицы в захардкоженный код?
Чтобы из таблицы в код помещать сразу захардкоженные константы?Что-то вроде этого?
class Event // Описание структуры { public string name; public long price; public float difficulty; } class Config { public Event[] events = // Заполнение данными [ new Event { name = "Event10", price = 1000, difficulty = 0.5f }, new Event { name = "Event11", price = 1000, difficulty = 0.5f } ] }Но этот json, очевидно, сервер или клиент (или оба) должны прочитать чтобы эти данные использовать. И не просто прочитать, а наверное как-то распарсить по своим структурам
У нас json повторяет структуру класса в который данные будут загружены. Мы в коде не дописываем какую-то дополнительную логику заполнения структуры данными json. Наш сериализатор автоматически по имени поля и типу данных маппит данные, а затем создаёт экземпляр необходимого класса.

VitalyZaborov
27.05.2026 06:11Что-то вроде этого?
Да, например так.
У нас json повторяет структуру класса в который данные будут загружены.
А вот это интересно. Что если в таблице (а за ней - и в json) появилось какое-то новое поле, которого у экземпляра класса нет? Может, дизайнер ошибся, а может делается новая фича и это поле как раз нужно добавить.Новое поле каким-то образом будет сгенерировано у нужного класса или мы получим ошибку при загрузке данных? Может, есть какая-то валидация в момент генерации json?

saigor33 Автор
27.05.2026 06:11Теперь понял что вы имели ввиду. Тогда по порядку.
Почему бы сразу не просить данные в готовые структуры на C#?
Честно скажу никогда не думал о том чтобы так делать в продакшен решении. Делал так в своих проектах, когда начинал изучать геймдев, по воспоминаниям это было скорее из-за не знаю и не умения хранить данные по другому (тогда хранил 30 000 строк данных в таком формате).
Сейчас могу предположить, что такой вариант хранения не очень удобный. Дело в том, что на проекте работают не только программисты, но и геймдизайнеры, тестировщики, художники, саппорт. Им иногда нужно корректировать конфиги, чтобы запустить игру в определённой ситуации.
Чтобы они могли редактировать конфиги у них останется два варианта:
- использовать гугл таблицу + парсер
- изучать семантику c# языка, которая для не профильных специалистов может быть сложной (Даже с подсветкой вариантов заполнения от IDE, и то это потребует всем установить такой софт).На мой взгляд такой вариант может создать лишние сложности, чем принести пользы.
даже немного сэкономмт время на загрузке клиента и сервера
У нас не так много данных, чтобы потребовалось задумываться об оптимизации чтения конфигов (думаю где-то 30-40к строк). Думаю их нужно несколько миллионов строк, чтобы почувствовать задержку, но и то для сервера это не так критично (т.к. он запускается и 2 недели работает без остановки).
Что если в таблице (а за ней - и в json) появилось какое-то новое поле, которого у экземпляра класса нет? ... Новое поле каким-то образом будет сгенерировано у нужного класса или мы получим ошибку при загрузке данных? Может, есть какая-то валидация в момент генерации json?
Первичной структурой данных является class в c# коде. Инструментов автоматического добавление полей в c# код нет (структуру определяет программист при разработки фичи).
Дизайнер может ошибиться двумя способами:
1. Опечатся(равносильно тому, что появляется лишнее поле)/Добавить лишнее поле.
2. Забыть ввести какое-то поле.Проектный сериализатор берёт целевой class, через рефлексию смотрит какие существуют поля у этого класса и заполняет их исходя из значения json. Json это по сути
Dictionary<string, object>, где ключ string - это название поля в json. Соответственно, когда происходит десериализация (из json в c# класс) в первую очередь ищутся известные поля (которые заданы в c# классе), если встретится неизвестное поле, то произойдёт краш при попытке десериализовать json.P.S. Десериализация немного сложнее, чем я описал выше(Выше упрощённый вариант для понимания концепции). При десериализации не создаётся
Dictionary<string, object>(чтобы не было генерации мусора в памяти) а происходит чтение json как текста по токенам (это всякие{,},[,],:,",.,,,") стремящееся к O(n) и сразу же создание экземпляра класса через класс System.Activator.На проекте есть простая валидация в виде юнит тестов (запуск юнит тестов автоматизирован на CI/CD). Суть юнит теста в том, чтобы загрузить все имеющиеся конфиги. Ну и если что-то не получится загрузить, то тесты будут красные.
Что касается
Ошибки. Забыть ввести какое-то поле.Если при десериализации какое-то поле отсутствует в json, то поле заполняется дефолтным значением в зависимости от типа данных (0,null,falseи т.д.).
VitalyZaborov
27.05.2026 06:11Я применял такой подход в нескольких личных проектах уже больше 10 лет, работал над одним ААА проектом и наблюдал со стороны ещё за несколькими где было также. Из-за NDA не могу сказать что это за игры, но вы наверняка о них слышали и может даже играли.
Каких-то проблем не наблюдалось. Тут важно понимать, что сгенерированный код никто руками не правит. На него можно даже не смотреть. У вас есть удобный для человека инструмент, и когда художнику надо что-то поменять - он лезет в таблицу, жмёт кнопку в тулзе и получает результат. Это гораздо удобнее чем править json на 20.000 строк.
"Источником правды" в данном случае является редактор. Как версионировать его данные - отдельный вопрос. Можно держать под гитом сами таблицы, можно использовать Google Sheets или выбрать какой-то ещё способ. Теоретически, сгенерированный код можно вообще не держать под гитом, а генерировать хуками при каждом pull / switch, но это уже детали.
Замечу, что кодогенерация подходит только для статических данных, например, для характеристик персонажей, предметов, квестов и т. п. Если речь, к примеру, о конфиге скидок на новогоднюю акцию, который сервер должен прожевать без рестарта и который еще надо отдать вебу чтобы отобразить на скидки сайте - тот тут обычный текстовый вариант гораздо удобнее.
shai_hulud
Привет, интересный инструмент вы сделали. На Хабре периодически выходят статьи как люди пытаются обуздать JSON или Excel. А вы не рассматривали уже существующие инструменты, типа https://gamedevware.github.io/charon/unity/overview.html? Если пробовали(необязательно этот), чем они не подошли?
saigor33 Автор
Спасибо, за ссылку на Charon, ни разу не слышал о нём.
Посмотрю что это такое. (Пока что показалось что это для Scriptable Object, у нас все конфиги на сервере в json файлах лежат)
Мы пытались найти инструменты, которые бы решили вопросы экспорта, но не нашли.
Попадались решения у которых была слишком жёсткая схема (обязательно таблица, где столбцы заполняют всегда существующие поля, т.е. нет вариативности типов).
Несколько раз писали парсеры под фичу, где много данных (Скачивался Excel файл, и через тулзы вручную считывались захардкоженные ячейки из таблицы).
shai_hulud
ScriptableObject там для удобства использования в Unity, источник правды это JSON файл. Схема данных внутри. Экспорт в Excel и обратно есть.