Принцип работы с наборами тайлов (тайлсетами)
Версия №1. В процессе проектирования.
Более старые версии:
- нет.
Данный текст описывает только проект, т.е. относительно подробное, и в то же время достаточно абстрактное описание того как все должно быть устроено и как должно работать.
Организация файлов тайлсетов
Все тайлсеты расположены в одном каталоге. Внутри этого каталога - по подкаталогу на каждый тайлсет, имя каталога является именем тайлсета. Внутри каждого каталога тайлсета содержатся следующие файлы:
- TileSet.ini - настройки тайлсета. Формат файла описан тут.
- Если набор не сжатый - то куча *.bmp-файлов с тайлами с именем вида {TileName}.bmp, где {TileName}=T_{Layer}_{Terrains}_{VarNumber}, где {Layer} - двузначное число, номер слоя (любое число от единицы до 99, ибо номер слоя 00 зарезервирован); {VarNumber} - двузначное число, номер варианта данного тайла; {Terrains}={Terr1}_{Terr2}_{Terr3}_{Terr4}, где {Terr1}-{Terr4} соотв названия террейнов в углах тайла - по строчкам, справа-налево, сверху-вниз, вот так:
Terr1 | Terr2 |
Terr3 | Terr4 |
- Если набор сжатый - куча *.7z-файлов с bmp-хами с тайлами с именем вида {TileName}.bmp, где {TileName} - то же самое, что описано выше.
- Пустые файлы с именами вида Deny_{Terr1}_{Terr2} где Terr1 и Terr2 - имена террейнов, между которыми запрещены переходы. Именем террейна может быть зарезервированное имя All что означает все террейны того же слоя что и второй террейн. По-умолчанию все переходы разрешены. Наличие такого файла запрещает переход. Порядок следования террейнов в имени значения не имеет. Смысловые дубли не должны мешать правильной работе реализации, но в то же время реализация должна стараться не допускать появления таких дублей, а также отслеживать их и при обнаружении вычищать. Имена Deny имеют более высокий приоритет над разрешением всех переходов по умолчанию и более низкий к именам Allow, поэтому можно сначала запретить все переходы для какого-либо террейна, а затем уже разрешать определенные переходы через Allow. Писать Deny_All_All нельзя, такой файл если он появится - должен быть удален, ибо настройки переходов в реализации должны идти для каждого террейна отдельно.
- Пустые файлы с именами вида Allow_{Terr1}_{Terr2} где Terr1 и Terr2 - имена террейнов, между которыми разрешены переходы. Использование имени All в данном случае бессмысленно, ибо по умолчанию все переходы уже разрешены. Отношение к дублям - такое же как для Deny.
Принцип реализации
Работа с тайлами реализуется несколькими взаимозависимыми классами, связанными через их элементы-ссылки. Основным классом, связывающим все остальные является TTileSet, который реализует работу с содержимым одного набора тайлов. Есть более высокий уровень - TTileSetsCollection - фактически контейнер, содержащий ссылки на объекты типа TTileSet для всех доступных программе тайлсетов. TTileSetsCollection содержит:
- Строка - имя набора тайлсетов (возм. по имени каталога - это чисто для юзверя).
- Массив ссылок на все объекты тайлсетов.
- Путь по которому располагаются тайлсеты.
- Путь к временному каталогу для временных файлов при работе с тайлсетами.
- Ссылка на CallBack-метод, для синхронизации при многопоточной работе, с учетом того что CallBack будет иметь доступ к инфе объекта и всей вложенной инфе.
- Булево значение - если true то CallBack используется.
Также содержит методы:
- Удалений, добавлений тайлсетов.
- Метод, осуществляющие собственно чтение набора тайлсетов.
- Метод, возвращающий true если в каком-либо из тайлсетов есть "неправильные" (с нестандартной структурой) тайлы и false если таких нет.
TTileSet содержит:
- Обратную ссылку на объект TTileSetsCollection.
- Ссылку на объект, реализующий работу с файлом TileSet.ini для этого тайлсета (TTileSetIni) - описание формата смотри тут.
- Строка - имя тайлсета.
- Строка - полный путь к тайлсету.
- Строка - полный путь к временной директории тайлсета (используется если он сжатый).
- Булево значение - используется ли svn.
- Число - номер ревизии в случае если используется svn.
- Массив ссылок на все тайлы, принадлежащие тайлсету - точнее на объекты класса TTile, реализующего работу с тайлом.
- Массив ссылок на все тайлы, файлы которых реально существуют.
- Массив ссылок на все тайлы, конфигурация которых неправильна (т.е. является одним из вариантов поворота для одной из стандартных конфигураций, а значит такой тайл требуется перевернуть в стандартную конфигурацию при нулевом повороте и переименовать).
- Массив ссылок на все террейны, принадлежащие тайлсету - т.е. на объекты класса TTerrain, реализующего работу с террейном.
- Массив ссылок на объекты класса TLayer, который по-сути является контейнером ссылок на объекты TTerrain и содержит ссылки на все террейны своего слоя.
- Ссылка на объект класса TMasksSet - описание класса смотри на странице? с описанием работы масок.
- Список строк Allow и Deny для информации из имен файлов разрешений-запретов переходов.
- Дополнительная информация о тайлсете, например номер ревизии в случае если используется svn.
- Объект класса TFlistFormat для списка тайлов в сжатом тайлсете.
- Объект класса TFlistFormat для списка тайлов в временном каталоге с тайлами тайлсета. Последние 2 элемента - используются только если работаем со сжатым тайлсетом.
- Список строк - имена всех террейнов тайлсета, вне выполнения методов должен быть в алфавитном порядке и соответствовать приоритету террейнов.
- Булево значение - true если есть "неправильные" тайлы и false если таковых нет.
- Булево значение - true если есть тайлы без реального файла с картинкой и false если таковых нет.
Также тут есть методы для:
- Удалений, добавлений тайлов (по имени, влияющая на все три массива ссылок на тайлы).
- Удалений, добавлений террейнов (по имени).
- Удалений, добавлений слоев (по номеру слоя, с удалением принадлежащий слою террейнов).
- Удалений, добавлений Allow и Deny, влияющее и на соответствующие объекты террейнов.
- Метод для синхронизации временного распакованного варианта тайлсета со сжатым реальным тайлсетом.
- Метод для обратной синхронизации содержимого временного каталога со сжатым реальным тайлсетом.
- Метод, собственно выполняющий чтение информации о тайлсете.
- Метод, ищущий неправильные тайлы, добавляющий их в соотв. списки, помечающий и выставляющий соотв. булево значение о их наличии в данном объекте.
- Метод, генерящий на основе информации о слое и переходах террейна конфигурации всех возможных для террейна тайлов, проверяющий наличие каждого такого тайла, если его нету - добавляющий "несуществующий" (т.е. для него отсутствует файл с картинкой) тайл в систему и все нужные списки, помечающий правильным образом этот тайл и ставящий в нужную метку соотв. булево значение этого объекта о наличии "несуществующих" тайлов.
- Метод, исправляющий тайлы с неправильной структурой (поворачивает и дает правильное имя).
- Метод, осуществляющий генерацию конкретного переходного тайла (в аргументе линка на объект тайла).
- Метод, осуществляющий генерацию всех переходных тайлов, для которых еще нету картинок.
Удаление тайлов, разрешений\запретов переходов, если оно связано с реальным файлом набора - автоматически приводит к удалению соответствующих файлов из набора. Юзверь этой системы классов должен сам обеспечить предупреждение пользователя проги о возможном безвозвратном удалении файлов до вызова таких методов. Кроме этого - следует организовать выделение и освобождение памяти так, чтобы памятью всех слоев, террейнов и тайлов заведовал именно класс TTileSet. Другие классы также могут иметь подобные ссылки, и даже выделять память под новые объекты - но в любом случае такой объект должен попасть в массив объекта TTileSet и в дальнейшем уничтожаться должен под управлением именно TTileSet, если же вложенные в него объекты сами новых объектов не создают, то использоваться может только копирование ссылки на уже созданный объект. Таким образом, в других классах может использоваться new, но delete всегда выполняется только классом TTileSet, причем гарантированно для всего, что могло быть создано по любому такому new.
Класс TLayer весьма прост и содержит только номер слоя (в виде числового значения), массив ссылок на террейны и обратную ссылку на тайлсет, которому принадлежит слой.
Класс TTileSetIni по сути также более чем прост, типичен для любого класса, работающего с конфигами в ini, содержит в себе структуры и переменные с данными секций TileSet.ini, а также методы Load, Save, Clean для загрузки, чтения информации в\из файла и очистки (простановке значений по умолчанию) соответственно. Метод Load должен проверять соответствие номера версии формата в файле текущей реализации - если в файле версия новее - давать ошибку, если старее - конвертировать файл в новую версию (без записи на диск), если тот же - просто читать.
На уровне террейна работа обеспечивается объектами класса TTerrain, который содержит:
- Имя террейна.
- Число - приоритет террейна. Если приоритет неизвестен то значение = 0.
- Ссылку на объект класса TLayer, которому принадлежит террейн. Если имеются проблемы с идентификацией единственного номера слоя - присваивается ссылка на слой с индексом 00 (ноль).
- Массив линков со всеми найденными для этого террейна значениями слоя.
- Массивы ссылок Allow и Deny на объекты террейнов, на которые, соответственно, разрешен или запрещен переход.
- Массив ссылок на все тайлы террейна.
- Обратная ссылка на тайлсет, которому принадлежит террейн.
Также тут есть методы для:
- Удаления\добавления тайлов. Работают по имени или индексу. Убеждаются что такой тайл имеется в террейне, затем удаляется на уровне тайлсета.
- Удаления\добавления элементов Allow и Deny - с синхронизацией с вовлеченными террейнами и списками реальных имен файлов в объекте тайлсета.
- Смена слоя. Работает независимо от того, есть ли проблемы со слоями, меняет слой, переименовывает файлы тех тайлов, от которых есть какой-то смысл, остальные тайлы (т.е. те переходы, которые больше не нужны) либо удаляются, либо переименовываются как будто бы в них был смысл (в зависимости от аргументов метода) - второй вариант может использоваться при смене слоя сразу для нескольких террейнов без потери перехода между ними.
На уровне тайла вся работа обеспечивается классом TTile, который содержит:
- Ссылку на свой тайлсет.
- Ссылку на свой слой.
- Путь к bmp-файлу, с именем файла (строка).
- Путь к файлу в тайлсете, с именем файла (может не совпадать с bmp если оно например 7z).
- Ссылка на объект типа TTileTerrs - содержит информацию о структуре тайла (территории) в удобном для быстрой программной обработки виде.
- Номер варианта тайла с такой структурой.
- Булевое значение Exists - true если тайл реально существует в тайлсете а не только должен наличествовать в теории исходя из структуры террейнов на слоях и возможных переходов.
- Булевое значение IsValid - true если структура тайла соответствует одной из стандартных, если соответствия нет - тайл является одним из вариантов поворота для одной из стандартных конфигураций, а значит такой тайл требуется перевернуть в стандартную конфигурацию при нулевом повороте и переименовать.
Также тут есть методы для:
- Загрузки информации о тайле по его имени (смотрит свойства тайлсета и ищет нужные файлы).
- Метод, возвращающий строку с именем тайла.
- Метод, возвращающий повернутую нужное количество раз bmp-ху тайла.
- Метод, возвращающий bmp-ху тайла без поворотов (вызывает предыдущий метод с нулевым поворотом).
Класс TTileTerrs был бы простой структурой если бы не имел методы сравнения двух объектов этого типа, поэтому он класс. TTileTerrs Содержит:
- Аналогично ссылки на террейны (вместо имен) в соотв. углах (справа налево, по строкам сверху вниз, A соответствует {Terr1} а D соответствует {Terr4} в имени тайла). Вот табличка расположения углов:
A | B |
C | D |
- Ссылка на родной тайл.
Также содержит методы:
- Для чтения террейнов в углах (т.е. ABCD) из имени-строки тайла (и записи в инфу объекта).
- Для генерации имени тайла по ABCD без той части где указывается вариант тайла.
- Метод, возвращающий integer - если аргумент не совпадает по структуре с тайлом (аргумент также типа TTileTerrs) - вернет -1, иначе - вернет количество поворотов которое нужно применить к аргументу чтобы структура тайла совпадала со структурой аргумента. Таким образом если возвращает 0 значит структура тайла и аргумента метода идентичны.
- Перегрузка оператора == - при сравнении двух объектов этого типа этим оператором - соотв. возвращает true если объекты идентичны и false если чем-либо различаются.
Как определяется правильна ли структура тайла
Для начала определимся с приоритетом террейнов. Приоритет определяется по имени террейна, в алфавитном порядке (не только для первой, для всех букв имени террейна). Чем выше в списке по алфавиту идет имя террейна тем выше его приоритет. В дальнейшем буду считать что террейн с наивысшим приоритетом имеет номер 1, террейн с приоритетом ниже номер 2, ниже номер 3 и так далее. На основе этого допущения определим стандартные структуры террейнов:
- Из двух террейнов-1:
2 | 2 |
1 | 2 |
- Из двух террейнов-2:
1 | 1 |
2 | 1 |
- Из двух террейнов-3:
1 | 1 |
2 | 2 |
- Из трех террейнов-1:
1 | 1 |
2 | 3 |
- Из трех террейнов-2:
1 | 1 |
3 | 2 |
- Из трех террейнов-3:
1 | 2 |
3 | 1 |
- Из трех террейнов-4:
2 | 2 |
1 | 3 |
- Из трех террейнов-5:
2 | 2 |
3 | 1 |
- Из трех террейнов-6:
3 | 3 |
1 | 2 |
- Из трех террейнов-7:
3 | 3 |
2 | 1 |
- Из четырех террейнов-1:
1 | 2 |
3 | 4 |
- Из четырех террейнов-2:
1 | 2 |
4 | 3 |
- Из четырех террейнов-3:
1 | 3 |
2 | 4 |
- Из четырех террейнов-4:
1 | 3 |
4 | 2 |
- Из четырех террейнов-5:
1 | 4 |
2 | 3 |
- Из четырех террейнов-6:
1 | 4 |
3 | 2 |
Как это все по идее должно работать
В начале программа создает верхний уровень - объект класса TTileSetsCollection. Все остальные уровни создаются конструкторами и прочими методами этого объекта и его вложенными объектами. Освобождение памяти во избежание утечек - там же, деструкторами классов и их методами. Программа должна позаботиться только о правильном удалении объекта TTileSetsCollection когда он станет не нужен.
После создания объекта класса TTileSetsCollection - программа пробивает в его опции все необходимые пути, если нужно - CallBack-метод, а затем вызывает нужный метод чтения информации тайлсетов.
Данный метод - читает имена тайлсетов, их количество - и создает нужное количество подобъектов класса TTileSet. Обращаю внимание, что все вложенные в TTileSetsCollection объекты имеют обратные ссылки на "родительский" объект, т.е. тот объект в который они вложены - что позволяет этим объектам использовать информацию своего владельца и даже своих соседей и вложенных в соседей объектов.
Для каждого объекта TTileSet вызывается метод чтения информации тайлсета. Причем перед запуском чтения тайлсета при необходимости вызывается CallBack что позволяет индицировать процесс загрузки, например, в окошко с прогрессбаром.
Метод чтения информации тайлсетов - для начала читает свой конфиг, если используется svn - читает информацию о ревизии. Если тайлсет сжат - первым делом выполняет синхронизацию с временной, несжатой версией тайлсета. Затем - производит поиск всех файлов с тайлами, подсчитывает их количество, после чего (либо одновременно - смотря как будет реализовано и как оптимальнее работает) выделяет нужное количество объектов в массив с тайлами и загружает в эти объекты информацию по всем найденным тайлам. Обновременно со вбивкой информации в основной массив - создаются объекты террейнов, слоев, в них вбивается соответствующая информация, в т.ч. присваиваются ссылки на объекты тайлов. По завершению всего этого процесса - все объекты террейнов и слоев уже созданы, в них есть массивы со ссылками на тайлы. После этого становится возможным составление списка террейнов по алфавиту и установка приоритета для них.
После того как приоритеты расставлены - становится возможным понять - какой тайл имеет правильную конфигурацию, а какой, увы, нет. Сканируется список тайлов, обнаруженные неправильные тайлы добавляются в соответствующие списки и помечаются. Автоматического исправления таких тайлов не происходит, это делает отдельный метод, вызываемый программой, например по команде пользователя.
Затем осуществляется генерация всех возможных тайлов-переходов - если такой переходный тайл уже наличествует, хотя-бы в количестве 1 (адЫн) штука - ничего не происходит, если же ни одного такого тайла найдено не было - создается объект тайла, с пометкой что файла картинки у него нет, ссылка на него добавляется во все соответствующие массивы соответствующих объектов.
Собсно это все что касается механизма чтения информации о тайлсете. После его завершения - в объекте класса TTileSetsCollection содержится вся необходимая информация о всех имеющихся тайлсетов, более чем достаточная для вывода любой информации в интерфейс программы, в т.ч. дерева террейнов и тайлов.
Кроме этого у всех объектов системы имеются дополнительные методы, которые не вызываются при чтении тайлсетов, и предназначены для непосредственного их вызова программой - например методы удаления\добавления тайлов, генерации переходных тайлов, исправления "неправильных" тайлов и т.д. Подробно расписывать эти методы смысла нет, ибо механизм их действия понятен из простого их описания, а описания же для них наличествуют выше в данном тексте, в списках методов классов.
Спасибо за то что прочитали весь этот бред :).