Комментарии 16
Привет, интересный инструмент вы сделали. На Хабре периодически выходят статьи как люди пытаются обуздать JSON или Excel. А вы не рассматривали уже существующие инструменты, типа https://gamedevware.github.io/charon/unity/overview.html? Если пробовали(необязательно этот), чем они не подошли?

Спасибо, за ссылку на Charon, ни разу не слышал о нём.
Посмотрю что это такое. (Пока что показалось что это для Scriptable Object, у нас все конфиги на сервере в json файлах лежат)
Мы пытались найти инструменты, которые бы решили вопросы экспорта, но не нашли.
Попадались решения у которых была слишком жёсткая схема (обязательно таблица, где столбцы заполняют всегда существующие поля, т.е. нет вариативности типов).
Несколько раз писали парсеры под фичу, где много данных (Скачивался Excel файл, и через тулзы вручную считывались захардкоженные ячейки из таблицы).
У вас первоисточником данных теперь являются эксель/гугл файлы? У вас дизайнеры еще не переизобрели гит случаем?
Это тупиковый путь развития, если есть только экспорт, и нет импорта обратно.
Первоисточник может лежать только в паре с исходным кодом, так как настоящая схема данных это и есть код. Любое изменение в дереве связей синхронизировано с кодом, и перестает быть совместимым если ошибиться.
После 30 версий и одновременных хотфиксов старых версий в проде, у вас будут бесчисленные каталоги ссылок на все версии документов, заполненные руками, и ручной мердж и бекпорт всех изменений по этим каталогам в обратную сторону. Так работать нежелательно...
Если есть возможность хранить джейсоны в гите, и только для редактирования временно доставать в таблицы, то это может быть решением.
Да, у нас первоисточником всегда являются гугл файлы. Там все математические рассуждения, выводы и т.д. Json файлы это скорее слепок результата.
Дизайнеры добавляют json конфиги в проект через гит (когда хотят внести какие-то правки в баланс).
Да, действительно остаётся только экспорт. Когда добавляется/изменяется схема данных фичи, дизайнера уведомляют в pull request и он может скорректировать схему данных в гугл таблице (сразу сейчас или позже. Это не тормозит разработку)
Верное утверждение, что хранение схемы данных вместе с кодом - это гарантия совместимости схемы данных. Тут в первую очередь хотелось добиться гибкости именно на стороне источника данных (гугл таблиц).
Интересная мысль по поводу обратного импорта json только для редактирования. Никогда не задумывался об этом. Звучит немного как создать свою базу данных с описанием семантики восстановления, но идея интересная, спасибо.
Вы импортируете данные обратно в гугл таблицы данные?
У нас две системы:
JSON в гите, там где данные очень древовидные и имеют опциональные ветви.
Табличные файлы (в основном в гугле) - Там хранится 2д табличный формат, заранее продуманый программистом для каждого листа, и который не планируется менять. Например, цены в магазинах, календарь эвентов или акций. Там у нас тоже только экспорт.
Но я в нашей компании был против п.2, тк теперь вся безопасность конфигов, их тестирование и бекпорт фиксов легли на сторону нетехнарей. Хотя большую часть вещей можно было бы проверять автоматизировано. Как по мне, проще доразвить вариант из п.1, сохранив надежность, чем делать что-то разваливающееся и чинить потом только руками.
Ну и самое главное, путем каталогирования ссылок на эти все гугл документы, дизайнеры, очевидно, изобрели гит и гитмердж, только полностью в ручном режиме, со всеми ручными рисками ошибок.
2. Табличные файлы (в основном в гугле)
Я правильно понял, что в этой системе все данные линейные (т.е. однотипные например колонка arrayIndex, price, amount). Нет возможности сделать вложенность (точнее она есть, но её разбивает программист на несколько листов)?
И появляются не удобства, что разное кол-во полей для json не сделать?
1. JSON в гите
Вопросы:
1) В итоге не совсем понял. Импортируете ли данные из json в гугл таблицы?
2) Если да, то как определяется в какой структуре должны отображаться импортируемые данные?
Полностью линейно их сделать достаточно сложно (возможно, но сложно. Либо на листе куча колонок с пустотами, либо множество листов с уровнями и скорее всего больше, чем уровней) .
3) Как в таком случае геймдизайнеры работают с этими данными? Им скорее всего приходится достаточно сложные манипуляции делать, чтобы соединить их в удобном им виде.
4) И как они работают с возможностями гугл таблиц (построение графиков, расчёты и математическое рассуждения этих расчётов)?
5) Как в случае каких либо изменений в схеме данных сохраняются все применения формул и ссылки на данные (для графиков и т.д.)
Для меня обратный импорт сопровождается сложностью определения структуры внутри гугл таблицы. Эта структура одновременно должна быть удобной для геймдизайнеров (чтобы не усложнять им жизнь) и одновременно строго определённой, чтобы импорт смог сработать.
Я так понимаю геймдизайнеры достаточно сильно загнаны в определённые рамки? Как они к этой системе относятся?
Если у вас игра целиком на Unity, то зачем вам json как промежуточный формат? Почему бы сразу не просить данные в готовые структуры на C#?
Игра мультиплеерная (есть сервер и клиент).
Вы имете в виду почему мы напрямую не запрашиваем данные из гугл. таблиц при загрузки сервера?
Нет. У вас есть какие-то игровые параметры где-то в таблице. Данной тулзой вы их конвертирует в json. Но этот json, очевидно, сервер или клиент (или оба) должны прочитать чтобы эти данные использовать. И не просто прочитать, а наверное как-то распарсить по своим структурам. Отсюда вопрос: если json всё равно генерится, то может стоит вместо него генерить сразу исполняемый код на C#, который все эти структуры будет создавать? Если сервер и клиент на разных языках (и даже более чем на двух) - то вопросов нет. Но если на одном - такой подход избавит от необходимости поддерживать парсер, усложнит вытаскивание данных из клиента и даже немного сэкономмт время на загрузке клиента и сервера. В зависимости от договоренностей между ГД и программистами такой подход может оказаться и более гибким в реализации, и простым в работе.
Не совсем понимаю о чём речь.
Я правильно понял, что вопрос почему мы не парсим таблицы в захардкоженный код?
Чтобы из таблицы в код помещать сразу захардкоженные константы?
Что-то вроде этого?
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. Наш сериализатор автоматически по имени поля и типу данных маппит данные, а затем создаёт экземпляр необходимого класса.
Что-то вроде этого?
Да, например так.
У нас json повторяет структуру класса в который данные будут загружены.
А вот это интересно. Что если в таблице (а за ней - и в json) появилось какое-то новое поле, которого у экземпляра класса нет? Может, дизайнер ошибся, а может делается новая фича и это поле как раз нужно добавить.Новое поле каким-то образом будет сгенерировано у нужного класса или мы получим ошибку при загрузке данных? Может, есть какая-то валидация в момент генерации json?
Теперь понял что вы имели ввиду. Тогда по порядку.
Почему бы сразу не просить данные в готовые структуры на 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 и т.д.).
Я применял такой подход в нескольких личных проектах уже больше 10 лет, работал над одним ААА проектом и наблюдал со стороны ещё за несколькими где было также. Из-за NDA не могу сказать что это за игры, но вы наверняка о них слышали и может даже играли.
Каких-то проблем не наблюдалось. Тут важно понимать, что сгенерированный код никто руками не правит. На него можно даже не смотреть. У вас есть удобный для человека инструмент, и когда художнику надо что-то поменять - он лезет в таблицу, жмёт кнопку в тулзе и получает результат. Это гораздо удобнее чем править json на 20.000 строк.
"Источником правды" в данном случае является редактор. Как версионировать его данные - отдельный вопрос. Можно держать под гитом сами таблицы, можно использовать Google Sheets или выбрать какой-то ещё способ. Теоретически, сгенерированный код можно вообще не держать под гитом, а генерировать хуками при каждом pull / switch, но это уже детали.
Замечу, что кодогенерация подходит только для статических данных, например, для характеристик персонажей, предметов, квестов и т. п. Если речь, к примеру, о конфиге скидок на новогоднюю акцию, который сервер должен прожевать без рестарта и который еще надо отдать вебу чтобы отобразить на скидки сайте - тот тут обычный текстовый вариант гораздо удобнее.

Gamedev. Парсинг данных из Google Sheets и Excel в json без привлечения программистов