Редактор гексагональных карт
Чтобы создать интересное приключение недостаточно просто подготовить набор несвязных локаций, городов и пещер. Гейм мастера создают целые миры, в которых существуют их игроки. Создавать карты событий и локаций мы уже научились, при помощи редактора РПГ карт, но что поможет в создании игрового мира, где располагаются все эти локации и живут драконы?
У нас как раз есть приложение, которое поможет в этом — редактор гексагональных карт. При помощи этой утилиты вы сможете создавать фентезийные миры или миры будущего. Все что угодно — все, до чего доведет вас фантазия. Как и с редактором РПГ карт, мы придерживались следующих принципов:
- Масштабируемость наши карты должны иметь возможность увеличиваться в размерах, без потери качества изображения, никаких размазанных картинок, все должно быть четко.
- Возможность кастомизации пользователь должен обладать возможностью подстроить элементы управления таким образом, чтобы ему было удобно пользоваться редактором.
- Создание иконок важно, чтобы гейм мастер мог сам создать недостающие иконки, при этом процесс не должен быть очень сложным. Разобравшись с редактором РПГ иконок, буквально через 40 минут, любой человек сможет делать иконки, которые можно использовать, где угодно, не только в РПГ картах
- Несколько уровней нам всегда нравились карты в играх HOMM3, там, где есть подземные уровни и в нашей карте тоже хотелось бы сделать такие подземные уровни, но мы решили пойти дальше, за облака.
- Простой и понятный интерфейс программа должна быть проста в освоении и понятна, в тоже время должна быть богата по функционалу. Мы используем единые подходы в наших интерфейсах, чтобы все было стандартизовано.
- Разные уровни мастерства используя наш продукт, любой пользователь сможет нарисовать карту прямо сразу, но изучив возможности гексагонального редактора РПГ карт или другим программ из нашей экосистемы человек сможет создавать карты на совершенно ином уровне.
- Должно быть ощущение высоты мы всегда хотели каким-то образом передать эффект высоты на гексагональных картах, и как нам кажется у нас получилось в некотором смысле.
- Гексагональная карта мы любим гексы, это очень удобная фигура и мы хотим использовать её для построения карт.
Основное окно Панель подпрограмм Центральная панель Правая панель
Обзор интерфейса
Проектируя интерфейс редактора, мы старались сделать все таким образом, чтобы он был максимально прост. Интерфейс можно разделить на основные области: основное окно, правая панель, панель подпрограмм, центральная панель.
Основное окно — это самый базовый объект нашей программы, здесь вы взаимодействуете с картой. В режиме нанесения текстур, иконок или кривых вы при помощи мышки закрашиваете области. В режиме работы с текстом, в этой области будут появляться манипуляторы, которые управляют поведением кривой, вдоль которой будет отображаться текст. Стоит отметить, что у холста есть горизонтальная и вертикальная полосы прокрутки, которые появляются в тот момент, когда карта не умещается на экране (полосы появляются в автоматическом режиме), так что вы можете пользоваться всем привычным функционалом связанного со скроллингом как на мышке, так и на трекпаде ноутбука.
Панель подпрограмм — на данной панели представлены ссылки на приложения по генерации контента для гексагональных карт, такие как:
Текст Дороги Текстуры Переходы Иконки Настройки Правая панель
- Редактор текста создание стилей для текста
- Редактор дорог создание стилей дорог, которые вы сможете применять на гексагональной карте
- Редактор текстур создание своих текстур или загрузка существующих на сервер
- Редактор переходов система для создания шаблонов переходов одной текстуры в другую
- Редактор иконок программа по созданию РПГ иконок для ваших карт
- Настройки приложения ссылка на раздел с настройками редактора гексагональных карт в приложении настроек РПГИнферно
- Кнопка сокрытия правой панели иногда требуется временно скрыть правую панель
Центральная панель — многофункциональная панель, находящаяся над холстом, в ней могут располагаться различные элементы управления, в нашем случае это:
Масштаб Уровень карты Наш Discord
- Слайдер масштабирования при помощи этого функционала можно уменьшить карту, чтобы посмотреть, на результат работы целиком.
- Переключатель уровней с его помощью вы сможете создать подземный уровень или воздушный.
- Ссылка на наш дискорд сервер на тот случай, если вам захочется пообщаться и обсудить наш редактор карт в дискорде.
Правая панель — это сердце редактора, сосредоточение всех функций редактора. Панель поделена на 5 основных сегментов:
Закладки Панель инструментов Кисть Размер кисти Сетка Цвет сетки Помощь Панель переходов Основной контент Дополнительная правая панель
- Закладки — здесь отражено логическое разделение на типы ассетов, которые могут использовать на карте: Поверхности, Иконки, Кривые и Текст. Для каждого из разделов может существовать свой особый функционал.
- Панель инструментов — на этой панели располагаются основные элементы управления, связанные с кисточками или другими параметрами коррекции карты. В зависимости от раздела наполнение данной области может меняться, но есть набор элементов, которые всегда доступны чаще остальных:
-
- Многофункциональная кисть данная кнопка переключает режимы между закраской и ластиком.
- Размер кисти в выпадающем списке можно выбрать один из четырех вариантов кисточки.
- Включение/выключение сетки сетка полезна при редактировании вашей гексагональной РПГ карты, но время от времени требуется взглянуть на карту без сетки, чтобы понять, как она будет выглядеть в финальной форме.
- Цвет сетки специальная утилита для выбора цвета сетки, которая накладывается поверх карты, на тот случай если вы нарисовали Нео-Токио и вам обязательно нужна фиолетовая сетка поверх карты, а может она должна быть еще и полупрозрачной?
- Иконка помощи если у редактора есть какая-то подсказка про не очевидные вещи, то при наведении на данную иконку будет выведена краткая справка, например, по горячим клавишам.
- Другие элементы элементы, которые будут появляться в рамках специфических разделов. Они будут описаны в соответствующих разделах данного мануала.
Рассмотрим подробнее каждый из четырех разделов подробнее.
Редактирование поверхности
Подземный слой Активный слой Наведение на текстуру Использование текстуры Сортировка Перенос текстур Удаление Заливка Панель текстур Текстуры принадлежащие сообществу Текстуры принадлежащие пользователю Тип текстуры: твердь Тип текстуры: твердь Тип текстуры: жидкость Тип текстуры: жидкость Тип текстуры: жидкость Тип текстуры: жидкостьДля перехода в этот режим необходимо нажать на закладку «Поверхность». Текстуры создаются при помощи приложения создания РПГ текстур. Для работы с текстурами разработаны разнообразные решения, которые позволяют добиваться очень интересных и красивых результатов. В некоторых редакторах бывает забавная ситуация из-за позиционирования текстур воды, когда вода «натекает» на поверхность, а не поверхность уходит под воду, нас такие вещи не устраивают.
Выбрав переход, вам требуется выбрать слой, на котором вы будете рисовать, слои находятся в панели основного контента. При активации слоя заголовок выделяется цветом и в правом углу появляется кисточка. Стоит отметить, что в рамках слоя текстуры уникальны, нельзя нарисовать 2 одинаковые травы, в таком случае все одинаковые текстуры будут склеены в одну запись.
Обратите внимание, что на подземных картах есть особый слой — потолок, который имеет самую большую высоту стенки.
В рамках одно слоя текстуры можно пересортировывать, таким образом вы можете настроить наслоение текстур друг на друга. Помните, что вода всегда находится под сушей, так что наш редактор карт не позволит смешивать твердь и воду в произвольных комбинациях.
Вы можете переносить существующие текстуры между слоями, при попадании на новый слой, если такая текстура уже существует данные будут объединены, так что учитывайте этот момент.
При наведении на текстуру в списке слоев, на основном холсте данная текстура будет подсвечена, это нужно для того, чтобы было понятно какие именно области вашей карты закрашены данной текстурой на выбранном слое.
При нажатии на текстуру в списке слоев, вы автоматически будете красить этой текстурой и на том слое, где она присутствует.
При наведении на текстуру в списке слоев, вы увидите дополнительные элементы управления текстурой:
- Заливка при двойном нажатии на данную кнопку, вы зальете указанную текстуру той, что выбрана в палитре текстур.
- Удаление при двойном нажатии вы удалите текстуру с указанного соля.
После выбора слоя, в дополнительной панели появится палитра доступных текстур, которые сгруппированы стандартным образом — сообщество/подписки/ваши. Также у каждой текстуры есть иконка, которая указывает на её принадлежность к одному из типов: вода, суша и воздух.
Редактирование иконок
Выбор слоя для иконки Список иконок Цветовые схемыДля перехода в этот режим необходимо нажать на закладку «Иконки». Иконки создаются в специализированном редакторе РПГ иконок. Стоит отметить, что иконки могут занимать до семи гексов, это создано для того, чтобы была возможность создавать большие площади покрытые урбанистикой или составными объектами, может быть долина с особым прудиком, где плавает магический лебедь?
При активации режима «Иконок» на панели инструментов появляется дополнительный селектор слоев, к которому будет принадлежать иконка. Это требуется для того, чтобы можно было размещать иконки как под кривыми, так и над ними. К примеру, иконки травы должны перекрывать водной гладью реки, а вот изображение кораблей должны находиться над рекой или озером.
Панель основного контента наполняется списком иконок, которые разделены по категориям: иконки сообщества, ваши иконки и иконки, на которые вы подписаны. Каждую из категорий можно скрыть, а некоторые можно вовсе убрать при помощи раздела настроек, также можно настроить какие разделы раскрыты по умолчанию. Больше информации на странице приложения настроек.
При выборе иконки будет открыта дополнительная панель с цветовыми пресетами для конкретной иконки. Пресеты сгруппированы тем же образом что и иконки. Помните, что вы можете добавлять свои собственные цветовые схемы иконок, которые будут видны только вам, в том случае если иконка вам не принадлежит.
Редактирование кривых
Выбор реки, озера или дорогиДля редактирования кривых надо активировать закладку «Кривые». После активации данного режима вы заметите что панель инструментов будет содержать только три кнопки: река, озеро и дорога.
Это приложение работает на базе системы редактирования кривых, но в отличии от редактора РПГ карт, в данном случае кривые являются составными, то есть кривые могут быть состоять из неограниченного количества частей, в этой связи будут доступны дополнительные функции.
После нажатия на любую из кнопок, система переходит в режим добавления соответствующего объекта, после этого достаточно перенести мышку на холст и поставить объект в необходимое место и затем продолжить коррекцию
При коррекциях есть набор общих элементов управления — кнопки прямого выделения, кнопка добавления точек, кнопка удаления кривой, кнопка связи кривых, поле ввода названия.
Прямое выделение Добавление точки Удаление точки Название кривой- Прямое выделение в этом режиме вы можете выбирать точки кривых, а также линии кривых. Для выбора точки — просто нажмите на одну из квадратных точек, а для выбора кривой — наведите на кривую, и вы увидите линию-помощник, на которую можно нажать.
- Добавление точек для добавления точек вы можете просто кликнуть по холсту, в этом случае точка будет добавлена в конец кривой. Или же вы можете навести курсор на конкретный сегмент кривой, в таком случае точка будет добавлена на указанную кривой и будет встроена таким образом, чтобы форма кривой не изменилась.
- Удаление кривых после выбора этого режима, вам необходимо навести на кривую и нажать, после этого сегмент кривой будет удален.
- Ввод названия вы можете указать название дороги, реки или озера для того, чтобы различать их в интерфейсе управления.
Связь кривых- Связь кривых после выбора конкретной точки, которая связывает две части кривой, вы увидите цепочку, когда она активна точки управления, связанные с этой точкой, будут взаимосвязаны, таким образом вы сможете делать плавные переходы одной кривой в другую.
Типы кривой Тип: прямая Тип: кубическая Тип: квадратичная- Смена типа кривой представлено в виде 3х кнопок, по нажатию на которые вы сможете выбрать вдоль какой линии будет отрисована кривая: прямая, квадратичная кривая, кубическая кривая
Ширина кривой- Ширина дороги этот слайдер доступен тогда, когда вы редактируете дорогу.
- Ширина сегмента реки при выборе точки кривой, вы сможете редактировать ширину реки в этом месте.
Список дорог, рек и озер Выбранная кривая Удаление кривой Удаление кривой Удаление кривой Список доступных схем или текстур Схемы принадлежащие сообществу Схемы принадлежащие пользователю Выбранная текстура или схемаВ области основного контента представлен список всех дорог, реки или озер, которые есть на карте. В области основного контента представлен список всех дорог, реки или озер, которые есть на карте.
При двойном нажатии на крестик вы удалите кривую запись (безвозвратно).
Когда активирован режим редактирования конкретной кривой, в дополнительной панели представлен список всех доступных цветовых схем для дорог или список текстур жидкости для рек и озер, которые разделены на группы также отмечена текущая схема или текстура, которая применяется для кривой.
Во время редактирования кривой, на основном холсте карты, вокруг текста есть специальная рамка, при помощи этого элемента управления вы можете переносить кривую по холсту, просто зажмите рамку и тащите её, при отпускании мышки, новая позиция кривой будет сохранена.
Кривая обладает двумя типами манипуляторов:
- Манипулятор точки выглядит в виде квадрата, если у квадрата есть диагональные линии, это означает что точка является либо началом кривой, либо её концом. Для изменения позиции точек, просто зажмите и перетаскивайте манипулятор. Стоит отметить, что если соединить начало и конец кривой, то вы её закольцуете, это актуально для дорог и рек. Если выбрана река, то при активации точки, вы сможете редактировать ширину реки, которая должна быть в этой точке, таким образом можно менять толщину рек в разных частях реки.
Рамка Манипулятор точки Манипулятор точки Манипулятор искривления Манипулятор искривления- Манипулятор искривления если вы выбрали режим квадратичной или кубической кривой, то появятся дополнительные круглые манипуляторы, которые будут связаны лучами с манипуляторами точек, которые являются родительскими по отношению к ним. Передвигая точки искривления, вы будете менять касательные линии кривых Безье и таким образом будет модифицироваться изгиб кривых.
Редактирование текста
Прямое выделение Добавление текста Добавление точки Удаление точкиДля перехода в этот режим необходимо нажать на закладку «Текст». Это приложение работает на базе специальной подсистемы управления кривыми и текстом, в этой связи органы управления в этом разделе свои и не похожи на 3 предыдущих раздела.
Первое что вы заметите при переходе в этот режим, в панели инструментов остается только 2 кнопки: прямое выделение и добавление текста.
- Прямое выделение режим нужен для управления манипуляторами текста. Данный вопрос будет раскрыт ниже.
- Добавление текста при нажатии на данную кнопку, вам следует переместить курсор мыши на холст, где вы увидите предварительный шаблон «text», который будет добавлен на карту, при нажатии на левую кнопку мышки. После добавления вы сразу же перейдете в режим редактирования выбранного текста.
- Добавление точек для добавления точек вы можете просто кликнуть по холсту, в этом случае точка будет добавлена в конец кривой. Или же вы можете навести курсор на конкретный сегмент кривой, в таком случае точка будет добавлена на указанную кривой и будет встроена таким образом, чтобы форма кривой не изменилась.
- Удаление кривых после выбора этого режима, вам необходимо навести на кривую и нажать, после этого сегмент кривой будет удален.
В режиме редактировании текста, автоматически будет включена функция «прямого выделения», но для добавления нового текста вы всегда можете нажать на «Добавить текст». Также вы заметите появление новых элементов управления в панели инструментов:
Связь кривых- Связь кривых после выбора конкретной точки, которая связывает две части кривой, вы увидите цепочку, когда она активна точки управления, связанные с этой точкой, будут взаимосвязаны, таким образом вы сможете делать плавные переходы одной кривой в другую.
Типы кривой Тип: прямая Тип: кубическая Тип: квадратичная- Смена типа кривой представлено в виде 3х кнопок, по нажатию на которые вы сможете выбрать вдоль какой линии будет отрисована кривая: прямая, квадратичная кривая, кубическая кривая
Центровка текста По левому краю По центру По правому краю Декарирование текста Жирный текст Курсив Подчеркнутый текст Зачеркнутый текст Размер текста Выбор шрифта Поле ввода текста- Центровка текста при помощи трех кнопок вы можете выбрать как будет произведена отцентровка текста: слева , по центру , справа. Помните — если вы перевернете кривую, текст тоже перевернется.
- Декарирование текста доступно 4 модификатора текста: жирный, курсив, подчеркнутый,
зачеркнутыйВсе 4 модификатора можно применить одновременно. - Размер шрифта выбор размера из списка размеров шрифта. Если необходимо добавить новый размер, напишите нам в дискорд и мы рассмотрим ваше предложение.
- Стиль шрифта список шрифтов, которые вы можете применить к тексту, добавление дополнительных стилей происходит по запросу пользователей посредством дискорда.
- Поле редактирование текста вводя текст в данное поле, вы сразу же меняете текст как на холсте, так и в базе данных, нажатие никаких дополнительных кнопок «сохранить» не потребуется.
Это все основные моменты, которые нужно знать для управления картой, далее мы рассмотрим специфику работы нашей системы слоев и подробно рассмотрим список доступных объектов, которые вы можете применять при создании РПГ карты.
Система слоев
В отличии от редактора РПГ карты, в текущем приложении слоев не так много — всего 12. Но это не означает, что редактор перестал обладать феноменальной гибкостью. Мы особо гордимся тем, как реализован метод отрисовки высоты — когда вы переносите текстуру с одного слоя на другой, то вы тем самым даете понять, что часть текстур должна располагаться выше остальных. К примеру, вы нарисовали песчаный пляж на слое под номером ноль, но не так далеко от этого пляжа есть существенное возвышение с обрывом, надо ли говорить, что мутные личности любят скидывать неугодных в воду с этого обрыва? Так вот, вам достаточно просто нарисовать текстуру на слое с номером один и у вас будет элегантный обрыв, для темных дел. Стоит отметить, что слой ноль является самым нижним, это что-то вроде «уровня моря», но не забывайте про специальный слой для подземных карт, «потолок» — это самый высокий слой для подземных миров.
Система обрисовки иконок обладает всего двумя слоями, которые идут над текстурами и между этими слоями проходит слой с кривыми, где вы рисуете кривые: дороги, реки и озера. Ну и последний слой, поверх всех остальных — слой с текстом.
Как и в редакторе РПГ подземелий, далее вы сможете визуально ознакомиться с устройством слоев.


- Слой 0: уровень моря
- Слой 1: текстуры уровень — 1
- Слой 2: текстуры уровень — 2
- Слой 3: текстуры уровень — 3
- Слой 4: текстуры уровень — 4
- Слой 5: текстуры уровень — 5
- Слой 6: подсветка возвышенностей
- Слой 7: иконки под дорогами
- Слой 8: дороги, реки и озера
- Слой 9: иконки над дорогами
- Слой 10: текст
- Слой 11: гексагональная сетка
Выше облаков и глубже ада
Когда мы только задумывали этот редактор, уже тогда хотелось реализовать воздушные острова. Что может быть круче? Вы создали свой мир, где одни фракции воюют с другими, игроки участвуют в различных интригах и ничего не подозревают, а потом партия забредает в старый маяк и прямо как в игре Биошок Инфинити: дирижабль, катапульта, портал или воздушный шар отправляет партию в небо и их взору предстает мир выше облаков. Кто там будет жить, какие секреты хранят эти острова или континенты в небе? На все эти вопросы вашим игрокам еще предстоит ответить!
Другой сценарий, партию отправили изучить озеро, где пропал какой-то важный персонаж, вот только был и уже нет. Партия отважно отправляется изучать дно озера, конечно, предварительно надо выпить соответствующие зелья или экипироваться снаряжением для глубинных работ. И неожиданно, сначала одного приключенца, а следом и всю партию захватывает подводным течением и уносит в какие-то глубинные каналы и через несколько минут паники выбрасывает на берегу подземного озера, где партию ожидает сбитый с толку персонаж, которого им было поручено найти. Таким образом приключение переносится в подземные глубины, прямо как в Героях Меча и Магии 3. Контрабандисты, а может древняя империя каких-то некромантов поджидает в глубинах или же вы найдете туннели давно вымерших гномов, которые по обыкновению копнули «глубже чем надо»?







Реки, озера и дороги


Гексы это, конечно, хорошо, но, когда вы хотите нарисовать тонкую речушку, при помощи гексов такие вещи решить сложно. В некоторых редакторах дороги и реки представлены в виде особых тайлов, которые могут размещаться на карте. В нашем понимании это не самый удобный вариант.
Для решения проблемы рек, дорог и озер — мы создали особый подход, при котором реки аккуратно впадают в моря и проистекают из озер, до тех порка вы используете идентичные текстуры для рек и озер с морями. При добавлении рек вы заметите как в земле автоматически «продавливаются» каналы, а на береговых линиях реки плавна впадают в моря. Озера в свою очередь, живут по тем же правилам что и реки, только имеют замкнутый контур.
Отдельно хотелось бы остановится на дорогах. Не забывайте, что стили дорог, вы можете создавать при помощи редактора дорог, так вот — особым вызовом являются мосты. Нам показалось, что мосты в виде спрайтов не самое удобное решение, мы ведь хотим делать карты быстро. Зачастую мост может иметь причудливый изгиб и никакими спрайтами вы не сможете учесть все варианты. «О, может использовать редактор РПГ иконок? Просто нарисовать нужный мост?» — это тоже не самый разумный вариант с точки зрения времени, каждый раз придется подгонять иконку под особенности текущей карты, делать копии иконки и так далее. Учитывая все это мы поступили просто — пускай система сама рисует мост ровно под дорогой и только над водой и реками. Не спрашивайте, как — «Оно просто работает» как говаривал один классик игровой индустрии. К слову, редактор стилей для дорог тоже работает весьма недурно и позволяет создавать цветовые схемы, которые вам требуются.
Заключение
Описание редактора гексагональных карт подходит к концу, мы надеемся, что у вы сможете реализовать свои задумки при помощи нашей программы. Не забывайте, что для генерации недостающих текстур можно использовать специальный редактор, если вас не устраивают цветовые схемы текста — создайте. Переходы текстур можно создавать в специальном редакторе переходов, ну и конечно — не забывайте делиться с нами своими идеями в дискорде нашего проекта!
Разработка мобильных приложений и компьютерных игр

Для украшения полученного ландшафта используем массив текстур. Для каждой отдельной ячейки можем задавать свою текстуру, но так как у нас все в одной меше, то необходим свой шейдер, позволяющий это сделать. На переходах между шестиугольниками нужно смешивать цвета вершин, чтобы сделать соединение визуально более приятным. Для создания новых игровых механик, связанных с водой, были реализованы также реки и моря. Для удобства в последующем создавать новые ландшафты также реализуем генерацию карт. Об подробностях можно будет узнать в следующих статьях.

На сетке с шестиугольниками можно легко использовать алгоритмы поиска путей. В проекте будем использовать популярный алгоритм А*, который я освещал в предыдущих статьях для квадратной сетки. Благодаря тому, что мы находимся на шестиугольниках, то расстояние до соседей одинаково, а их количество уменьшено на 2, по сравнению с квадратами, это позволяет делать плавную анимацию переходов юнитов из одной ячейки в другую.

Так как, разрабатываемая игра имеет жанр 4х стратегии, то для каждого игрока необходимы территории их владений и соответствующие постройки. Для проекта сделаем объекты, которые будут распространять влияние игрока на соседние ячейки. Эту роль будут играть города, а сами территории, которые находятся под влиянием будем ограничивать кривыми. Так у нас есть список всех ячеек и для созданиях границ нам достаточно создать ломанную по вершинам шестиугольников в заданном порядке. Порядок можно определить проходом по всем имеющимся на территории ячейкам и проверкой их соседей по некоторым правилам.

Таким образом, решение использовать сетку из шестиугольников пошло на пользу разрабатываемому проекту. Такое представление позволяет добиться приятной картинки, интересных игровых механик и реализации большого количества разных алгоритмов. Так был создан фундамент для последующей моей научно-исследовательской работы, а именно реализации ИИ для ботов в данном проекте.
Гексагональные тайловые миры
Тайловость в играх – очень распространенное явление, особенно в играх инди сегмента. Чаще всего используют квадратные тайлы – в них проще всего задать необходимые данные, будь то карта уровня или инвентарь. Однако на квадратных и прямоугольных формах возможности подобной системы не ограничиваются.
В топовых играх конца девяностых — начала нулевых часто можно увидеть шестиугольную сетку заместо квадратной – в то время это смотрелось очень интересно, в особенности в сочетании с изометрией. Именно о работе с такими сетками и пойдет речь.

Если вы не читали мою предыдущую статью про прямоугольные сетки, то рекомендую ознакомится, потому как я иногда буду опускать некоторые вещи, ссылаясь именно на нее. Дабы не изобретать велосипед, некоторые алгоритмы я взял с этой статьи на английском. Кто не хочет или не может в английский, есть ее перевод на хабре.
Все демки и примеры сделаны на движке Godot Engine v 3.2.3 с использованием его встроенного языка.
Его синтаксис прост, а про некоторые специфичные функции можете почитать тут:
VisualServer.canvas_item_add_rect — рисование залитого цветом прямоугольника.
Система координат
На протяжении всей статьи мы будем работать только с правильными шестиугольниками (не считая искажений и поворотов)
Если не брать всякие повороты и искажения, существует два вида шестиугольных сеток, вертикально и горизонтально ориентированных. Непосредственно в движке одну из другой можно получить просто поворотом холста на 90°, но в математике я предпочту их разделить на разные, т.к. поворот зависит от места его применения в матричных выражениях и проще рассмотреть по отдельности. Итак, о чем это я:
Такие я буду называть вертикальными (у ячейки есть явный вертикальный сосед):
А такие горизонтальными (у ячейки есть явный горизонтальный сосед):
Уже на этапе введения координат могут возникнуть проблемы. Дело в том, что в экранные оси никак не получится интерпретировать в свете шестиугольников — всегда будет ось, вдоль которой у ячейки не будет явного соседа. На самом деле существует огромное количество систем координат для таких сеток. Более подробно о них рассказано в упомянутой вначале статье. Я рассказывать про каждую не буду, все таки пост больше про использование сеток, а не их исследование. Упомяну только основные.
Пожалуй первое, что приходит в голову, это таки впихнуть экранные оси в сетку, однако для этого по одному из направлений координаты придется смещать:

Такая система называется координатами смещения. Она родились из смещения рядов обычной, прямоугольной сетки на половину длины. Например, все нечетные столбцы сдвигаются на половинку вниз (картинка слева) или все нечетные строки на половинку вправо (картинка справа). Тогда получается так, что каждый прямоугольник имеет шесть соседей, и в таком случае в эту сетку можно вписать шестиугольники, что уже сделано на картинках выше
(на самом деле там просто шестиугольная сетка).Главная проблема этих координат состоит в выборе базисов. Если вы помните из статьи про прямоугольные тайлы, там мы обращали матрицу из базисных векторов и умножали на нее радиус-вектор пикселя, получая координаты ячейки.
Здесь так не выйдет, потому что в таком представлении вектор перехода к соседней ячейке по какой-то из осей будет для разных ячеек разным, поэтому никак не получиться ввести один базис для всех ячеек, и, как следствие, применение векторной математики невозможно, а колоссальная часть функционала усложняется.
Так что давайте введем такой базис, при котором можно использовать вектора. Вообще у сетки шестиугольников есть три ярко выраженных оси:

Что-то напоминает, не находите? Тут три оси, прям как в пространстве. На самом деле гений тот человек — кто не просто увидел, что если взглянуть на куб под определенным углом, то получится шестиугольник, а еще и додумался использовать трехмерные координаты в двумерной сетке шестиугольников.
Правда вот попробовав посчитать координаты на двумерной сетке, могут вскипеть мозги, ведь третья ось тут кажется лишней и ее использование будто только мешает. Для разрешения данной ситуации просто посмотрим откуда взялись кубы:
Для получения сетки шестиугольников надо взглянуть в изометрии на пирамидку, на каждом уменьшении высоты которой увеличивается количество кубиков в ряду на один. Включив фантазию, можно увидеть в каждом кубе шестиугольник. Включив внимательность, можно увидеть в левом нижнем углу координаты. Включив логику, можно увидеть, что сумма этих самых координат всегда равна некоторому числу, которое, кстати говоря, может быть любым, ведь мы сами решаем, откуда начнется система координат. В моем случае это 15, просто потому, что система отсчета в этом редакторе начинается с нижнего дальнего угла (в случае изометрического вида). Нам незачем таскать за собой лишнюю константу, поэтому возьмем ее за ноль.
Таким образом, сумма трех координат всегда и везде равна нулю, поэтому третья нам попросту не нужна, ведь мы можем получить ее из двух других. Теперь и получается, что «мешающая» третья ось уходит. Наконец то мы можем спокойно направить базисы и у каждой ячейки всегда будет точный сосед, находящийся вдоль оси. Я выбрал такие оси:
Думаю здесь очевидно, почему начало координат удобно поместить в центр шестиугольника.
Преобразование координат
Пожалуй это то, на чем запарываются многие при попытке сделать нечто шестиугольное. Я слышал множество способов, порой они были очень забавные. Например, мне больше всего запомнился способ, где предлагалось сделать невидимую маску с такой же сеткой, где каждая ячейка окрашена в свой цвет, а каждому цвету соответствуют координаты. При смещении курсора смотрим в маску и по цвету определяем координаты. Сложно, с костылями, но работать может. А если мы захотим другой размер? Перерисовывать? Вы тут явно не за этим.
Вообще работать с шестиугольниками не очень удобно, ведь их надо как-то обрабатывать на прямоугольном экране. Хорошо было бы их привести к более прямоугольному представлению. Для этого найдем диагонали шестиугольника (а — сторона):

Оранжевые (маленькие) диагонали делятся пополам зелеными (большими), а зеленые оранжевыми, если последние провести из середины стороны. Уже получается, что мы можем разделить шестиугольник на 4 прямоугольника. Однако некоторые вершины в таком случае будут лежать где то между углами сетки, а ведь нам хотелось бы, чтобы все они попадали ровно в углы.
На самом деле «где то», это ровно по серединке, ведь бОльшие диагонали состоят из четырех половинок сторон (двух сторон). Поэтому проведем еще по оранжевой диагонали из углов, тогда все вершины прямоугольника отлично впишутся в прямоугольную сетку:

Желтую сетку в дальнейшем я буду называть вспомогательной. Для задания ее базисов будем использовать такие значения:
Теперь мы можем выразить базисы шестиугольной сетки, используя базисы вспомогательной сетки:

Запишем все базисы в коде:
Я пользуюсь именно встроенными средствами Godot для упрощения работы в целом. Все подобные места будут поясняться в общем виде, как бы это делалось без встроенных возможностей.
Шестиугольник в пиксель
Вот за что я люблю математику, так это за то, что если она работает, то работает везде. Так что для получения центра шестиугольника в пикселях из его координат на сетке надо просто умножить базисы на координаты:
Для получения каждой вершины просто прибавляем по нужным базисам:

Тогда для получения вершины в коде прибавляем нужный вектор (см. картинку выше) к центру шестиугольника. Я написал только функцию получения массива вершин, ибо по отдельности они почти никогда не нужны.
Для вертикальных шестиугольников:
Для горизонтальных шестиугольников:
Пиксель в шестиугольник
Наверно самая интригующая часть поста. На самом деле ничего нового почти не будет, ведь для получения вещественных координат ячейки все также обращаем матрицу и умножаем на нее радиус-вектор пикселя:
Для горизонтальной ориентации

cw — длина гор. базиса вспомогательной сетки ch — длина верт. базиса вспомогательной сетки
В коде это записывается так:

cw — длина гор. базиса вспомогательной сетки ch — длина верт. базиса вспомогательной сетки
В коде это записывается так:
Однако я буду пользоваться функцией affine_inverse у Transform2D , для того, что бы при изменении базисных векторов постоянно не менять функции преобразований, позже увидите зачем это надо.
Вы скорее всего работаете в другой среде
(и зря), поэтому вам придется писать обращение матрицы самостоятельно. Кто не знает как это делается, или забыл, может почитать тут, или переписать следующие функции в свой язык:Средствами Godot это можно записать всего в одну строчку:
Тут .xform(Vector2) — это метод для умножения матрицы на переданный в него вектор, аналог vec_mul_basis из моего кода. Такой код работает для обеих ориентаций.
Если вы хотя бы бегло прочитали вышеприведенный код, то наверняка заметили функцию round_hex вместо типичных приведений к int . Дело в том, что полных координат у шестиугольника 3, и они обладают условием x + y + z = 0 , а после округления каждой из них равенство может нарушиться. Поэтому необходимо задать координату с наибольшей ошибкой округления через две другие, тогда условие выполнится.
Да, данный метод полностью слизан отсюда, однако зачем придумывать велосипед, если можно взять готовый? Так же тут используется именно round , а не приведение к int , ведь основание каждой ячейки находится в ее центре, а не в левом верхнем углу, как в случае с прямоугольными сетками:
Работает все замечательно:
Однако я надеюсь вы не думаете, что сетки, это вручную нарисованные текстуры. Я не самоубийца.
Рисование сеток
Все примеры и объяснения я буду приводить на горизонтальной сетке, ведь для вертикальной они аналогичны. Для рисования последних я просто оставлю готовую функцию.
Для рисования сетки необходимо знать ее размеры. Размеры шестиугольной сетки будем задавать в координатах смещения, так просто понятнее. Тогда по горизонтали будет в два раза больше ячеек вспомогательной сетки, чем шестиугольников, ведь по горизонтали у шестиугольника 2 ячейки. Для нахождения вертикальных размеров заметим, что для перехода к нижнему шестиугольнику мы вниз проходим три вертикальных базиса, однако для крайнего шестиугольника соседа снизу нет, поэтому самая нижняя чать остается неучтенной, так что нужно прибавить единицу:
Для вертикальных шестиугольников все аналогично, только формулы для вычисления ширины и высоты меняются местами:
Сетку из шестиугольников можно разбить на две части, на вертикальные линии и на паттерн вершин:

Будем рисовать каждую составляющую по отдельности. Начнем с вертикальных линий. Можно заметить, что в каждом ряду линии рисуются с интервалом в 2 ячейки, а каждый четный по счету ряд начинается со второй, а не с первой ячейки. Также увидим то, что первый ряд начинается со со смещением в одну ячейку относительно верхей границы, а ряды разделяет одна ячейка. С учетом того, что длина штриха в две ячейки, между верхними концами отрезков находятся три ячейки.
Тогда в цикле начинаем с единицы и идем до нижнего края карты с шагом 3. Во втором цикле начинаем со столбца, индекс которого обратен четности ряда, проще говоря 1-i%2 , и идем до правого края карты (на единицу больше, чтобы нарисовать крайние линии) с шагом в две ячейки. В каждой итерации второго цикла просто рисуем отрезок высотой две ячейки:
Этот код будет рисовать только вертикальные линии. Теперь нужно нарисовать паттерн вершин. Есть всего два вида наклонных линий, от нижнего левого угла к верхнему правому (их я буду называть нижними диагоналями), и от верхнего левого к нижнему правому (их я буду называть верхними диагоналями). Можно запомнить по положению левого конца отрезка: у нижних он снизу, у верхних — сверху.
Легко увидеть, что в одной строке они чередуются, а в следующей строке паттерн начинается с другой диагонали. Четным я буду называть паттерн, начинающийся с нижней диагонали, нечетным — с верхней. Получается, что от строки к строке паттерн меняет четность.
Для рисования паттернов пробегаем каждую третью строку, начиная с нулевой, а в каждой строке пробегаемся по столбцам. Тут я считаю нужным показать, как задается каждый угол ячейки с координатами
, где j — столбец (как бы x ), i — строка (как бы y ): 
Каждые две строки паттерна разделяют две ячейки, поэтому чтобы перейти от одной к следующей, необходимо сдвинуться на 3 строки вниз.
Как я уже говорил, четность паттерна меняется от строки к строке, значит на выбор нижней или верхней диагонали влияет индекс строки. А внутри одной строки нижние и верхние диагонали чередуются, значит и индекс столбца влияет на выбор диагонали.
Заметим, что в нулевой строке и нулевом столбце (левый верхний угол) должна находиться нижняя диагональ, а там остаток от деления на два индексов столбца и строки, очевидно равен нулю. В следующем столбце остаток от деления его индекса на два уже равен единице, а у индекса строки он по прежнему ноль. Там мы рисуем верхнюю диагональ (т.к. чередование)
Проведя аналогичные рассуждения для следующей строки можно вывести, что если четности индексов строки и столбца одинаковы, то рисуем нижнюю диагональ, иначе верхнюю. Посмотрите на картинку из начала раздела и со всеми этими чередованиями вы быстро разберетесь.
В коде этот алгоритм выглядит так:
Теперь, наконец, получаем полноценную сетку. Однако просто нарисовав ее на холсте, получатся непонятки с координатами:
Дело все в том, что начало координат находится в центре шестиугольника, а начинаем рисовать мы с левого верхнего угла вспомогательной сетки, также как мы это делали с обычными сетками. Для рисования сетки правильно, т.е. из начала координат, необходимо сдвинуть рисование на одну вспомогательную ячейку влево и на две вверх, ведь именно под таким смещением находится начало координат.
Однако и на этом не все. Если просто объединить весь код выше в одну функцию, то при четных высотах она будет рисовать ненужные хвосты:

Эти хвосты всегда рисуются прямо в углах вспомогательной сетки, так что чтобы не мудрить просто напишем условие нерисования в этих самых углах.
Соединив все вместе, получим такую функцию:
При рисовании вспомогательной сетки кстати тоже используем смещение. На всякий случай оставлю тут и ее рисование:
И, как и обещал, функция для рисования вертикально-ориентированной сетки:
Результат вполне неплох, нигде линии не рисуются дважды (сетка рисовалась немного прозрачной на черном фоне, а яркость линий везде одинакова):
Сетка вертикальных шестиугольников


Однако рендерить такие сетки в реальном времени довольно затратно, тут рисуется множество отдельных отрезков, что сильно замедляет работу. Просто для примера: пустое черно окно у меня имеет fps около 950, а при рисовании слегка прозрачным белым цветом Color8(255, 255, 255, 200) шестиугольной сетки размера 10×10 и размером шестиугольника 32 пикселя, fps примерно 260.
Так что рисовать сетки процедурно резонно только на начальном этапе разработки, потом лучше отрендерить ее заранее и использовать как текстуру. (В годо механизм рисования на нодах немного сложнее, чем просто рисовать каждый кадр, но при тесте я делал это именно так, намеренно)
Рисование шестиугольной сетки шестиугольников
Сетки шестиугольников конечно здорово, но иногда хочется чего то большего. Например больше шестиугольников, поэтому сделаем большой шестиугольник из маленьких. Такая сетка может пригодиться, если мы, например, делаем шестиугольные шахматы.
Рисование этой сетки лишь немного сложнее рисования обычной, но алгоритмического удовольствия она доставит не меньше 🙂 Результат будет примерно таков:

Для начала нам конечно же нужны размеры сеток. Т.к. мы рисуем сетку в виде правильного шестиугольника, ее размер можно задать одним значением. В коде я использую тип Vector2 только для совмещения этой переменной с прямоугольной сеткой, при рисовании будет использоваться только X координата.
Тогда для задания размеров вспомогательной сетки нужно найти диагональ большого шестиугольника. Для этого вспомним, что бОльшая диагональ шестиугольника в два раза больше его стороны. В нашем случае шестиугольник состоит из маленьких таких же. В таком случае центр будет учитываться два раза, поэтому вычтем единицу:
Размеры вспомогательной сетки задаются аналогично тому, что мы делали ранее. Для горизонтальной ориентации ширина будет в два раза больше диагонали, а высота в три раза и еще на единицу больше:
Для вертикальных значения меняются местами:
Шестиугольную сетку можно точно также разбить на две части, на паттерн вершин и вертикальные линии:

Начнем с рисования вершин. Рисовать каждый слой по-отдельности не имеет смысла, ведь фигура симметрична. Мы можем разделить всю вспомогательную сетку на четыре части и, нарисовав одну четверть, отобразить ее зеркально на все остальные.
Сетка кстати всегда будет делиться ровно, и вот почему: по горизонтали понятно, ведь в формуле ширины мы удваиваем диагональ шестиугольной карты. А эта самая диагональ будет всегда нечетна, потому что мы от четного числа отнимаем единицу ( hex_map_size.x*2-1 ). В формуле высоты вспомогательной сетки мы умножаем эту диагональ на 3 , и результат получится тоже нечетным, а после прибавления единицы все выражение становится четным. Таким образом, ширина и высота вспомогательной сетки всегда четны, и как следствие, ее можно всегда разделить на четыре одинаковые части:

Тогда мы можем пробегать в циклах только до половин размеров вспомогательной сетки, а для рисования в других частях будем просто отражать точки рисования.
При такой форме сетки рисование вершин начинается не с самой левой колонки и паттерн всегда четный. При увеличении размера сетки на единицу, первый шестиугольник в самом верхнем ряду сдвигается на одну ячейку вспомогательной сетки, т.к. мы увеличиваем в том числе и размер левой грани, в которой под каждым шестиугольником следующий находится не только ниже, но и левее на половину шестиугольника, т.е. на одну ячейку.
Тогда каждый следующий паттерн начинает рисоваться на ячейку ближе к левому краю, а самый первый ряд имеет смещение на единицу меньшее, чем размер шестиугольной карты, т.к. первый шестиугольник в нем тоже является частью левой грани, так что под ним шестиугольников меньше на эту самую единицу, чем размер карты.
Также вспомним, что каждый следующий паттерн рисуется со смещением в три ячейки от предыдущего, поэтому в цикле идем от нуля до половины высоты вспомогательной сетки с шагом в три, попутно вычисляя смещение для каждого ряда:
Проходить по ширине будем также до середины вспомогательной сетки, начиная с высчитанного ранее смещения:
Каждый паттерн при рисовании шестиугольной карты четный, а вот смещение чередует свою четность. Четность начального смещения напрямую зависит от четности размера карты — они противоположны (следует из того, что ниже первого шестиугольника таковых на один меньше размера карты).
Двигаясь вниз по рядам паттерна, индекс ряда меняет четность, как и смещение для этого ряда. Если помните, для выбора диагонали при рисовании прямоугольной сетки мы сравнивали четность ряда и колонки. Тут же меняются обе четности, и при разных размерах карты они будут то совпадать при начальных значениях, то нет.
Приведу пример. Мы рисуем нижнюю диагональ, если индексы ряда и колонки совпадают, иначе верхнюю. Поставим размер карты 5. Тогда начальное смещение будет четным, как и индекс первого ряда ( i=0 ). Исходя из условия, рисуем нижнюю диагональ, как и должно быть. Однако поставив четный размер, скажем, 4, начальное смещение будет нечетным, а вот индекс первого ряда по прежнему четным. Тогда, взглянув на условие, компьютер выберет верхнюю диагональ, а ведь нам все еще для начала нужна нижняя. Вот как это будет выглядеть:

Тут на самом деле всего лишь надо поменять четность паттерна, тогда все встанет на свои места. Получается, выбор условия рисования нижней диагонали зависит от четности самого размера карты.
Тут можно заметить, что разница четностей столбца и ряда в каждой первой диагонали ряда паттерна обратна четности размера карты. А при рисовании паттерна диагонали просто чередуются, как и чередуется четность столбца, и как следствие чередуется равенство разностей четностей ряда и столбца и четности размера карты. Поэтому для выбора диагонали используем равенство abs(i%2 — j%2) != parity , где parity — это остаток от деления размера карты на два. Если это условие верно, рисуем нижнюю диагональ, иначе верхнюю. Получим то что нужно, осталось отразить по красным линиям:
Код рисования четверти всего паттернаДля отражения точек рисования отнимаем от края вспомогательной сетки индекс точки, ничего сложного. А вот в коде это выглядит громоздко. Взгляните сами:
Но ничего страшного, главное, что работает, и работает безошибочно. Теперь получаем правильный паттерн для сеток с четными и нечетным размерами:

Если вы что-то поняли во всей этой мешанине четностей, то либо вы гений, либо у меня получилось что-то объяснить. Однако дальше не легче, но радует то, что мы почти нарисовали сетку. Осталось дополнить паттерн линиями.
Тут я не стал ничего придумывать с отражениями, ведь отрезки рисуются в две ячейки и при отражении некоторые места будут рисоваться два раза, а обрабатывать кучу исключений отдельно не очень хочется. Самым простым решение мне показалось рисовать вертикальные линии также, как мы это делали с прямоугольной сеткой, только отбрасывать ненужные. Напомню код рисования линий в простой прямоугольной сетке:
Однако просто скопипастив его в нашу функцию, получим кривое рисование при четных размерах карты, ведь при них первый ряд должен иметь смещение в единицу, а при нечетных этого смещения быть не должно. Это вытекает из смещения первого шестиугольника в первом ряду, при четных значения оно нечетно поэтому и рисуем со смещением, и наоборот.
Для выбора смещения сравним четности размера карты и ряда, если они отличаются, то рисуем без смещения, иначе со смещением. Пихать сюда условие не имеет смысла, ведь мы можем выбрать смещение через отличие четности карты и четности столбца конструкцией abs(parity-i%2) . Просто напомню — parity это остаток от деления размера карты на два. Проверьте сами, при четных столбцах и нечетных размерах карты получается единица — то самое смещение. Запишем это выражение в смещение в цикле:
Цель почти достигнута, осталось избавиться от лишних линий по углам:

Для этого при рисовании линий добавим некоторое условие, что мы хотим нарисовать линию в пределах каких-то границ. Для обозначения границ заметим, что с каждым рядом мы начинаем рисовать на ячейку ближе к левой границе, поэтому границу можно задать как смещение первого ряда минус индекс ряда:
Однако в нижней половине шестиугольника смещение начинает идти обратно, а индекс ряда только возрастает. Поэтому будем смотреть, в какой половине мы находимся, и выбирать нужную формулу для расчета левой границы. Для нижней части карты используем положение ряда относительно центра карты, просто отняв от его индекса половину ее размера:
Это мы задали левые границы. Для правых просто отразим левые в силу четности размеров вспомогательной сетки:
Вот и все — сетка готова. Осталось только добавить смещение для расположения сетки в начало координат, offset = grid_basis.x+grid_basis.y*2 . Однако тут опять играет роль четность размера карты, так что когда она четна прибавляем к смещению горизонтальный базис ячейки.
После всего этого мы наконец может расслабиться и посмотреть на точнейшим образом нарисованные сетки:
Рисование шестиугольников
Тут на самом деле все просто, можете расслабиться. После этой жести с четностями ничего хуже уже не будет. Для рисования шестиугольников вспомним функции для получения вершин, которые я приводил выше и просто соединим массив вершин шестиугольника линиями:
Функции для получения вершин, если лень мотать наверх
И рисуем множество линий между точками, не забыв замкнуть цепь:
Замечу, что здесь важен порядок следования вершин в возвращаемом функцией списке. В моем случае они идут по часовой стрелке, начиная с правой и с правой верхней для вертикальных и горизонтальных шестиугольников соответственно.
Для заливки шестиугольника, по аналогии с прямоугольником, рисуем полигон:
Выглядит все это как то так:

Шестиугольные сетки в изометрии
Что может быть лучше сетки шестиугольников? Правильно, сетка шестиугольников в изометрии. Вы могли заметить, что ни в одной функции я не использовал числа — везде я работал с векторами. А это значит, что поменяв базис, автоматически поменяются функции — так и должно работать программирование. Помните, в части про преобразование координат я все вычисления автоматизировал? Так вот, наконец то нам это пригодится.
Как вы уже наверняка поняли, шестиугольная сетка это лишь обертка над обычной прямоугольной, ведь все функции рано или поздно сходятся к работе с базисами вспомогательной сетки. Поэтому для создания изометрии не будем придумывать велосипед, просто зададим ее переменной, отвечающей за искажение отношений горизонтальных размеров к вертикальным:
Тогда для изменения вида делим y -координату каждого базиса вспомогательной сетки на это искажение:
И все, даже не нужно менять базисы шестиугольной сетки, ведь мы их задавали через базисы вспомогательной. Вот пример:
На самом деле, благодаря векторным преобразованиям мы можем делать с базисами вспомогательной сетки все что угодно, и все функции все равно будут работать. Поэтому давайте сделаем сетку не просто в изометрии, а еще и повернем ее на 45°, также, как мы это делали в статье про прямоугольные сетки:
Напомню, что базисы шестиугольной сетки мы не трогаем, ведь они все так же заданы через базисы вспомогательной. Выглядит вся эта магия как то так:
Красиво, конечно, но игру на этом не сделать. Нужно также уметь что-то на этих сетках делать.
Изометрические преобразования
С преобразованиями тоже мудрить ничего не стоит, ведь, напомню, шестиугольная сетка — это только обертка над обычной, поэтому все функции будут работать в штатном режиме. Единственное, что наверно стоит рассмотреть, это получение ячейки вспомогательной сетки, левый верхний угол которой находится в центре шестиугольника.
Для этого вспомним, как мы направляли базисы для различных ориентаций шестиугольников. Вот картинка из почти самого начала моего рассказа:

Посмотрев на нее, все сразу становится ясно. В случае горизонтальной ориентации при каждом перемещении по x на шестиугольной сетке мы двигаемся на две ячейки вспомогательной, а при движении по y мы движемся на одну ячейку вправо и на три вниз. Для вертикальных применима эта же логика. По этим формулам написать функции можно проще простого:
В изометрии ближние объекты должны рисоваться раньше дальних, и, если помните, в прямоугольных изометрических сетках мы использовали сумму координат. Здесь делаем тоже самое, только находим сумму координат центральной ячейки в шестиугольнике, получаемую по алгоритму выше.
Расстояние на сетке
Часто бывает нужно найти расстояние между двумя шестиугольниками. Для этого заметим, что при перемещении в любой из соседних шестиугольников меняются сразу две координаты из трех, причем на единицу. Тогда сумма модулей всех трех координат либо меняется на два, либо не изменятся вовсе. В таком случае сеточное расстояние от начала координат можно найти как половину суммы модулей трех координат шестиугольника, а для нахождения расстояния между двумя шестиугольниками можно просто найти смещение одного относительно другого через разность. Это, опять таки, взято из англоязычной статьи. Получается вот такая небольшая функция:
Сеточное направление
Для поворота объекта в сторону ячейки на прямоугольных картах мы, если помните, находили направляющую ячейку. Здесь будем делать то же самое.
Это один из немногих алгоритмов, где нам понадобится третья координата. Для нахождения направляющей разделим сетку по трем осям и заметим, что в каждой части получившейся сетки одна из трех координат максимальна по модулю:

Теперь все, что нам нужно, это выбрать нужную часть и, сравнив модули не наибольших координат, выдать одно из осевых направлений, представленное в виде направляющей ячейки. Прямо на осях одна из координат всегда равна нулю, поэтому просто перехватим их как особые случаи, хотя это и не обязательно. Для указания вдоль оси будем использовать знак разности конечного и начального шестиугольников.
Также заметим, что на диагональных ячейках модули не наибольших координат равны, поэтому мы можем смещаться в любую из двух осей. Я выбрал смещение в сторону оси по часовой стрелке, в коде это выражается строгостью знака сравнения. Для смещения против часовой стоит допустить равенство. Так выглядит этот алгоритм в коде:
Принцип работы этого алгоритма тот же, что и в статье про прямоугольные сетки. Мы смотрим на разницы координат и по ним определяем направляющую. Как мы видим, все работает:
На всякий случай скажу, что этот алгоритм не зависит от ориентации шестиугольников, ведь работа идет с их координатами, а координаты внутри сетки никак не зависят от ее ориентации в пространстве (поверните голову на 90° и вертикальные шестиугольники станут горизонтальными)
Поиск пути
Основной алгоритм поиска пути тот же, что и у обычной прямоугольной сетки, отличаются только соседи и проверки на нахождение точки внутри карты. Главный алгоритм — A*, его не трогаем совсем.
Соседи у шестиугольника выглядят как то так:

Можно заметить, что они также не зависят от ориентации шестиугольников, поэтому в будущем будем пользоваться одной функцией для обоих случаев.
Также у разных видов карты отличаются проверки на принадлежность шестиугольниа им. Алгоритмы для разных ориентаций шестиугольников немного отличаются, но в целом они похожи, поэтому пояснять я буду только на примере горизонтальных, а для вертикальных оставлю уже готовые функции.
Начнем с прямоугольной карты. Для наглядности напомню как она выглядит:

Синим обозначены границы карты. Оси в такой сетке идут не параллельно сторонам прямоугольника, поэтому просто ограничить их нулем и границей карты не выйдет. Так сработает только для Y оси сетки, а горизонтальные границы зависят от смещения по Y . Перемещаясь вдоль оси Y , расстояние до левой границы в ячейках вспомогательной сетки увеличивается на единицу, значит на половину шестиугольника. Аналогично с правой границей, только до нее расстояние уменьшается. При округлении левой границы используем floor , т.к. когда граница проходит ровно между шестиугольниками, мы выбираем тот, что внутри. По аналогии используем ceil для правой границы:
Для вертикальной ориентации логика точно такая же. Вот функция для нее:
Теперь про шестиугольную карту. Ее вид:

Для простоты вычислений будем считать границы от центра карты. Просто потыкав и посмотрев на координаты я пришел к следующим формулам центров:
Так как карта симметрична во все стороны, нам достаточно просто расстояния от центра для того, чтобы сказать, внутри точка или нет. Подобно кругу в привычной математике: если расстояние от центра круга меньше радиуса, значит точка внутри.
Так что функция получается длиной буквально в три строки:
Для проверки поставим условие, что для рисования шестиугольника под курсором он должен быть внутри сетки:
Отлично, теперь можно спокойно реализовывать алгоритм поиска пути:
Ищем путь истинный
Данный код я взял напрямую из своей реализации, так что некоторые моменты не освещены, за ненадобностью. Поглазеть на полный код сможете кликнув по ссылке в конце статьи. Так это выглядит:
Растеризация отрезка
Вот с растеризацией у шестиугольной сетки большие проблемы. Можно, конечно, придумать что нибудь с алгоритмом Брезенхема для растеризации отрезков, однако я не думаю что вам нужно растеризовывать по 1000 отрезков за кадр, поэтому на оптимизацию позволим себе немного подзабить и воспользуемся линейной интерполяцией, которую, кстати, и предлагает автор популярной англоязычной статьи. В данном случае она легко реализуема, т.к. расстояние между шестиугольниками — целое число.
Здесь я оставлю только реализацию:
Вот так это выглядит:
Пару слов в завершение
Вот и подошел столь запутанный рассказ к концу. Я постарался объяснить все максимально подробно и вставлял как можно больше картинок, надеюсь не зря. Никакую маленькую игру я делать не стал, ибо тут и так хаватает над чем подумать, поэтому просто оставлю код получившегося класса на >500 строк. Я подразумеваю его использование через автозагрузку, как собственно я и делал во время работы над ним, однако его лучше не использовать в реальном проекте, ибо там все запихано в кучу, да и еще все время висит как синглтон, хотя сетка нужна далеко не всегда.
Я надеюсь эта статья позволит вам полностью реализовать давние мечты по созданию «убийцы героев» или что она позволила просто интересно провести вечер. До скорого!
Гексы и пиксели: советы по рисованию
Как правильно рисовать шестиугольники для тактических видеоигр и пошаговых стратегий.
Привет! На связи Саша Тюпин. Последние несколько лет я разрабатываю пошаговые стратегиями на гексагональных картах.
В этой статье расскажу как собрать из шестиугольных тайлов нормальную карту с нормальными переходами, да и вообще поделюсь всякими мыслями, техниками, фишечками и прочим.
Речь пойдет прежде всего про 2D и пиксель-арт в частности. Если вы любите 3D и/или текстуры высокого разрешения, то стоит обратить пристальное внимание на туториалы Catlike Coding, там все очень подробно и с кодом объясняется.
Впрочем, про используемые там техники я тоже немного напишу.
Для начала объясню в чем, собственно, сложность и почему для квадратных тайлов есть сотни туториалов, тысячи примеров, специальные инструменты в редакторах и движках, а для гексагональных нет вообще ни черта, хотя про их существование вроде как всем известно.
Проблем тут две:
- Бесшовные текстуры — их сложно рисовать, поскольку тайлы не прямоугольные и нетривиально стыкуются между собой. Нет инструментария в редакторах.
- Переходы между биомами — опять же, сложная стыковка гексов и практически полное отсутствие наработанных практик. Непонятно, как вообще подступиться к задаче.
Как обычно рисуется прямоугольный тайл? Создаем спрайт, ставим tiled mode по обеим сторонам и смотрим, чтобы он был красивый и бесшовный. А с гексом что делать?
Здесь есть несколько решений и пара из них даже хорошие.
Тоже вариант, конечно. Сделать каждый гекс отдельным и хорошо заметным. Путь простой, но скучный. Кроме того, игра становится похожей на настолку, что глубокому погружению не способствует.
Ну тут уж совсем все просто. Рисуется красивая карта, а слой с гексами натягивается поверх нее. Опционально можно раскидать какие-нибудь препятствия. Такое мы видели в Panzer General и в Героях.
Основная проблема решения — ограниченная вариативность карт, но если этого не требуется, то вариант роскошный. Можно делать сколь угодно сложные и красивые задники.
А может все-таки можно как-то заморочиться и нарисовать бесшовные гексы? Ладно, варианты есть, но все они так себе.
Нарисовал, сохранил, посмотрел в игре. И так сто раз по кругу. Для настоящих самураев. Если игра очень быстро собирается или способна обновлять ресурсы в рантайме (так кроме веба вообще умеет кто-то?), то, в принципе, терпимо.
Для Aseprite, кажется, ничего нет, но можно написать скрипт. Правда, придется кнопку жать все время, но хоть что-то. Когда-то я даже делал на Node.js скрипт, который следит за файлом и показывает в браузере превью.
В общем, тут только грусть и костыли, человеческих решений нет. Если знаете редактор, который поддерживает гексагональный тайлинг — напишите в комментах.
А что если нарисовать прямоугольный тайл, а потом как-то хитро его повернуть, нарезать и превратить в гексагональный? Пара вариантов есть — они описаны в гайде для Battle for Wesnoth, так что повторять их не буду.
Существует еще несколько вариантов натянуть прямоугольную текстуру на гексагональную сетку, например вот эти:
В первом случае нужно 4 разных гекса для создания бесшовности, во втором всего один, но текстура очень часто повторяется.
На мой взгляд этот путь тупиковый, хотя бы потому что есть следующий, и он намного лучше.
Берем бесшовную текстуру побольше и обрезаем ее в шейдере по маске гекса. Решение современное, красивое и удобное. Требует знания шейдеров на уровне «сложить и перемножить числа».
Делается достаточно просто, выглядит прекрасно. На мой вкус — лучший вариант.
Ок, с бесшовностью вроде разобрались.
Так, а вот это уже намного сложнее. Биомы-то у нас есть, но они все еще выглядят как из настолки. Нужны красивые переходы между ними.
Посмотрим, что мы тут можем сделать.
Ну да, это мы уже проходили. Ничего не делай и ничего не будет.
Есть одна текстура, есть другая, давайте мы их сблендим и будем красиво. Основной способ стыковки игр с 3D окружением и/или текстурами высокого разрешения. Может выглядеть очень пристойно, особенно если блендинг нелинейный и дополнен геометрией.
Пустыня мягко перетекает в равнину, а в более резком переходе к траве уже используется другой паттерн — с пятнами. Эффект усиливают геометрия, объединяющая гексы.
Решение это, конечно, хорошее, но для пиксель-арта подходит слабо. Скорее всего получится шумная каша. Я с трудом смог найти пример такого подхода в единственной игре — Emperor of the Fading Suns, где это немного сглажено видимой границей гекса и CRT-монитором.
При этом переходы к воде здесь все еще сделаны нормально.
В наше время такое делать уже, конечно, стыдно.
Я не знаю игр где используется этот паттерн, но я видел его в паре паков, предназначенных для создания карт. В нем переход рисуется от вершины к вершине для каждого их сочетания. Выглядит это так:
На мой вгляд, этот подход состоит целиком из недостатков:
- Полный сет переходов это целых 30 тайлов.
- Все их как-то надо состыковать друг с другом или замаскировать стыки границами гексов.
- Переходы жестко привязаны к биомам.
- Иногда непонятно к какому конкретно биому принадлежит гекс.
В общем, не рекомендую.
Перейдем к рабочему решению — сделать кучу стыковочных гексов с вариантами для каждого сочетания граней. Вот так это может выглядеть:
Тут читатель может сказать — ты вообще считать умеешь? Здесь же 64 варианта, до пенсии рисовать можно! А ты еще говорил, что 30 это много. А стыковать все добро как?
На самом деле всё не так страшно. Как можно заметить, переходы по граням все очень похожи друг на друга, а значит их можно собрать из боле мелких элементов и в следующей части статьи я расскажу как. А пока просто будем считать, что они у нас есть.
Поясню как это все работает. Возьмем вот такой кусок местности:
Сначала сортируем биомы по приоритету, от этого будет зависеть то, как они будут перекрывать друг друга. В нашем случае это вода — земля — трава.
Биом с самым низким приоритетом, воду, пропускаем.
От земли делаем переходы ко всем биомам с более низким приоритетом, в данном случае к воде.
Покрасим переходы. Я их просто залью одним цветом, но можно применить ту же текстуру, что используется на гексах (см. выше про маски).
Теперь повторяем все то же самое с травой.
Вот и все. Как можно заметить, переходы к воде смотрятся, мягко говоря, не очень. Ну да, чтобы выглядело хорошо, нужно для воды специальные рисовать. Этот подход применяется в небезызвестной Battle for Wesnoth. Когда-то и там были стремные переходы к воде:
А потом ничего, нарисовали красивые (правда не для всех биомов):
Решение похоже на предыдущее, но, если надо, то переходы рисуются с обоих сторон грани — можно одного типа, а можно разные. Это позволяет делать промежуточные биомы, что решает сразу две проблемы.
Во-первых переходы к воде теперь выглядят по-человечески:
Во-вторых, если переход двух биомов друг в друга выглядит странно — ну там снег в лаву или что-то такое — то можно сделать промежуточный биом из обычной землицы:
Теоретически возможна, практических реализаций я не встречал. Однако, это не значит, что шейдеры бесполезны — с их помощью можно усилить эффектность перехода, например сделать волны и прибой:
Теперь давайте разберемся как вообще рисовать все это добро. Я делаю это скриптом на JS (не входит в комплект), как и всю подготовку ассетов, но можно и прямо в игре все собирать, просто спрайтов будет больше в несколько раз.
Каждый гекс может примыкать к другому биому любой комбинацией из своих 6 граней. Я помечу каждую из них номером от 0 до 5, начиная с северо-восточной.
Каждая грань находится в одном из двух состояний — примыкает или нет. Таким образом, каждый шаблон можно обозначить числом от 000000 до 111111 в двоичном формате или от 0 до 63 в десятичном. То есть нам нужно сгенерировать 64 шаблона чтобы покрыть все возможные комбинации.
На самом деле, можно все дико вращать и зеркалить и тогда понадобится только 14, но если уж мы делаем все скриптом, то проще подготовить сразу все варианты.
Теперь надо придумать как бы все это разбить на кусочки, повращать и сложить обратно. К счастью, эта работа уже проделана мной.
Если долго вглядываться в гексы, то можно заметить что каждая вершина в переходе может быть открытой (1), то есть граничить только с одной гранью с переходом, или закрытой (2), то есть с двумя.
Это важное наблюдение, которое позволит нам разбить грани на 4 группы:
- A — переход от одной открытой вершины к другой.
- B — переход от закрытой вершины к открытой по часовой стрелке.
- C — переход от открытой вершины к закрытой по часовой стрелке.
- D — переход от одной закрытой вершины к другой.
Нарисуем для каждой группы грани 0, 1 и 2. Остальные зеркалим. Можно только 0 и 1, но я считаю, что если уж что-то зеркалить, то нужно делать это по обеим осям сразу, иначе очень заметны повторения. Кроме того, при такой разбивке отзеркаленные грани никогда не буду примыкать друг к другу.
Помимо прочего, я обрезал кусочки шаблонов таким образом, чтобы они не перекрывали друг друга при сборке — резать надо ту половину, которая примыкает к закрытой вершине. Поэтому кусочки из группы D такие куцые.
Теперь собираем шаблоны. Алгоритм простой — берём числа от 0 до 63, переводим в бинарный формат и пробегаемся по всем граням. Для каждой из них смотрим на соседей и выбираем группу, а потом и номер грани. Для 3, 4 и 5 зеркалим по обоим осям 0, 1 и 2 соотвественно. Иногда двигаем кусочек шаблона куда надо.
Теперь неплохо бы проверить как всё это стыкуется. Я собрал для теста вот такую вот загогулину, которая, по идее, включает в себя все возможные стыки:
В качестве финального штриха я подрезал и повернул некоторые кусочки, чтобы часть стыков была видна сразу и вся конструкция занимала меньше места.
Теперь огонь! Можно пользоваться.
Такие вот мысли про гексы. Статья не претендует на какой-то ультимативный гайд по теме, скорее на точку отсчета для своих мыслей. Про то как рисовать обычные тайлы материала миллионы, а вот про гексы совсем нет. Пусть будет.
Этой статьи не было бы, если я не разрабатывал свою пошаговую 4x-стратегию на гексагональной карте. Она называется «Луна» и почитать про нее можно в обзорной статье. Девлог я веду в Телеграме и Твитере.