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. Надеюсь, что материал вам понравился и показался интересным.
При написании этого материала использовались следующие источники: