CastleDB - это статическая база структурированных данных, для редактирования которых используется одноименный клиент. Данные в CastleDB хранятся в виде JSON файлов, которые можно использовать в проектах на Haxe с помощью библиотеки
castle, значительно упрощающей работу с данными, автоматически генерируя типы данных, хранимых в базе. Кроме использования в проектах на Haxe, есть пример интеграции CastleDB в проектах на Unity - https://blog.kylekukshtel.com/castledb где также используется автоматическая кодогенерация для работы с типизированными данными.
В данном материале я хотел бы рассмотреть работу с редактором CastleDB, в том числе со встроенным в него редактором карт, а также показать небольшой пример интеграции данных в проект на движке Heaps.
Для работы нам понадобится скачать сам клиент с официального сайта, установить библиотеки castle и heaps с haxelib, а также пример базы данных (немного модифицированный пример с официального сайта). Клиент распространяется в виде простого архива и не требует дополнительных действий по установке, нужно его только распаковать в любую удобную для вас папку. Также скачайте и распакуйте куда-нибудь пример базы.
Запускаем клиент и открываем пример (ищем файл data.cdb в папке с примером).
Данные представлены в виде таблицы, где у каждого столбца задан свой тип данных. Узнать тип данных можно кликнув правой кнопкой мыши по заголовку столбца и выбрав пункт “Edit”. При этом типы могут быть составными (об этом чуть позже):
Unique Identifier - уникальный идентификатор, по которому можно будет обращаться к данным в строке с указанным идентификатором. Для полей такого типа макрос генерирует абстрактный enum;
Integer, Float, Bool, Text - думаю, что не нужно объяснять что они означают и какие значения могут хранить;
Color - значение цвета (только RGB), хранится как целое число;
Enumeration - перечислимое. Может принимать единственное значение из заданного списка, который задается как значения, разделенные запятыми:
Для Enumeration, как можно догадаться, при импорте базы в проект также генерируется абстрактный enum. Имя типа генерируемого enum’а формируется как Имялиста_имястолбца. Например, на листе npc есть столбец type с типом Enumeration, тогда enum будет иметь имя Images_type. Стоит отметить, что листам в базе не стоит давать имена с заглавной буквы, иначе имя листа будет совпадать с именем генерируемого для него типа. Для значений Enumeration нет ограничений, можно даже задать им имена-числа, только потом нельзя будет обратится к ним по имени.
Flags - несколько значений из заданного списка, которые храняться в базе как одно целочисленное значение (с помощью битовых операций);
Reference - ссылка на строку из другого листа в данной базе;
File - относительный путь к файлу, хранится как строка;
Image - изображение (не ссылка!), которое хранится в базе. При этом создается дополнительный файл с расширением .img, в котором сохраняются данные изображения в кодировке base64, а в самой базе сохраняется лишь ключ (md5-сумма), по которому эти данные можно найти в img-файле.
Tile - тайл, часть изображения. При этом само изображение в базе не сохраняется и должно находиться рядом с файлом базы (сохраняются только имя файла и данные об используемой области изображения).
List - список структурированных значений. Своего рода таблица, которая встраивается в ячейку. Для таких данных создается скрытая от пользователя таблица. На следующей гифке столбцы triggers и nps как раз имеют тип List:
Custom - собственный enum, конструктор которого может иметь аргументы. Для создания таких enum’ов используется простое текстовое поле, доступное по ссылке “Edit Types” (в нижнем правом углу окна редактора);
Dynamic - любые данные в формате JSON;
Tile Layer - слой карты, в котором хранится информация о тайлах;
Data Layer - слой карты, в котором хранятся дополнительные данные карты, например, описание областей карты, на которых срабатывают определенные триггеры.
Рассмотрев поддерживаемые типы данных, давайте теперь перейдем к практике. Попробуем загрузить данные из официального примера в проект на Heaps.
Создадим проект (как это сделать можно посмотреть здесь), и кроме библиотек heaps и hlsdl (или hdx, у меня, например, проекты, собранные с hlsdl не запускаются из-за того, что встроенная видеокарта не поддерживает OpenGL 3.2 и выше) добавим в зависимости библиотеку castle:
(возможно вам придется еще установить / обновить библиотеку format с haxelib)
Если же хочется увидеть, какой код и типы генерируются макросом, то в hxml-файл можно добавить строчку:
тогда в проекте будет создана папка dump, где можно будет с ними ознакомиться.
Добавим в проект файл Data.hx, поля которого будут генерироваться макросом, для чего необходимо объявить следующий typedef в этом файле:
Как можно догадаться, для генерации полей нужен файл базы данных, который поместим в папку res проекта вместе с остальными файлами из примера базы данных с сайта CastleDB.
Теперь добавим в проект файл основного класса Game.hx:
Как видно, в Heaps уже есть класс h2d.CdbLevel для загрузки и отображения уровней, созданных в CastleDB. К сожалению он несколько ограничен и сохраняет не все данные, которые могут быть в уровне, но для знакомства его более чем достаточно.
Итак, скомпилировав и запустив результат мы увидим уровень из базы, однако на нем будут отсутствовать пара спрайтов персонажей. Дело в том, что персонажи на карте из примера хранятся не в слое тайлов (Tile Layer), а в отдельном слое данных (Data Layer), и выступают как маркеры, по которым можно добавить на игровой уровень соответствующие им сущности.
Чтобы добавить на карту такой слой данных необходимо на лист с данными об уровнях добавить столбец с типом List:
При этом у добавленного List’а должны присутствовать числовые поля x и y. В примере таким столбцом является столбец npc, у которого помимо этих полей добавлены поля kind (ссылка на тип npc), item (ссылка на тип item) и id (простой текст).
После добавления такого столбца в таблице свойств уровней он появится в списке слоев в редакторе уровня:
При этом если в добавленном List’е есть столбец с типом Tile или ссылка на страницу в таблице, в которой есть такой столбец, то в редакторе объекты этого слоя будут отображаться с помощью иконок из столбца с типом Tile.
Редактирование объектов на слое Data Layer можно осуществлять двумя способами:
E, тогда появится окно свойств объекта, где их можно изменить.
Давайте попробуем считать данные из этого слоя и как-нибудь их отобразить с помощью следующего кода, который поместим в конец метода init():
Все довольно просто.
Также у уровня в примере добавлен слой (столбец данных) triggers, в котором задаются данные не по отдельным тайлам на уровне, а для областей тайлов. У таких слоев кроме свойств x и y должны быть свойства width и height (в примере также есть свойство action c пользовательским типом). Данные таких слоев редактируются также, как и в предыдущем примере:
На гифке видно, что при заполнении свойства action у области тайлов осуществляется проверка введенного значения - значение подсвечивается красным до тех пор, пока не будет введено поддерживаемое значение.
А вот как можно считать данные подобного слоя:
Рассмотрим чуть подробнее создание уровней в CastleDB, т.к. не все моменты раскрыты в официальной документации и в статьях в интернете.
При редактировании слоев на уровне доступны несколько режимов:
Tile - каждый тайл на слое задается вручную, либо рандомно из выбранного набора тайлов, либо можно залить область тайлов:
Object - режим, в котором тайлы (объекты) могут располагаться не по сетке, а произвольным образом (но если необходимо поместить объект с привязкой к сетке, то такой режим включается клавишей G). Кроме того, такие объекты можно вращать и зеркалить (клавиши D и F), а также размеры объектов могут быть больше, чем один тайл:
Объекты, помещенные на такой слой, сортируются по Y-координате и могут перекрывать друг друга.
Ground - режим, в котором работает автотайлинг - автоматическое построение границ между областями разных тайлов.
Но для того, чтобы он работал, необходимо задать у используемого тайлсета ряд свойств.
Для начала нужно задать типы тайлов (типы местности). Для этого в панели тайлсета выбираем режим Ground, затем на этой же панели выбираем тайл местности (например, зеленый тайл травы), задаем ему имя (grass), а также приоритет (по-умолчанию он равен нулю). И таким образом задаем все типы местности (на гифке я задал еще тайл для воды).
Далее необходимо задать наборы тайлов, которые будут использоваться для автотайлинга между типами местности. Делается это в режиме Border.
В CastleDB заложено 4 типа таких границ. К сожалению единственная документация по ним есть только в виде комментария в классе cdb.TileBuilder в библиотеке castle:
Corners - для границ такого типа нужен 3х3 блок тайлов. Такие тайлы используются для построения “выпуклых” границ:
Lower corners - для границ такого типа нужен 2х2 блок тайлов. Такие тайлы используются для построения “вогнутых” границ, причем ширина “вогнутой” области должна быть больше одного тайла:
Для “вогнутых” областей шириной в один тайл используются границы типа U corners, для задания которых используются 3х3 блоки тайлов:
Задание границ осуществляется после того, как были заданы Ground тайлы.
Для этого нужно перейти в режим Border на панели редактирования тайлсета, выбрать блок тайлов для границы, задать типы тайлов, для которых эта граница будет использоваться, и задать тип границы.
При выборе тайлов, между которыми будут строится задаваемые границы, выбираются “внутренние” (In) и “внешние” (Out) тайлы. При этом можно выбрать либо конкретный тип тайла (например, grass), либо приоритет (lower или upper), который задавался при создании типов тайлов. Таким образом, есть возможность задать границу между каким-то конкретным типом местности и группой других типов с высшим или низшим приоритетом:
Для тайлсетов имеется также возможность создания именованных групп тайлов (режим Group в панели редактирования свойств тайлсета), которые затем можно как-то интерпретировать при загрузке базы данных в игре (пример будет приведен далее):
Кроме стандартных свойств тайлов (ground, border, group) возможно задание собственных. Для этих целей используется стандартный столбец tileProps (по-умолчанию он создается пустым). Тип этого столбца - List, следовательно тайлам можно задать сколько угодно дополнительных свойств. Однако следует иметь в виду, что такие свойства являются общими для всех карт, использующих одинаковые тайлсеты.
В примере базы данных в столбце tileProps добавлены столбцы collide (ссылка на лист collide) и hideHero (тип Enumeration). На следующей гифке показано задание свойства collide у тайлов:
Если в качестве изображения для тайлсета выбрано изображение, которое уже использовалось, то новый тайлсет не будет создан, а будет использоваться уже существующий со всеми его свойствами.
Попробуем теперь как-нибудь использовать свойство collide в проекте на Heaps (здесь для простоты я сделаю только отображение свойств тайлов):
И для полноты картины приведу еще пару примеров загрузки разных типов данных из базы.
Загрузка свойств групп тайлов (режим Group на панели редактирования свойств тайлсета) в настоящий момент не поддерживается в классе h2d.CdbLevel, однако я внес в него небольшие изменения и отправил Pull Request для этого. Найти дополненную версию класса можно здесь. Используем ее и покажем анимацию, состоящую из тайлов группы:
Рассмотрим как загрузить изображение (тип Image, а не Tile), сохраненное в базе данных. В примере я добавил лист, в котором один из столбцов имеет такой тип:
После добавления изображений на этот лист рядом с файлом базы данных data.cdb автоматически был создан файл data.img, который на самом деле является json. В нем в качестве ключей используются md5-суммы для изображений, а в качестве значений - данные изображений в кодировке base64. А в самой базе (в файле data.cdb) хранятся только md5-суммы изображений, по которым мы можем найти нужные нам данные.
Функция загрузки данных изображений может иметь следующий вид:
А использовать результаты ее работы можно так:
Пример чтения значений типа Enumeration из базы данных:
Пример чтения значений типа Flags из базы данных:
И в качестве последнего простейшего примера приведем загрузку текстового файла, ссылка на который хранится в столбце datafile листа npc:
Вот мы и рассмотрели основные типы данных в CastleDB, а также работу с ними в проектах на Haxe. Надеюсь, что материал вам понравился и показался интересным.
При написании этого материала использовались следующие источники: