Предисловие
Год назад я потерял работу. Не буду вдаваться в подробности, скажу лишь, что это был очень сложный период принятия новой ситуации и поисков работы.
Но, как бы там ни было, оставалось одно – любовь к коду и возможность создавать свои миры, а еще я глубоко и бесповоротно влюбился в настольную TTRPG Dungeons and Dragons – это еще и классный способ социализации, знакомства с новыми людьми и здоровый способ сбросить стресс.
Если кто-то еще не знает, то Dungeons and Dragons – это настолка, где мир игры, его лор, персонажи, квесты создаются, планируются и симулируются "Гейм Мастером", а игроки берут на себя роль "приключенцев" – персонажей, которые волею судеб вынуждены отправиться в опасные приключения и спасать мир.
Пожалуй, лучшее определение того, что такое D&D, дала Deborah Ann в этом интервью:
...
Во время наших сессий я замечал, что часто возникает один и тот же вопрос: “а сколько сейчас времени”, “а сколько времени прошло с момента А”, или “а куда делись эти 3-4 часа, если прошло всего 15 минут (внутриигрового времени)”. И я подумал: "блин, а было бы классно, если было бы приложение..."; но его не было.
Хотите верьте, хотите нет, но однажды вечером, в полудреме, уже почти засыпая, я увидел картинку: большие часы на весь экран, и рядом крутится планета с облаками, и я подумал: "Блин, круто! Вот это оно! Можно просто и быстро запилить, и будет выглядеть эффектно". Такое маленькое приложение, но при этом уютное и удобное. Так и родилась идея проекта dnd-time.com

В чем суть проекта?
На данный момент dnd-time.com – это внутриигровые часы, а также планировщик и менеджер событий на временной линии для TTRPG (Table Top Role Playing Games) сессий. Вы можете создавать множество отдельных миров, каждый из которых – это таймлайн, где вы можете описать ключевые вехи, события и даже дни рождения ваших персонажей (если нужно), вы также можете планировать события для своих сессий наперед.

Прототип часов и планету я собрал быстро, буквально за вечер.
Но дальше – больше: мне хотелось добавить интерактивности и превратить это в арт-инсталляцию, родились идеи:
- а давай запилим смену дня и ночи, и чтобы красивые градиенты меняли друг друга с течением времени
- а давай сделаем интерактивное солнце, чтоб оно также показывало примерное положение солнца над горизонтом (если нижняя часть экрана – это горизонт)
- а давай, чтобы облака, короче, создавались вокруг планеты с рандомным углом поворота, с рандомизируемой в диапазоне скоростью вращения, высотой и длительностью "жизни" и чтобы после "смерти" через рандомизируемое в диапазоне время создавалось новое облако.
- а и давай еще сами облака нарисуем, потому что все, что нахожу, не подходит и не нравится
- а давай сделаем, чтобы вариантов стрелок изменения часов было штук 6 и они, например, совались в интерфейс каждый раз по-разному
- а давай сделаем, чтобы ночью появлялись звезды
- а давай добавим музыку
- а давай, чтобы можно было по звездам кликать и чтобы проигрывались звуки в тональности музыки
- а давай, чтобы, когда на паузу поставил, все звуки замедлялись и анимации останавливались
... этому не было конца и края... НО МНЕ НРАВИЛОСЬ!
Какие могут быть юзкейсы?
Расскажу эпизод из моего личного опыта, когда я проводил игру для своих друзей. Мои игроки, разумеется, начали свой путь с таверны (даа, скукота, но это был мой первый опыт проведения игры, камон). Будучи новичками, они повели себя агрессивно, устроили акт вандализма, угрожали оружием тавернщику и посыльному, который дал им квест. К счастью, никто не пострадал (разве что портрет почившей жены тавернщика, проткнутый арбалетным болтом)! Я заранее предусмотрел такой исход событий и дал им переговорный камень, чтобы в случае чего они могли сообщить страже из ближайшего города.
Я понимал, что страже потребуется около часа времени, чтобы снарядиться, выдвинуться и прибыть на место происшествия. Я засек это время на часах 🙂
Мои игроки суммарно потратили еще примерно 30 минут игрового времени на то, чтобы исследовать таверну и ее окрестности, прежде чем выдвинуться дальше в путь. Стража прибыла слишком поздно, чтобы застать их врасплох, однако они решили подождать и устроить им засаду в самой таверне. Так и получилось – игроки были арестованы.

В моей истории у тавернщика также был бар и разные напитки, которые дают при умеренном употреблении полезные бафы длительностью до 1 часа, а при чрезмерном употреблении – неприятные дебафы, которые, в зависимости от дозы, увеличивали свою продолжительность и усиливали негативные эффекты.
Все это, как и многое другое, можно заводить и отслеживать на временной шкале.
А самое интересное, что это там на ней и останется, поэтому, когда вам понадобится вернуться в прошлое, чтобы вспомнить о каких-то событиях, вы всегда можете это сделать. Можно запланировать параллельные события или чем занимаются ваши NPC в других местах заранее. Или, например, ваша пати разделилась на 2 ветки, и в какой-то момент вам нужно “откатиться назад во времени” и продолжить за других игроков, при этом вы хотите видеть и знать, что, когда и в какое время закончит или начнет делать.
Если будете пользоваться приложением, присылайте ваши кейсы сюда: dm@dnd-time.com (заодно проверим, работает он или нет XD , мне будет интересно почитать, или пишите комментарии с идеями под этим постом.
Зачем нужно солнце?
Если вы посещали проект, то, возможно, заметили, что днем появляется солнце… которое вращается вокруг планеты? Если вы задавались вопросом, есть ли у этого какое-то практичное применение помимо красивого визуала, то вы не ошиблись: оно и правда есть.
Это все очень субъективно и всегда остается на усмотрение гейм-мастера, но, на мой взгляд, если в вашей пати нет персонажа с перком “keen mind” или какого-то соответствующего артефакта/заклинания, которое дает вам знать точное время, все, что вам остается, будучи героями в средневековом фэнтези-мире, – это ориентироваться по солнцу, его положению и цвету неба. Даа, я знаю, возможно, это кому-то покажется странным или притянутым за уши… но мне показалось это интересным.
Поэтому я постарался сделать так, чтобы положение солнца на экране, как и цвета неба, примерно соответствовали текущему времени суток, для того чтобы быть вдохновением для гейм-мастера.
Как это работает: представьте, что нижняя часть вашего экрана – это условный горизонт вашего мира. Утром солнце поднимается из-за горизонта, ближе к 12 часам солнце в зените, а вечером солнце уходит за горизонт. Так и здесь. Космос и планета – это скорее мое желание сделать это все частью чего-то целого и визуально приятного. У меня получилось? :)
Таким образом, вы можете описать игрокам цвет неба и примерное положение солнца (если они видят их), чтобы дать им намек или возможность бросить на survival или nature, чтобы понять приблизительный диапазон возможных значений.
У солнца есть пасхалка, возможно, вы ее найдете :)

Что еще планирую делать?
Планирую расширять проект и добавлять в него больше маленьких и больших полезных инструментов, которые будут синергировать друг с другом, а также улучшать сам таймлайн и его фичи, например:
- поиск событий по датам
- возможность создавать закладки на таймлайне и быстрое переключение между ними
- возможность ускорять или замедлять течение времени
- возможность масштабирования (zoom in и zoom out на временной линии)
Также я хочу создать:
- систему оповещений – вы можете включить эту опцию, и вам будут приходить пуш-уведомления о событиях, которые начались или закончились.
- планировщик и трекер погони: есть конец и начало гонки, можно создать несколько движущихся объектов, дать им скорость, возможность редактировать, менять скорость, возможность измерять расстояние между объектами. Лог погони, а также синергия событий с таймлайном.
- денежный конвертер – быстрая конвертация одних монет в другие по заданному курсу, а также возможность разбить общую сумму денег на количество членов в пати
- гербарий: энциклопедия растений с картинками и их описанием, по разным биомам, возможность поиска по тегам/фильтрам
- торговец: возможность настройки и генерации торговца в зависимости от уровня вашей репутации, жадности торговца, контрабандности/редкости товара. Он будет показывать стоимость продажи и покупки, если указана базовая стоимость.
- трекер инициативы и лог боя – возможность создать своих персонажей (связь с публичным SGI), выбирать действие и цель, например атаку или заклинание.
- создание аудиосцен (как в https://tabletopaudio.com/ только круче :D )
- сохранение состояния приложения через бэкенд
Что уже есть сейчас:
- интерактивный UI, звуки, музыка, пасхалки, анимации, смена дня и ночи
- создание, удаление и редактирование миров, у каждого своя временная линия, шкала и треки, каждый из которых редактируется, а состояние запоминается в local storage браузера. Данные обновляются и сохраняются каждую секунду течения времени.
- возможность кастомизировать события – цвет, описание, продолжительность, драг-н-дроп
- динамическое редактирование времени мира – можно задать день, месяц, год, часы, минуты и секунды. Есть возможность быстрой смены времени по заданным шаблонам
Технические особенности
Я постараюсь коротко, потому что, если буду подробно, то это растянется в бесконечное количество слов.
Итак, особенности, они же сложности:
- я узнал, что, оказывается, есть browser life cycle и что вкладка проживает разные стадии жизни. И если уйти со вкладки, то она уйдет в спящий режим в целях экономии ресурсов, во время которого все таймауты и интервалы замедляются. Для часов это убийственное ограничение, т.к. ты как Гейм Мастер вынужден держать 100500 вкладок открытыми и ты не хочешь принудительно держать открытыми часы. Решение: есть несколько способов, как не давать вкладке уснуть, например можно обновлять title каждую минуту. Есть и другие, о них можете прочитать вот по ссылке выше.
- я впервые попробовал поработать с audio API и побаловаться с разными аудиоэффектами. Оказывается, можно менять питч, делать cutoff-фильтры, заменять темп аудио и так далее. К сожалению, я не был силен в теме, а ИИ год назад был так себе, и насоветовал он мне херни – создавать аудиоконтекст на проигрывание каждого отдельного звука. Интуитивно я понимал, что контекст должен быть общим, но в тот момент у меня не было желания детально изучать тему, поэтому я забил болт и оставил как есть. Как результат, после всех моих идей со звездами – безумное количество ненужных листенеров и куча контекстов, которые создаются и умирают
- Я разлюбил стек tailwind + shadcn. Один мудрец сказал однажды: "начиная работу с tailwind, вы начинаете проект с техническим долгом"; и был совершенно прав. Tailwind невозможно нормально читать – это каша из 2-3-4-5 строк классов в html, которая черт пойми что делает, а колонка справа, где можно было бы прочитать стили, просто заспамлена целой кучей непонятно чего. Да, это прикольно и приятно для создания быстрых прототипов, но как будто можно так же или не хуже, но без вот этого всего. С ростом приложения этим становится сложнее пользоваться и сложнее поддерживать. К тому же новая версия tailwind перестала поддерживать scss “за ненадобностью” 🤷♂️ Короче, переношу стили обратно в scss, ухожу постепенно от tailwind, использую его в основном как набор переменных. Смотрю на Mantine UI
- Zustand и Jotai – оч популярные библиотеки, работающие по аналогии с recoil.js. Их проблема в том, что они сильно завязаны на react life cycle, и твоя бизнес-логика начинает жить в хуках или селекторах. Но бизнес-логика не должна зависеть от UI, и UI не должен диктовать условия, каким образом управлять бизнес-логикой, – такая система становится менее гибкой. Реакт – это просто UI-слой. Как альтернатива, можно поставить computed midleware, чтобы вычислять производные значения, подписавшись на поля в сторе, эдакий аналог "юзэффекта", однако библиотечка оказалась с багами, и пришлось ее локально пропатчить. Другая проблема, что твои слайсы с ростом приложения начинают становиться зависимыми друг от друга, влиять друг на друга, и всему этому не хватает какой-то прозрачности, чистоты и структуры. Команда разработчиков Zustand прямо говорит, что, когда появляются зависимости между слайсами, то иметь god-like стор – это ок. Судьба свела меня с разработчиком awai.js, на базе которого можно довольно легко построить свою декларативную систему стора и сообщений. Хочу попробовать как-нибудь позже.
- Для таймлайна: драг-н-дроп/ресайз событий использовал https://www.npmjs.com/package/interactjs просто потому, что 2 в 1, понятный интерфейс и пример, который был под рукой. В других либах либо демки багованные были, либо не было ресайза, либо еще что-то. Возможно, я просто долбился в глаза.
- Для событий я сделал виртуализацию https://tanstack.com/virtual/latest – т.е. рендерится только то, что попадает во вьюпорт + половина вьюпорта слева и справа, но не меньше 300px.
- Проблемным местом было прикрутить интервальные деревья: я очень прям хотел иметь возможность быстро получать события в заданном диапазоне. Так, например, каждую секунду я смотрю, есть ли пересечения, чтобы пометить те события, которые попали в дельту и оказались "в прошлом". Эти события становятся чуть более прозрачными... 😁 Использовал вот эту библиотечку https://www.npmjs.com/package/@flatten-js/interval-tree обещают логарифмическую скорость поиска, что достаточно быстро. Заодно посмотрел курс по алгоритмам про самобалансирующиеся красно-черные деревья https://www.coursera.org/learn/algorithms-part1-ru/home/module/1 Так вот, проблема в том, что структура интервального дерева мутабельная. Ну т.е. вы не хотите заново пересоздавать все дерево, если вы добавили или удалили событие, правильно? Потому что их может быть тысячи. А реакт любит иммутабельность (сторы, стейты, вот это все). Как быть? Короче, оказывается, есть вот такая херня: https://react.dev/reference/react/useSyncExternalStore, можно сделать что-то вроде простейшего pub/sub – инкрементально увеличивать ревизию при изменениях и оповещать подписчиков, и это как-то более-менее с пердежом и скрипом работает.
С какими трудностями пришлось столкнуться?
Звезды! Короче, звезды сажают перформанс! Оказывается, рендерить ~300 объектов, рандомизированно анимировать их – это, блин, дорого. Изначально у каждой звезды был блюр-фильтр, чтобы создать ощущение глубины, словно некоторые звезды ближе / дальше. Однако это оказалось слишком дорого – прям экран ночью начинал подлагивать. Это не годится.
Я пытался использовать svg и webgl. Возможно, с текущим уровнем ИИ писать шейдеры на webgl будет проще, но спустя полтора месяца экспериментов я вконец задолбался и решил откатиться к более простой и базовой версии звезд.
Ну и, по сути, уровень моих знаний, который, как оказалось, вырос, и недостаток которого сказался на архитектурных решениях, о которых я говорил уже выше. Сейчас я бы многие вещи сделал иначе.
UI все еще остается для меня больным местом. Из-за динамической смены дня и ночи/градиентов нужно подобрать цвета таким образом, чтобы они одинаково хорошо смотрелись на темном и светлом фоне. Это проблема. Чтобы решить ее полностью, мне нужно поработать над "светлой" и "темной" темами, пока что это отложено.
Я не делал stress test, возможно, при работе с тысячами событий в дереве я обнаружу падение перформанса. В теории все должно быть ок, но пока потестить руки не доходили.
Сколько потратили и заработали? Есть идеи, как это можно монетизировать?
Я работал над проектом в течение года в свободное от работы время и не заработал ничего, и не знаю, заработаю ли вообще. :) Я просто хотел сделать что-то крутое и доступное. Приложение бесплатное и хранит данные в браузере. Единственная идея, как можно было бы это монетизировать, – это предложить возможность хранения данных и регистрации/аутентификации, а значит, синхронизации и восстановления состояния между устройствами через бэкенд. Думаю предложить подписочную модель за 2$ в месяц.
Какие планы на будущее?
Мне нужно поработать над SEO. Разделить приложение и посадочную страницу, постараться постучаться в комьюнити и показать аппку. На Реддите меня забанили просто за пост о том, что я такие комьюнити ищу (на самом же Реддите)... Апелляцию уже подал, но все пока очень долго.
Я хочу сделать обучающие скринкасты и оформить лендинг.

Нужно также оформить и завести соцсети, начать их вести.
Я думаю, я расчленю данную статью на куски и буду скармливать ее по кускам там.
В голове есть сценарий и идея для ролика на ютуб. Должно получиться забавно... но когда до этого руки дойдут, я хз.
Все это занимает уйму времени, мне, конечно, хотелось бы больше уделять времени приложению и фичам, я все же разработчик, а все эти SEO и SMM – это не мое.
А еще, работая год над приложением, я понял, что... я перестал играть в D&D... Как-то это контрпродуктивно получается, не находите? Все затевалось для того, чтобы сделать крутые инструменты и пробовать вести игры, а в итоге сама игра стала чем-то совсем далеким. В то же время, если я переключусь на свои D&D игры, то где брать время на проект?..
Нужны ли какие-то советы или помощь Клуба?
Афигеть как нужны! Любая помощь востребована, начиная просто от слов поддержки, заканчивая конкретной помощью по проекту.
Как я и говорил, большая сложность для меня – это маркетинг и SEO.
Буду исследовать эту область, строить семантическое ядро, пилить лендинг, оформлять соцсети.
Любые советы от людей, запускавших свои веб-приложения, будут в кассу. У меня это "первенец". Я двигаюсь исходя из common sense и тех пары часов после работы, что остаются.
Вы пользовались приложением и у вас есть фидбек?
Или, может быть, вы нашли баг?
Может, у вас есть идеи/предложения?
Вы знаете классные уютные D&D сообщества или интересных контент-мейкеров/блогеров/инфлюенсеров, к которым можно постучаться и попросить рассказать о проекте?
Может быть, вы SEO-специалист, маркетолог, SMM-щик или запускали свой патреон?
Круто! Напишите мне об этом тут или на почту dm@dnd-time.com или в телегу @maliyshock
...
Если хочется поддержать финансово и купить мне пиво, то вот мой пейпал – maliyshator+paypal@gmail.com, мне будет приятно, хотя и слов поддержки и так вполне достаточно. :)
Музыка
Музыка была написана одноклубчанином Лешей Козловым, за что ему огромное спасибо!
Вот его сайт, YouTube и SoundCloud.
А пересеклись мы с ним в Вастрик.Музыканты

