Вход

Просмотр полной версии : Legends & Myths (dev log)



Deadly
30.12.2025, 19:41
Всем привет.
Тема создана как альтернатива группе в ТГ (https://t.me/ZXGame_LegendsAndMyths).


Работы над проектом начались в феврале 2025 года. Ветка доступна на git (https://github.com/DeadlyKom/HoMM).

Всё началось с общения во внутреннем чате. Рома (Beaver) предложил — я поддержал. На руках были готовые арты от ЛД (Dragon Lord’а) и Snake (https://zxart.ee/rus/avtory/s/snake1/)’а
, и возник вопрос: а почему бы не попробовать использовать эти арты для создания хорошей игры по мотивам «Героев» и «Страны мифов».
Графика планировалась исключительно цветная. Так как опыта работы с цветом у меня нет, решил написать людям, с кем ещё не испорчены отношения. По итогу откликнулся только один — и то потом молча ушёл… но затык, собственно, не в них, а в моих требованиях и хотелках к конечному результату (наверное).


Параллельно начались эксперименты, фейкшоты — как могло бы всё это выглядеть на практике, и это вылилось в такие арты.
https://i.postimg.cc/LX9FVNgM/411340826-5456e2e3-e5aa-4cbf-b2bb-5beda24ef8e3.gif https://i.postimg.cc/YS75pb3H/411340720-0e420286-f7a8-4b68-bc04-1715fd7980b8.gif

https://i.postimg.cc/NjZ0pqQY/1.png

Потихоньку начал перерисовывать героя из HoMM3, и вот что из этого вышло:
https://i.postimg.cc/Px0skhFJ/411341328-c07f2ede-1492-47e6-b930-1921fd68e23a.gif
https://i.postimg.cc/wxsYQy7M/415997891-4e4ed956-34e5-479f-8ef8-0965090179e4.gif

Так как готового ничего не нашлось, пришлось засучить рукава и нарисовать курсор,
а после — и всю остальную графику с нуля, отталкиваясь от ранее озвученного.
https://i.postimg.cc/Hnt299MK/426013500-fb96767d-c29d-425a-a928-5766cc96ae5c.gif

Когда набралось достаточно контента, был написан рендер на основе тайлов из DDD и некоторых наработок Torn Metal.
Собрал первый видосик, чтобы показать, что имеем на руках.

https://rutube.ru/video/1e89633fbdf6757e60abc5846360d3d6/

Поле боя хочется сделать больше, чем в «Стране мифов», но так, чтобы при этом можно было уместить всю необходимую информацию.
https://i.postimg.cc/3rsjkRy4/No141414.gif


https://rutube.ru/video/44b7ed522c8fafa39ad564b86517f94a/?r=wd

Заставки перед миссиями (примерные конечно)
https://i.postimg.cc/7L5MCTNG/4141414.gif

https://i.postimg.cc/m2Pqshvc/Zastavka.gif


https://rutube.ru/video/e571f65d45a76a5f4e82313e2c84630e/

Инвентарь:
https://i.postimg.cc/htLRkfmm/inventar'.png

Предметы:

https://rutube.ru/video/f5c3d90ca1cb2c1a4b296f62a015db2d/

https://rutube.ru/video/b3fe3ebc3734553ee0e7f94c44ae6b49/

https://rutube.ru/video/913c0347e62d1328ccaef8f2922f0028/

Персонажи (не окончательно):
https://i.postimg.cc/vBqs0kVk/Lica-glavnyh-geroev.png

Где-то по пути возникла мысль: а что если… И я решил поэкспериментировать — отказаться полностью от классических тайлов и перейти на гексагональные, приблизив игру больше к настольной. Никогда не любил идти по пути голого копирования, поэтому захотелось привнести что-то новое и интересное.
Выкинув старую графику тайлов, начал рисовать и экспериментировать, искать ответы на вопросы: каким должен быть размер шестигранников, как в них нарисовать цветную графику в 6912 и так далее.
Процесс рисования:

https://rutube.ru/video/53007d140d369a1461ce92c56558099d/

https://rutube.ru/video/2874981296823df19b37afa5342cc511/

Эксперименты, эксперименты, эксперименты с визуалом:
https://i.postimg.cc/pXdg0sVd/image.png https://i.postimg.cc/K4XBg9JM/image.pnghttps://i.postimg.cc/pV36ZRSk/image.pnghttps://i.postimg.cc/YS2n99vn/image.png

Также никуда не делись неисследованные гексагоны и вопрос, как они могут выглядеть.
https://i.postimg.cc/QN7bj56g/N4141.gif

В процессе пришлось немного доработать эмулятор до редактора спрайтов, который я планировал ещё со времён TM. Коротко о нём:
Основная идея: загрузить PNG — редактор разбивает изображение на пиксели, атрибуты и формирует маску. Далее можно переключиться в режим 6912, чтобы увидеть все неточности рисования и сразу исправить их прямо в редакторе.

Следующий этап — нарезка спрайтов нужного размера. В каждом отдельном спрайте можно сформировать области свойств, задав для них размер, имя, тип и само значение.

При экспорте выбранных спрайтов на выходе получаем JSON, описывающий спрайт со свойствами, и три бинарных файла: пиксели, атрибуты и маска. Далее с помощью Python (зависит от формата спрайта при экспорте) файлы конвертируются в исходный бинарный формат для встраивания в проект.

(Это очень кратко.)

https://rutube.ru/video/86545eae6e3dfc40fa097e599a7dfbf7/

Текущие задачи — написание рендера гексагонов. Всё залито в репозиторий (ветка HEX). Видно полный тайминг вывода одного гексагона полностью и частично обрезанного. Можно предположить, что отрисовка такой графики займёт достаточно времени, но это нормально для 3,5 МГц и выше.
https://i.postimg.cc/Fzqzf7Fc/image.pnghttps://i.postimg.cc/MGDG7sKH/image.pnghttps://i.postimg.cc/dtytHQVc/image.png

https://rutube.ru/video/86f9e399e76f1d71a472dc5640f54041/

Ну и напоследок — скетч. Не думаю, что он останется, но на память — почему бы и нет?

https://rutube.ru/video/e8664a5beb03dbe6b8410075de53bb11/

Dart Alver
30.12.2025, 22:49
Эх, не привыкши мы на спеке к гексагонали, всё думается что проще задержку для диагональной компенсации втюхать, хотя ... :D
Старенький-престаренький мокапчик под типа "страну мифов" с псевдо гексагональю. Думалось в карте для бекграунда использовать 16x16 спрайты в столбцах со смещением по вертикали, а для построек и локаций уже доп. спрайты, чтоб с масками не издеваться и атрибуты не совмещать.
https://pic.maxiol.com/thumbs2/1767123110.3557002821.xpeccy222120146.png (https://pic.maxiol.com/?v=1767123110.3557002821.xpeccy222120146.png&dp=2)

Deadly
30.12.2025, 23:20
всё думается что проще задержку для диагональной компенсации втюхать
Тут я несовсем понял, о чём речь

Старенький-престаренький мокапчик под типа "страну мифов" с псевдо гексагональю
Да, прикольный.
Мне тоже приходилось плясать, не знал, с какой стороны подступиться, так как не было наглядных референсов.
Полез на Pinterest искать шестиугольники, пробовать их адаптировать под 6912 и минимальный клешинг.
В итоге пришёл к такому виду гексагона:
https://i.postimg.cc/mDykNC7x/image.png

Красный — вывод с маской + опциональное закрашивание атрибутов
Синий — рисуется без маски, но атрибуты всегда применяются
Бирюзовый — рисуется без маски, края просто чёрный ink
Жёлтый — не обновляется

Dart Alver
31.12.2025, 00:06
Тут я несовсем понял, о чём речь
Ну это я о том что для поквадратного деления карты путь по диагонали примерно в 1,4 ( √2 ) раза длиннее прямого, и если использовать диагональное перемещение, то эту длину нужно компенсировать. ))

- - - Добавлено - - -

В вашем случае гексогон довольно крупный, для моей картинки гексогоны мелкие и я рассчитывал привести формат
https://pic.maxiol.com/thumbs2/1767128719.3557002821.x1g.png (https://pic.maxiol.com/?v=1767128719.3557002821.x1g.png&dp=2)
к виду обычных тайлов но со смещением
https://pic.maxiol.com/thumbs2/1767128081.3557002821.x2g.png (https://pic.maxiol.com/?v=1767128081.3557002821.x2g.png&dp=2)
для обозначения земли, леса и т.д. этого как-бы хватало, а сложные картинки (здания, локации и т.п.) разбивать на большее количество тайлов и так выводить. Но всё это так, в голове покрутилось и выветрилось ))

Deadly
31.12.2025, 00:10
да, да, я понял о чём ты

Lethargeek
31.12.2025, 14:02
Потихоньку начал перерисовывать героя из HoMM3, и вот что из этого вышло:
под такие спрайты нужен преимущественно светлый фон + светлая обводка скорей всего
из твоих примеров тайлов как на песке еще получится норм, на траве уже не очень смотреться будет

Deadly
31.12.2025, 14:33
под такие спрайты нужен преимущественно светлый фон + светлая обводка скорей всего
Для более или менее нормального отображения ink всегда должен быть чёрным, а не белым или каким-либо другим.
Это правило позволяет минимизировать визуальные неприятности с клешингом — думаю, это очевидно.

Есть старая сборка (https://t.me/ZXGame_LegendsAndMyths/111?comment=179), где работал над поиском пути. Там можно увидеть и курсор, и героя с выводом через маску, и то, как при чёрном ink’е это не слишком больно для глаз.
https://i.postimg.cc/fbbZKKr9/141414.gif

Lethargeek
31.12.2025, 17:30
Есть старая сборка, где работал над поиском пути. Там можно увидеть и курсор, и героя с выводом через маску, и то, как при чёрном ink’е это не слишком больно для глаз.
на траве местами всё же неприятное месиво

а чёрные стебли на зелёном фоне не пробовал?

Deadly
01.01.2026, 00:14
Всех с новым годом !!

Deadly
04.01.2026, 00:04
В итоге пришёл к такому виду гексагона:
собственно, если кому-то интересно сам процесс вывода гексагонов на экран

https://rutube.ru/video/6893831b01c1e7bef71afef46729bb81/

Deadly
08.01.2026, 13:18
Добавил поддержку анимации гексагонов, и в качестве первого примера реализовал анимированный туман.
Для быстрого proof of concept прикрутил небольшой код, позволяющий визуализировать анимацию тумана — вот что получилось.

Вжух

https://rutube.ru/video/bfd194e698871a8630c63863a908b351/?r=wd

P.S. Вывод временно осуществляется в одном экране, без буферизации, поэтому видны мерцания (в эмуляторе NoFlick попытался это сгладить)

Deadly
15.01.2026, 16:01
https://i.postimg.cc/FzxzTF0D/photo-2026-01-15-15-56-59.jpg

Display List для гекс-рендера

Хочу описать одну архитектурную идею, к которой я пришёл при оптимизации рендера гексагональной карты.

Текущая реализация:
В рендере карты есть несколько проходов:
- отрисовка гексов;
- туман войны;
- эффекты;
- обновление экрана.

При этом все они проходят по одним и тем же гексам и тем же маршрутами, но каждый раз:

- заново считают экранные координаты;
- заново обходят карту;
- держат много временных значений в регистрах.

Это работает, но не быстро и не очень эффективно.

Идея
Разделить процесс на два этапа:

Генерация списка — выполняется редко (например, при скролле или других событиях).
Исполнение списка — выполняется каждый кадр.

Вместо того чтобы каждый проход сам обходил карту, я один раз формирую линейный список того, что нужно отрисовать.
По сути — простой software display list.

Как выглядит список
Список — это массив (или стек) записей, по одной на каждый видимый гекс.

В каждой записи хранится:
- индекс обрабатываемого гексагона в рендер-буфере (0–39);
- номер строки начала рисования (0–191);
- количество рисуемых колонок гексагона (0–5).

Такой список даёт несколько преимуществ:
- порядок элементов уже правильный (сверху вниз);
- в будущем можно добавить сортировку по оси Y для спрайтов;
- длина списка фиксирована;
- внутри прохода нет ветвлений (не обновляемые гексагоны просто не попадают в список).

Генерация списка

Этот этап выполняется:
- при изменении положения видимой области карты;
- при анимации гексагона;
- при игровых событиях, влияющих на состояние гексагона.

То есть список пересобирается только тогда, когда это действительно нужно.

Использование списка

Каждый проход рендера просто:
- берёт готовый список;
- линейно по нему проходит;
- делает своё грязное дело.

Например:
один проход рисует гексы;
другой — эффекты;
третий — туман войны.

Никаких повторных расчётов — только чтение уже подготовленных данных.

Задел на будущее

Пока список содержит только гексы, но формат легко расширяется:
- можно добавить тип записи (гекс, спрайт, эффект);
- смешивать разные элементы в одном списке;
- рисовать всё одним проходом сверху вниз.

Даже если до этого не дойдёт, текущая реализация уже даёт ощутимый выигрыш.

Итог:
Исключается многократный обхода карты в пользу одного подготовленного списка.
Это упростило код, сделало его более читабельным, снизило нагрузку и сделало рендер более контролируемым.
По сути — простой display list без лишней магии, который хорошо ложится на ограниченное железо.


Прошло время.... )


https://rutube.ru/video/1effacf09506f0621d6454f8099592ae/

Здесь постарался исправить почти все проблемы с обновлением гексов. Самое главное — когда экран не скроллится, даже с анимациями FPS не проседает. Далее нужно корректно обновлять гексагоны выше и ниже (если это необходимо) текущего анимированного гексагона. Это хорошо видно на горах (высоких тайлах): гора не обновилась после того, как выше изменился гексагон. Но это уже другая задача — расширение текущего рендера.

Как и в предыдущих версиях, буферизации нет — при скроллинге хорошо заметно, что окно отрисовывается за несколько фреймов.

P.S. Да, это может показаться регрессом, но всё же работает на 3,5 МГц.
Тут (https://t.me/ZXGame_LegendsAndMyths/131?comment=207) можно скачать билд, описание во вложении...

Deadly
16.01.2026, 12:16
Скоро буду заниматься вопросом буферизации, когда из основного окна будет производиться переброска в теневое. При этом копирование целого игрового экрана займёт чуть меньше одного фрейма.

Но я пошёл чуть дальше. С подобным сталкивался ещё в TornMetal, когда обновлялась только та часть экрана, которая изменилась. Там это было проще — всегда были доступны два окна. В текущей реализации такой возможности нет, так как спрайты значительно больше, и копирование из страниц в буферы перед отрисовкой слишком затратно. Поэтому приходится рисовать в основном экране (который выступает в роли теневого), а затем обновлять теневой экран.

Логику обновления описал следующим образом:
; -----------------------------------------
; ⚠️ холодный запуск:
; 1. принудительно выставить адрес левого верхнего угла отображаемой карты
; данные о центрировании карты могут браться из метаданных карты и/или иным способом
; 2. принудительное копирование в Tilemap-буфер из тайловой карты на странице 1
; 3. принудительное выставление флагов в Render-буфере
; формируется для каждого тайла
;
; ⚠️ горячий запуск:
; не должен самовозбуждать флаги обновления в Render-буфере,
; дабы снизить нагрузку на обновление экрана
;
; ℹ️ цикл обновления экрана:
; 1. в выставленные тайминги скролла проверить вектор движения карты,
; обновить адрес левого верхнего угла отображаемой карты
; 2. принудительное обновление Tilemap- и Render-буферов по необходимости,
; если адрес левого верхнего угла отображения карты изменился
; * Tilemap-буфер копируется из тайловой карты на странице 1
; * Render-буфер формируется для каждого тайла, где:
; - выставляется флаг HU (принудительного обновления гексагона)
; - копируется флаг FG (тумана, 0 — гексагон закрашен туманом целиком)
; из буфера метаданных карты
; - номер анимации гексагона устанавливается в ноль,
; в последующих шагах он будет модифицирован
; * Render-буфер для каждого столбца гексагона выставляется флаг CU
; необходимости обновить столбец гексагона (0 - обновление столбца не требуется)
; 3. применение внешних событий изменения/обновления гексагонов
; * основной список гексагон-объектов,
; которые обязаны менять анимацию каждый тик гексагонов
; каждый такой объект имеет информацию о количестве анимаций в цикле
; * дополнительный список гексагон-объектов,
; которые рандомно выбираются из доступных на экране
; каждый такой объект после проигрывания одного цикла анимации
; удаляется из списка
; 4. отображение гексагонов
; 5. отображение объектов на карте
; * список динамических объектов влияет на флаг HU (принудительного обновления гексагона)
; спрайты героя, VFX заклинаний и т.п.
; 6. отображение тумана на видимых гексагонах
; 7. копирование узора рамки на игровой части экрана
; 8. анализ флагов HU (принудительного обновления гексагона) и высот гексагонов,
; позволяет определить, какие экранные блоки требуется копировать в теневой экран
; * выставленный флаг HU говорит о том, что гексагон изменился
; используя высоты гексагона, можно определить,
; какие соседние экранные блоки были задеты,
; и на их основе формируются флаги обновления грязных экранных блоков
; * экранные блоки копируются в теневой экран
;
; ℹ️ цикл обновления курсора:
; 1. если не требуется копирование из основного экрана в теневой:
; ⚠️ важно: обязательно в начале прерывания (избежать сечения луча)
; * восстанавливаем фон под курсором (в теневом экране), если он имеется
; * отображаем курсор в новой позиции (в теневом экране)
; ℹ️ ToDo: можно избежать этого пункта,
; если предыдущая анимация и положение не изменились
; * любая логика прерывания
; 2. буферный экран (основной) готов копироваться в теневой экран
; * включить в адресном пространстве #C000–#FFFF страницу 7
; (с теневым экраном)
; * отображаем курсор в новой позиции (в основном экране)
; * переключаемся на отображение основного экрана
; (теневой экран перестаёт быть видимым)
; * на странице 7 расположен код копирования экранных блоков,
; буфер под курсор и другие необходимые массивы
; (гексагон-объектов и т.п.)
; копируются блоки экрана в теневой
; (ℹ️ сечение луча не страшен)
; * отображаем курсор в новой позиции (в теневом экране)
; * переключаемся на отображение теневого экрана
; (основной экран перестаёт быть видимым)
; * восстановить фон в основном экране под курсором
; * сброс флагов готовности экрана
; -----------------------------------------

Но вот насчёт копирования из основного в теневой экран решил разбить на блоки:
https://i.postimg.cc/jjsQ9frY/image.png

Разбиение игрового окна на блоки копирования позволяет оптимизировать вывод и не обновлять весь экран одновременно в статическом виде. С анимациями есть исключение — требуется полное обновление игровой области:
При скролле обновляются все буферы, и игровое окно необходимо перерисовать полностью.
Игровое окно размечено на блоки 6×6 знакомест, так как это подходит для стековой переброски POP/PUSH. Также присутствуют области 4×6, 6×4 и 4×4.

ℹ️ Во время отображения каждая новая строка начинается на 4 знакоместа ниже. При выводе в буфер рендера (RenderBuffer) записывается информация о высоте каждого знакоместа: если знакоместо высокое — бит хранит 1, иначе — 0.

Эти данные необходимы для:
наложения сглаживающего тумана,
определения высоты структуры столбца гексагона.

Структура столбца гексагона:
бит 7 — самое нижнее знакоместо,
бит 0 — самое высокое.

При сканировании битов с 7 по 0 (LSB → MSB) ищем первый встречающийся включённый бит — номер бита обозначает высоту данной колонки гексагона.

- - - Добавлено - - -



https://rutube.ru/video/7b70ab1c2b7492a950f3855b911b67d6/

Небольшой экскурс в происходящее.
Если какой-то тайл гексагона обновляется (в данном случае это туман — простой способ отработать поведение обновления гексогональных тайлов корректно), то для корректного восприятия top-down гексагонной карты должны перерисовываться и соседние тайлы.

Добавил возможность перерисовывать ровно половину гексагонного тайла — его верхнюю часть. Напомню, тайл имеет размер 6×8 знакомест, и именно эта половина соответствует нахлёсту нижнего тайла. После смены флага обновления анимированного тайла окружающие его тайлы тоже должны обновляться, но не полностью.

Каждый гексагонный тайл разбит на 6 столбцов. Необходимо обновить все 6 столбцов текущего гексагона, а также по 3 столбца гексагона выше и ниже относительно текущего: правую часть одного и левую часть следующего — именно те области, которые перекрываются текущим тайлом.

Верхние тайлы обновляются полностью (в будущем можно оптимизировать и обновлять только нижнюю половину), а для нижних тайлов необходимо обновлять лишь их верхнюю половину. Иначе стокнёмся с проблемой зацикленности и придётся обновлять ещё и тайл ниже нижнего, и так далее, пока не закончится экран.

P.S. собственно можно взглянуть под капотом как оно работает.

Deadly
16.01.2026, 23:34
https://rutube.ru/video/189fbee5326d39052658acef80dfb9b3/

В целом удалось настроить всё вышеописанное — почти работает

В ToDo пока переносится установка флага обновления половины гексагона. Есть кейс, когда под текущим гексагоном тоже идёт обновление: включённый флаг блокирует обновление нижнего тайла. Нужно проверять это перед установкой флага и при необходимости сбрасывать его.

В остальном можно спокойно двигаться дальше по списку к буферизации отрисовки (ранее об этом писал).

P.S. В целом доволен проделанной работы.

Deadly
18.01.2026, 13:39
https://i.postimg.cc/NGDkCc4B/image.png
Потихоньку начинают проявляться очертания новой функции анализа изменённых screen block'ов игрового экрана (22x22 знакоместа) с последующим их обновлением через стек. Она, конечно, пока далека от рабочего состояния, но уже позволяет немного понять, как анализируются данные каждого готового фрейма перед тем, как он отправляется в основной экран.

Использовал INC (HL), чтобы потом визуально видеть счётчик того, какие screen block'и должны копироваться.




https://rutube.ru/video/6363073df7f1330e8584a1d2001a2924/
Это, конечно, не финальная версия, но в итоге выглядеть будет примерно так.
Обращу внимание на то, о чём писал ранее: разные высоты в гексагонах, привязанные к знакоместам. Именно эта логика и закладывалась в идею — чтобы сквозь туман проглядывали знакоместа графики гексагонов.
Добавим сюда ещё мерцающую анимацию на неизведанных гексах.

P.S. Если честно, я бы ещё поработал над картинкой и подумал, как придать объём на мягких переходах видимых гексагонах. Накладываться всё будет через OR, то есть без маски — возможно, получится как-то добиться ощущения объёма…

Deadly
22.01.2026, 10:39
https://rutube.ru/video/b37c8ab39214cdecc4c14b56408c4ccc/
Как я и рассказывал ранее, игровое окно разбито на 16 блоков копирования. Визуально это можно увидеть в области миникарты (справа сверху): там отображается сетка цифр 4×4. Цифра, отличная от нуля, означает, что соответствующий блок игрового окна должен быть обновлён.






https://rutube.ru/video/3294e58321dfa78159245d87c24cb71e/

Прикрутил переброску из теневого экрана — мерцание пропало, но вылез один нюанс (как всегда ������): переброска стеком в теневой экран не укладывается во фрейм, и при включённых прерываниях иногда пролетает порча экрана. Посмотрим, что с этим можно будет придумать позже. Плюс осталось ещё пару ToDo на исправление багов обновления гексагонов, которые тоже аффектят и дропают FPS.

В целом картинка сейчас вполне ожидаемая. Как и говорил ранее, в статическом режиме (без скроллинга) FPS будет более-менее нормальным, а со скроллом результат примерно такой и предполагался.

https://i.postimg.cc/W4tN4DTq/14141414141414.gif
P.S. Не стал перезаписывать видео, дабы показать процесс как он есть.




https://rutube.ru/video/614101b01fd544a13ad61f6a37f9b93b/
Собственно, ковыряюсь с UI игрового мира и параллельно пишу под это универсальный вывод спрайтов — с опциональной отрисовкой.

P.S. Что, зря я для себя инструмент писал?!
Сам себя не похвалишь — никто не похвалит (С)