Редактор пиксельной графики и карт локаций на PascalABC.NET.
Два режима над одним окном:
- тайл — рисование пиксельной картинки;
- карта — локация игры Andors-Love
в её формате
data/maps/*.map.
Оба режима пользуются общими панорамой, масштабом, историей отмен, выделением
и обзорной панелью. Документы живут одновременно, переключение между режимами
(клавиша Tab или меню) ничего не теряет.
- Windows с .NET Framework 4.x
- PascalABC.NET для сборки из исходников
Откройте TJTM.pas в среде PascalABC.NET и нажмите F9. Модули рядом
компилируются автоматически.
pabcnetcclear.exe TJTM.pas
Рядом с исполняемым файлом должны находиться:
| Путь | Назначение |
|---|---|
Config.txt |
настройки; создаётся автоматически, если файла нет |
Resources/ |
палитра по умолчанию |
Картинки/ |
стартовое изображение debug.png |
Отсутствие любого из них не мешает запуску: палитра заменяется радужной заготовкой, а вместо стартового изображения создаётся пустой тайл размера из настроек.
| Файл | Содержимое |
|---|---|
TJTM.pas |
состояние документов, отрисовка, ввод, экраны интерфейса |
TJTMColor.pas |
преобразования HSV и RGB, смешивание с прозрачностью, растровые буферы |
TJTMConfig.pas |
чтение и запись Config.txt |
TJTMMap.pas |
разбор, запись и проверка карт локаций |
Обработчики мыши выполняются в потоке интерфейса, параллельно основному циклу, поэтому клик приходит в произвольный момент — в том числе посреди отрисовки кадра. Раньше каждый экран в конце кадра просто сбрасывал признак нажатия, и клик, пришедший после того, как его кнопка уже нарисована, не доставался никому: чем дольше перерисовка, тем чаще кнопка «не срабатывала».
Теперь кадр начинается со снимка (BeginFrameInput): накопленное нажатие
снимается под замком целиком — и признак, и координаты, — и весь кадр
работает с этим снимком, а живой признак снова копит то, что приходит.
Клик, пришедший посреди кадра, достаётся следующему. Кнопка, сработав,
забирает снимок себе, поэтому одно нажатие приводит в действие ровно одну
кнопку.
Замерено на экране справки: до правки из 16 долетевших нажатий кнопки ловили 14, при частых нажатиях — 23 из 25; после правки — 17 из 17, 25 из 25 и 21 из 21 на итоговой сборке.
Удерживаемые клавиши лежат в наборе (KeyHeld, карта по коду), а не в
отдельных переменных на Shift, Ctrl и Alt: спросить IsKeyPressed можно про
любую клавишу, и добавление новой не требует ни новой переменной, ни правки
двух обработчиков. Именно карта по коду, а не список нажатых: список пришлось
бы искать перебором и дописывать при каждом повторе автоповтора — система шлёт
нажатие снова и снова, пока клавишу держат, — заново выделяя память. Уход
фокуса гасит весь набор: отпускание после переключения окна придёт уже не нам,
и снять клавишу было бы нечем.
Правило кнопок мыши одинаково во всей программе:
- левая применяет: рисует, заливает, кладёт цвет, ставит объект;
- правая берёт: пипетка цвета или местности, убирает объект;
- ролик как кнопка: протяжка двигает изображение, щелчок без протяжки меняет местами рабочие цвета;
- вращение ролика меняет масштаб вокруг курсора.
Внизу панели справа документ показан целиком, жёлтая рамка отмечает видимую на холсте часть. Протяжка средней кнопкой по обзору двигает холст, а панорама холста двигает рамку: обзор и редактор всегда смотрят на одно место документа, только в разных масштабах.
Карандаш, ластик, заливка, пипетка, линия, прямоугольник, овал, дизеринг, выделение.
Фигуры строятся протяжкой с предпросмотром. Ctrl во время протяжки делает
фигуру заполненной, Shift приводит её к квадрату или кругу.
Выделение: рамка бежит «муравьями» — пунктиром со сдвигом фазы, — поэтому её видно и на пёстром тайле. Протяните рамку, чтобы скопировать область в буфер; щелчок без протяжки вставляет буфер; протяжка за выделенную область копирует её на новое место.
Местность, заливка, прямоугольник, объект, выделение.
Строка объекта правится по средней кнопке настоящим полем ввода: работают
выделение, Ctrl+C и Ctrl+V. Строки вроде
exit 46 8 forest 2 8 key=rusty quest=ink:2 длинные, и их переносят между
локациями целиком, а не набирают заново.
Инструмент объектов: левая кнопка ставит объект выбранного вида или перетаскивает уже стоящий, правая убирает, средняя открывает правку строки.
| Клавиша | Действие |
|---|---|
Escape |
главное меню |
Tab |
переключить режим тайла и карты |
N / L / S |
создать, загрузить, сохранить |
A |
сохранить выделение или буфер отдельным файлом |
Z / Y |
отменить и повторить |
V |
вставить буфер в позицию курсора |
X |
поменять местами первый и второй цвета |
P |
точный выбор цвета по каналам ARGB |
G / C |
сетка, шахматка под прозрачными пикселями |
B |
бесшовный предпросмотр |
W |
заворот рисования через край тайла |
F / D |
вертикальная и горизонтальная оси симметрии |
E |
режим замены вместо смешивания |
I |
индексная палитра |
T |
набор тайлов Andors-Love |
R / Delete |
случайные цвета, очистить тайл |
1…9 |
выбор инструмента |
+ − |
масштаб |
| стрелки | сдвиг изображения |
Пробел |
вернуть изображение в центр |
Alt |
над холстом временно включает пипетку |
H |
справка и благодарности |
Пункт меню Наборы тайлов (или клавиша T) показывает четыре набора:
| Набор | Что в нём | Откуда состав |
|---|---|---|
| Основные | 17 слотов игры: пол, стена, вода, герой… | список в редакторе |
| Жители | по картинке на жителя: npc_elder.png |
файлы в папке |
| Враги | по картинке на врага: mob_wolf.png |
файлы в папке |
| Значки | картинки на кнопках инструментов | список в редакторе |
- правая кнопка открывает тайл в редакторе;
- левая записывает на его место текущий тайл, спросив подтверждение, если файл уже есть.
Папка набора задаётся ключом TilesFolder или прямо на экране; у значков своя
— IconsFolder. Открытый из набора тайл становится текущим файлом, поэтому
S сохраняет его обратно туда же.
Имена основных слотов повторяют src/gfx/tiles.cpp игры — это контракт между
двумя проектами, такой же, как коды клеток в картах: слот, названный иначе,
игра просто не найдёт.
А вот состав жителей и врагов редактор не хранит. Он читает саму папку:
что в ней лежит с приставкой npc_ или mob_, то и показывает. Список
существ живёт в игре, и вторая его копия здесь рано или поздно от неё
отстала бы. Завели нового врага в игре, её скрипт создал ему заготовку — и он
появился в редакторе сам.
Пустое место — не ошибка: игра рисует такой тайл прежним способом, заливкой и знаком шрифта, поэтому набор можно делать по одному тайлу и в любом порядке. Первые семь основных слотов — местность, она рисуется непрозрачной; остальные десять — объекты, у них фон прозрачный.
Кнопки инструментов рисуются картинками из Resources/Icons — по файлу на
инструмент, tool_pen.png и так далее. Они правятся тем же редактором: набор
Значки, правая кнопка открывает, левая записывает. Изменённый значок
появляется на кнопке сразу, как только вы вышли из экрана наборов.
Пока файла нет, кнопка выглядит подписью, как раньше. Убрать все значки можно,
очистив ключ IconsFolder.
Двойной щелчок по клетке карты открывает тайл, которым она нарисована:
- под курсором житель — откроется его собственный тайл,
npc_elder.png; - точка появления врага — его,
mob_rat.png; - другой объект — тайл его вида;
- пусто — тайл самой местности.
Если файла ещё нет, редактор предложит завести его сразу с нужным именем, и Сохранить положит тайл туда, куда смотрит игра.
Второй щелчок пары обычного дела инструмента не делает: иначе двойной щелчок инструментом объектов ставил бы два объекта разом.
Клавиша I связывает пиксели тайла с ячейками палитры того же цвета. После
этого правка ячейки левой кнопкой перекрашивает все связанные с ней пиксели:
набор тайлов перекрашивается сменой палитры, а не перерисовкой.
Включённый режим виден по рамке палитры цветом выделения, а ячейка, из которой
взят первый цвет, обведена ею же. Правка ячейки и перекраска тайла отменяются
вместе, одним Z.
Привязка — подсказка, а не источник истины: цвет пикселя по-прежнему хранится в самом тайле, и перед перекраской сверяется с прежним цветом ячейки. Пиксель, который с тех пор перекрасили иначе, остаётся на месте. Поэтому устаревшая привязка не может испортить картинку — она лишь не сработает.
Пиксель, не совпавший ни с одной ячейкой, остаётся свободным. Чтобы связать весь тайл, сведите его к цветам палитры в операциях с цветом: после этого палитра управляет каждым пикселем. Цвет, набранный ползунками, связи не имеет, пока не совпадёт с какой-нибудь ячейкой; пипетка берёт цвет вместе с его связью.
B включает бесшовный предпросмотр: холст рисуется повторяющимся тайлом,
соседние копии слегка притушены, сам тайл обведён рамкой. Швы видно прямо
во время работы.
W включает заворот: мазок, уходящий за правую границу, продолжается от левой.
Вместе с предпросмотром это позволяет доводить стык, не выходя из редактора.
Предпросмотр работает на любом масштабе. Раньше он отключался, когда на холст
попадало больше сорока тысяч клеток: холст рисовался по клетке, и повтор во
весь экран стоил бы четверти миллиона вызовов на крупном тайле. Теперь тайл
держится ещё и как картинка, и холст закрывается несколькими DrawImage —
столько, сколько копий тайла видно, обычно единицы. Стоимость отрисовки
перестала зависеть и от размера тайла, и от масштаба, а ограничение снято.
Картинка собирается ближайшим соседом: пиксель-арт нельзя сглаживать, иначе при увеличении выйдет мыло вместо пикселей.
Режим карты работает с форматом Andors-Love напрямую, без промежуточных файлов. Разбор проверен на всех 50 картах игры: чтение и обратная запись дают файл байт в байт.
name Дрейфующий луг
size 48 18
grid
################################################
#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,#
... ровно 18 строк ровно по 48 символов ...
objects
sign 24 10 Облака идут в одну сторону, тени от них — в другую.
exit 24 15 node3 24 5
end
Сетка хранит только геометрию:
| Символ | Клетка | Проходима |
|---|---|---|
. |
пол | да |
, |
трава | да |
= |
дорога | да |
: |
стоячая вода | да |
# |
стена | нет |
~ |
вода | нет |
T |
дерево | нет |
Всё остальное описано объектами: npc, item, exit, spawn, chest,
note, bed, sign. Редактор разбирает у объекта только вид и координаты,
а хвост строки хранит как есть и пишет обратно без изменений. Поэтому условия
перехода, содержимое сундуков и тексты табличек не теряются, даже если
редактор не умеет их показывать; править хвост можно вручную средней кнопкой
по объекту.
Пункт «Свойства и проверка локации» повторяет проверки tools/gen_maps.py
из игры: объекты должны стоять на проходимых клетках, а проходимая часть
локации — быть связной.
После правки карт в игре:
python3 tools/gen_maps.py && make embed && makeВсе параметры хранятся в Config.txt в кодировке UTF-8, по строке на параметр
в виде ключ = значение. Порядок строк значения не имеет, неизвестные ключи
игнорируются, а испорченный или отсутствующий файл не мешает запуску. Файл
можно править вручную или через пункт Настройки в меню.
| Ключ | Значение |
|---|---|
ShowHelpOnStart |
показывать справку при запуске |
ShowMenuOnStart |
показывать главное меню при запуске |
DefaultTileWidth, DefaultTileHeight |
размер нового тайла, 4..512 |
ShowGrid |
сетка между пикселями |
ShowChecker |
шахматка под полупрозрачными пикселями |
ShowSeamless |
бесшовный предпросмотр |
StartInMapMode |
запускаться сразу в режиме карты |
DefaultMapWidth, DefaultMapHeight |
размер новой карты, 4..256 |
MapsFolder |
папка карт локаций для диалогов |
TilesFolder |
папка набора тайлов, например data/tiles игры |
IconsFolder |
папка значков кнопок; пусто — кнопки с подписями |
StartupImage |
картинка, открываемая при запуске; пусто — не открывать |
AutoLoadLastFile |
открывать последний файл вместо стартовой картинки |
PaletteFile |
палитра 8×8, загружаемая при запуске |
LastFile, LastMapFile, LastFolder |
заполняются программой |
При запуске программа пробует открыть стартовое изображение, и размер тайла берётся из него. Размер из настроек нужен только тогда, когда открыть картинку не удалось: тогда создаётся пустой тайл заданного размера.
GNU General Public License версии 3, см. файл LICENSE.
Автор — dpsmpx, vk.com/dpsmpx.
Команде PascalABC.NET за язык, среду разработки и библиотеку GraphABC, на которой построен весь интерфейс редактора.
Игре Andors-Love за формат карт, ради которого сделан режим локаций.
Группе vk.com/programmer_ne_lamer за сообщество и поддержку.