Як я збожеволів на ігровій статистиці і врешті-решт створив сайт для її відстеження
Всім привіт! Мене звати Григорій Качуровський, я Full-stack developer. В цьому топіку я розповім, як створював Chronex — сервіс для відстеження ігрової статистики.
Протягом усього мого дитинства у мене було 3 ігрові приставки: PlayStation 1, PlayStation 2 та PlayStation Portable (2000 версія). ПК з’явився значно пізніше, тому тривалий час я задовольнявся своєю «бібліотекою» ігор для старих консолей (і на той час цього мені цілком вистачало).
Потім у 2017 році я придбав власний ПК, який на той час був досить потужним — адже відеокарта NVidia 1060 — це не жарти, а який там був процесор, я вже й не пам’ятаю. І в той момент ігри полилися рікою, одна за одною. Я поглинав безліч ігор, які, по суті, пропустив за все життя, адже ніколи не грав на ПК. Буквально через півроку я замислився: «А може, записувати ігри в блокнот? Було б класно потім, через пару років, гортати його й дивитися, що я пройшов, а що ні». Розкреслив у блокноті табличку з колонками «Назва», «Статус» (типу «пройшов гру» або «закинув»), «Оцінка» (з мене той ще об’єктивний оцінювач) і остання колонка показувала кількість проходжень на випадок повторного проходження гри.
Як багато хто може здогадатися, навіть банальна наявність графи «кількість проходжень» вже ставила під сумнів концепцію звичайного блокнота (адже треба скористатися коректором, щоб стерти старе число й вписати нове), але хто б мені тоді про це сказав...
Взагалі, продумування повноцінної системи з самого-самого початку — не моя сильна сторона, адже всі наступні ітерації такої «бібліотеки» я завжди допрацьовував на ходу.
Загалом, цей блокнот я вів, мабуть, приблизно 2 роки. Потім я вже потроху почав користуватися комп’ютером ширше, а не лише грати в ці ваші ігри. Випробовував різне програмне забезпечення, яке мене цікавило. Так я натрапив на Notion — досить цікавий і зручний блокнот. Там я незабаром почав писати свої міні-конспекти з програмування, а потім якось поглянув на свій фізичний блокнот з іграми, і в голові спалахнула ідея: «Я перенесу свої записи в Notion» (якби я знав, скільки разів я буду ці записи кудись переносити... зараз назвемо це першим перенесенням).
Коротше кажучи, я створив файл, створив у ньому таблицю з потрібними мені стовпцями — і вперед. В принципі, нічого особливого, але це дійсно набагато зручніше, адже якщо якісь дані потрібно змінити — просто перезапиши їх, і все. Ну і вручну з лінійкою не треба таблицю креслити, це теж зручно, знаєте. Цим методом я користувався приблизно рік.
Потім я почав поступово стикатися з тим, що один файл у Notion не справлявся — програма починала лагати під час прокрутки, відкриття тощо (а там було значно менше ніж 200 ігор). Та й загалом Notion якось перестав мені подобатися. І тут я натрапив на богоподібний Obsidian (це лише моя особиста думка, я не ставив його на п’єдестал перед іншими подібними програмами — просто особисто мені він подобається найбільше).
Конспекти я, наскільки пам’ятаю, вже не переносив туди, а переніс лише бібліотеку ігор (друге перенесення). Тут, звісно, було трохи складніше, адже таблицю так просто не створиш — але я швидко знайшов потрібний плагін, який створював таблицю, додавав рядки/стовпці одним натисканням кнопки, і, в принципі, все пішло нормально.
Знову той самий набір стовпців, і все так само — бібліотека ігор.
Згодом я почав заглиблюватися в систему Obsidian із посиланнями файлів один на одного і вирішив, що назви ігор можна робити такими посиланнями — щоб згодом створювати файл для кожної гри й щось там писати. І тоді як елемент моєї бібліотеки у мене з’являвся б гарний графік зв’язків, на який можна було б дивитися.
Ось тут я не знаю, що сталося в моєму мозку, але ще через рік я вирішив знову перенести свою бібліотеку... цього разу в Google Таблиці (третє перенесення). Озираючись назад, взагалі не розумію, навіщо я це зробив. Може, сподобалося, що цю саму таблицю можна дуже зручно переглядати з телефону... але щоб через це прямо перейти з Obsidian... дивна історія, сам розумію. При цьому я пам’ятаю, що великий файл із величезною таблицею в Obsidian теж починав трохи лагати (хтозна як, але, мабуть, комп’ютер, який на початку свого життя був побитий вірусами через мою недосвідченість, починав втомлюватися).
У цій ітерації я вперше додав нові стовпці! Це було поле «Першого запуску» та поле з галочкою «Платина». Довелося заглиблюватися в історію покупок Steam, щоб виявити приблизні дати перших запусків кожної гри, а щодо інших ігор — згадувати приблизні дати з пам’яті.
І що ви думаєте? Так, через рік відбулося четверте перенесення. Куди? Ніколи не вгадаєте — знову в Obsidian. І так, саме перенесення, адже стару версію я повністю видалив, а нову під час створення допрацював набагато, я маю на увазі НАБАГАТО краще (і в функціональному плані, і у візуальному). Загалом, як виглядала ця ітерація, адже вона, на мій погляд, поки що найфункціональніша й найцікавіша:
Спочатку всі ігри — це окремі файли під кожну гру з метаданими всередині. Уся інформація про гру, яку я збирав, була в метаданих, це зручно для подальшого відображення всієї бібліотеки. Щоб створити таку кількість файлів і не збожеволіти — знайшов плагін
Media DB, який за назвою знаходив різні ігри й заповнював частину потрібних мені полів сам (наприклад, було зручно, що він сам додавав дату релізу та розробників, хоча насправді ці дані мені особливо цікавими не були). Найважливішою частиною та нововведенням стають теги, а саме теги з модифікаторами.
Які теги були в наборі:
planned— означає, що гра у мене в планах на майбутнє;playing— означає, що гру я проходжу саме зараз;completed— означає, що я завершив гру;dropped— означає, що я закинув гру;platinum— означає, що я отримав у грі платину.
Але я сказав, що теги містили модифікатори. Це означає, що в кінці кожного тегу додавалася примітка /{рік}, тобто, наприклад, completed/2025 — це означає, що я пройшов гру у 2025 році. Що це нам дає? Дуже гнучке й точне відображення в майбутньому, коли ми дійдемо до відображення всієї цієї історії. З такими модифікаторами ми можемо відстежувати точнішу статистику за роками.
Що я маю на увазі. Уявімо, що ви почали проходити гру в грудні умовного 2024 року, наприклад у playing/2024, тому що рік ви фактично закінчили зі статусом «у процесі проходження» цієї гри, але після проходження у гри з’являється тег completed/2025. У підсумку, коли у нас з’явиться відображення всієї бібліотеки, ми зможемо точніше відстежувати статуси ігор, прив’язані до років.
Звісно, якщо ви почали гру в 2025 році і закінчили її в 2025 році — то тег playing/2025 нам не потрібен, і він повністю видаляється з гри. Єдиний тег, до якого я не прив’язував модифікатор року, — це тег planned. Звісно, можна було б так робити, умовно «я планую пройти цю гру саме в такому-то році», але для мене це сумнівне планування, адже нерідко в процесі можуть траплятися набагато цікавіші ігри, і в підсумку цей «план на рік» може взагалі навіть наполовину не виконатися.
Також важливо розуміти, що це зручно в довгостроковій перспективі під час повторного проходження — я часто кидав гру під час повторного проходження, хоча колись її проходив, і тоді у нас виходить така картина: у гри відразу може бути такий набір тегів:
playing/2022completed/2023dropped/2025platinum/2026
Тобто вперше я запустив її у 2022 році, не встиг пройти, дограв у 2023. У 2025 почав перегравати, але кинув. А у 2026 вирішив ще раз переграти, так і отримав платиновий трофей. І всі ці теги враховуються під час відображення бібліотеки з фільтрацією за роками.
Далі до справи береться плагін Dataview. Він у нас відповідає за гарне відображення всієї бібліотеки. Вирішив, що блоки з візуалізацією я робитиму у файлах канви (десь підглянув щось подібне, і мені сподобалося). Спочатку створив файл для загальної статистики за весь час, написав потрібний код, розпарсив усі дані — і вуаля, гарна табличка, яка була ще й інтерактивною!

Поки що не звертайте уваги на нові для нас поля — це скріншот поточної версії, найновішої, яка була доопрацьована.
Отже, тут у нас є фільтри (верхні 3 картки, на які можна натискати, і які фільтрують список), індикатор прогресу (який, по суті, дублює частину функціоналу карток, але про це не будемо) — на нього теж можна натискати, він теж фільтрує. Фільтр за платформами теж натискний. Картки ігор самі по собі гарненькі, з усією необхідною інформацією, щоб не довелося переходити до файлу гри.
Тепер про нові поля, які ще не були описані... через більше ніж півроку після впровадження всієї цієї системи сталося... ні, не нове перенесення xD. Сталося вдосконалення/доповнення. Спочатку я вирішив, що потрібно додати не тільки дату першого запуску, а й дату першого проходження (для мене це саме перший раз коли я побачи титри, а не проходження гри на всі 100%). Адже у нас є тег, який показуватиме, що гру пройдено в той чи інший рік, а коли саме відбулося перше проходження — інформації немає. Потім ще через якийсь час я вирішив, що було б класно додати й дату «останнього запуску», адже теж цікаво, коли я востаннє запускав конкретну гру, а не просто знати останній рік запуску за тегами. Це, до речі, також означало, що потрібно було переглянути всі ігри заново вручну, додати нове поле та згадати, в які дати я востаннє запускав ту чи іншу гру (десь я просто залишав рік без дати). Добре що у Steam можна побачити дату останнього запуску.
Потім, ще через деякий час, я почув фразу, що «відеоігри — це найдешевший вид розваг, якщо брати до уваги вартість за годину». І справді, якщо так задуматися. Якщо ти купив гру за 1000, пройшов її вздовж і впоперек, витратив на це 100 годин, то виходить, що ціна за витрачену годину гри становить 10 (валюта зараз умовна). У мене так із грою Red Dead Redemption 2 — я купив гру двічі, витративши на неї загалом 1400 грн. Але я її пройшов вже 3 рази, і зараз проходжу вчетверте (ні разу не проходив на 100%). Награв я понад 300 годин (точний час дізнатися складно, тому що одного разу у PlayStation щось сталося з серверами, і години в іграх десь зменшилися, а десь збільшилися). Виходить, що ціна за годину становить 4,66 грн. Зрозуміло, що тут є фактор вражень, і що, купивши одну коротку гру та пройшовши її за 10 годин, можна отримати такий спектр емоцій, якого ти не отримаєш під час проходження якоїсь RPG на 100+ годин. Але ціну вражень, на жаль, неможливо підрахувати.
Отож, мені тепер потрібно було додати одразу два поля до всіх ігор — суму, витрачену на гру, та кількість відіграних годин. Знову довелося переглядати всі файли й вручну додавати все це (але, чесно кажучи, мені це приносило більше задоволення, ніж болю).
Ось ви можете ще раз поглянути на зображення і зрозуміти, що це за картки та значки на зображенні кожної гри.
А ось як виглядає така сама статистика, але вже в іншому файлі, конкретно за 2025 рік:

Тут не відбувається розрахунку вартості за годину, адже ціна та час, проведений у грі, не прив’язані до конкретного року. І, як ви можете помітити з цих перших двох рядків ігор, у них у всіх є тег play, хоча кожна з цих ігор має дату закінчення. А це тому, що всі ці ігри я ПРОЙШОВ саме у 2026 році, адже 2025 рік закінчився зі статусом playing для цих ігор. І переглядаючи статистику за минулі роки, буде видно саме фінальний статус гри за конкретний рік.
Начебто все пояснив, сподіваюся, що зрозуміло.
Тепер..... П’ЯТЕ перенесення. Це перенесення сталося ЧЕРЕЗ останнє нововведення в базі Obsidian — додавання кількості годин.
У чому суть. Коли я додав відстеження кількості годин у грі, мої ігрові сесії стали такими: я запускаю гру в Steam, граю пару годин, закриваю — бачу час у самому Steam, змінюю його у своїй базі даних. Загалом це не дуже клопітно й складно... Але запускаючи гру не через Steam, я вмикав таймер на телефоні, а потім стикався з таким: у базі вже записано, що я награв 1 годину 47 хвилин, а зараз я награв 1 годину 24 хвилини. І ти сидиш, рахуєш, скільки це загалом. І так після кожної сесії. Я протримався так близько місяця. Навіть придумав лайфхак — додавав сторонні ігри в Steam і запускав їх через нього, таким чином він мені показував, скільки годин награно. Але все одно це не вершина зручності.
Коротше кажучи, з’явилася ідея власного сайту з вбудованим таймером!
Стек було підібрано досить швидко — я багато працював із Next.js, тому одразу вирішив обрати саме його. Також для візуальних елементів обрав ChakraUI, оскільки мені сподобалася концепція налаштування власних стильових рецептів для їхніх компонентів. А щодо зберігання даних — було обрано принцип Local-first: дані зберігаються у кожного користувача в його ж браузері за допомогою Dexie. Жодної загальної бази даних, жодного сервера. Хоститься лише візуальна частина.
Звісно, для мультиплатформи було додано можливість прив’язати свою пошту та створювати резервні копії на власний Google Диск (знову ж таки — навіть у цьому випадку дані все одно зберігаються у користувача, а не на якомусь сервері для резервних копій). Згодом я додав можливість експортувати JSON-файл усієї своєї бази, якщо ви не хочете прив’язувати пошту.
Початкові концепт-арти візуалізації я показувати не буду, адже вони виглядають майже так само, як моя остання версія в Obsidian. Потім до справи долучилася моя дівчина, яка на той момент навчалася на UI/UX-дизайнерку. Побачивши те, що я зробив, вона, м’яко кажучи, була вражена і взялася розробляти для мене дизайн сайту, який мені дуже сподобався вже з перших концептів.
Через два тижні дизайн був повністю готовий. Паралельно я покроково застосовував його весь цей час. По суті, до кінця розробки дизайну він уже був змінений і на самому сайті. І ось я випустив версію 1.0, якою почав користуватися в реальних умовах з 24 червня 2026 року. З того дня я почав переносити всі ігри з бази, АЛЕ не звичайним чином, а з невеликим спрощенням (про це трохи пізніше).
Отже, головна сторінка проєкту — дашборд:

Як на мене, тут є все, що душа забажає (але не виключаю, що через якийсь час я захочу додати сюди якісь нові дані). І це ще не вся сторінка:

Ось тепер точно вся.
В принципі, з дашбордом ми закінчили, переходимо до бібліотеки:

З точки зору функціональності тут мало чого нового. Просто тепер це виглядає красиво завдяки чудовому дизайну. Можна також переглядати статистику за роками. Праворуч вгорі є вибір року (зараз він встановлений у положенні «за весь час»).
Перейдемо до сторінки гри:

Отже, що ми тут маємо. Найважливіша функція — таймер, який автоматично під час зупинки:
- додає час сесії до загальної кількості годин у грі;
- може створювати нову річну активність (якщо ви цього року ще не грали в цю гру, під час першого запуску таймер створить активність для цього року і відразу встановить статус «Playing» для цього року);
- автоматично змінює дату останнього запуску.
Тобто коли ви вже користуєтеся додатком із створеними іграми, вам потрібно лише використовувати таймер. Звісно, коли ви пройдете гру або кинете її, доведеться зайти в налаштування гри й змінити статус на відповідний.
До речі, щодо налаштувань гри (це водночас і сторінка створення нової гри):

Отже, тут у нас є різні поля, більшість із яких є очевидними. Найцікавіший блок знизу — річні активності. Це така собі заміна тегів із Obsidian.
Ви додаєте річну активність, вказуєте статус, обираєте платформу (можна на один рік вказати навіть усі платформи, які надає сайт, якщо ви, скажімо, за рік пограли в гру на кількох із них). І можна перемкнути перемикач «платини». Підкреслю, що у нас є два поля «остання дата запуску»: перше — основне, друге — прив’язується до кожної річної активності (щоб побачити точну останню дату запуску КОЖНОГО року). Обидві ці дати оновлюються автоматично після зупинки таймера на попередній сторінці.
Помітили цікаве поле, про яке я ще нічого не розповідав? Так, ось воно, прямо під полем для посилання на зображення — Steam App ID. Навіщо воно? Якщо створюєте гру вручну — можете пропустити, його заповнювати не обов’язково. Пам’ятаєте, я писав, що перенесення бази на сайт було спрощеним? Ось саме це — імпорт власної бібліотеки Steam. Та не звичайний імпорт, а розумний. Зустрічайте наступну сторінку:

І от. Ви вводите свій ID профілю та свій Steam API. Це потрібно для того, щоб вам не довелося відкривати свою бібліотеку, якщо вона у вас прихована від сторонніх очей. А також тому, що якби я ввів просто свій API-ключ, то через великий наплив користувачів він міг би «задихнутися». Після натискання на кнопку ми бачимо список усієї вашої бібліотеки. Важливо, що після натискання на «Fetch My Library» нічого в базу ще не записалося. Ви просто отримали список на цій же сторінці, де вже можете переглянути, що вам потрібно додавати, а що ні.
Наприклад, у багатьох у Steam може бути якесь неігрове програмне забезпечення. У мене це, наприклад, Soundbar, OBS, Wallpaper Engine тощо. І я не хочу бачити це програмне забезпечення під час кожного нового імпорту бібліотеки. У куті кожної картки є перекреслена кнопка, яка додає програму до блок-лісту (зліва в додатковій навігаційній панелі вона розташована одразу після цієї сторінки). Додавши програму до списку заблокованих, вона більше ніколи не відображатиметься на цій сторінці, доки ви не зайдете до блок-лісту і не видалите її звідти.
Крім того, кожній грі ви можете одразу присвоїти статус або просто видалити якусь гру, не додаючи її до списку заблокованих.
А ще імпорт зі Steam також автоматично встановлює для гри дату останнього запуску. І для цієї дати створює річну активність, до якої прив’язується статус гри, який ви оберете під час імпорту.
А тепер дивіться:

Припустимо, що локально на сайті у вас одні дані щодо годин у грі, а в Steam — інші. Під час наступного імпорту конфлікт даних буде виявлено, і сайт запропонує вам два варіанти: замінити кількість годин або об’єднати їх (або ж можна пропустити). Звісно, для дашборду такий варіант не вносить жодних даних. Дашборд підраховує відіграні години на місяць лише тоді, коли ви користуєтеся таймером.
Показувати інші сторінки немає особливого сенсу: на останній вкладці налаштувань є функція прив’язки облікового запису Google, а також функція створення резервних копій або завантаження резервних копій.
Також є зручна функція. Якщо ви користуєтеся сайтом з телефону та ПК (і там, і там один обліковий запис Google), і, припустимо, ви відстежили час у кількох іграх через телефон — зайшли та зробили резервну копію. Потім відкриваєте сайт на ПК, і вам з’являється модальне віконце з повідомленням: «На твоєму диску є свіжіша резервна копія — хочеш оновити дані?». Це зроблено для того, щоб ви не забули синхронізувати свіжіші дані між пристроями.
Дилема щодо загальної бази даних
Вже кілька друзів і знайомих сказали мені щось на кшталт «Було б зручніше, якби можна було зареєструватися й не морочитися з ручними резервними копіями, а просто відкрити сайт — і в тебе завжди були б актуальні дані». Важко не погодитися.
До того ж загальна база даних на сервері відкрила б можливість для низки інших цікавих речей. Наприклад, можна було б ділитися посиланням на свій дашборд з друзями замість звичайних скріншотів.
Але на противагу цьому стоїть моє початкове розуміння та філософія проєкту. По-перше, мені подобається, що дані зберігаються виключно у користувача, і я не маю до них жодного доступу. По-друге — базу даних потрібно десь розміщувати, а це коштує грошей. І ось уявіть: ви користуєтеся сайтом кілька років, збираєте статистику, все чудово — а потім у моєму житті щось трапляється, і я вирішую, що більше не можу оплачувати загальну базу даних. Все, ваша статистика зникає (звичайно, варіант з експортом JSON-файлу всієї бази залишається, і її можна буде отримати — але що ви з ним робитимете? Ті, хто розбирається, можливо, захочуть розпарсити свій JSON, а ті, хто не розбирається — просто втратять усе).
Плани на майбутнє
Поки що я активно працюю над проєктом, оскільки сам щодня ним користуюся. І на власному досвіді помічаю, чого мені бракує, а що можна покращити. Іноді раджуся з ШІ щодо того, «а що ще цікавого можна додати до такого проєкту?».
Наприклад, нещодавно вдосконалив функцію зі «стріком». Раніше це був просто гарний елемент, який показував лічильник днів, коли ви поспіль грали щодня мінімум 30 хвилин (я встановив мінімум у 30 хвилин, щоб не можна було продовжити серію, просто увімкнувши та вимкнувши таймер на декілька секунд — це було б нечесно). Тепер же це більш інформативний елемент, який при наведенні курсора показує трохи більше інформації:

Тепер тут є календар, який підсвічує «стрікові» дні. А внизу показує інформацію про те, скільки потрібно грати щодня, щоб стрик зарахувався (бо я зрозумів, що цієї інформації більше ніде немає, і користувач не розумів, як це працює).
А також додав «механіку» заморожених днів: кожні 14 днів стрику вам надається 1 день заморозки. Ці дні заморозки автоматично витрачаються, якщо ви пропускаєте якийсь день. Умовно візьмемо приклад зі скріншоту: якщо я пропущу 14 липня, на нього буде використано єдиний день замороження, а погравши
Зручна фішка на випадок, якщо пропустите день. Хоча, можливо, я зменшу кількість днів серії для отримання дня заморожування до 7 днів, ще не вирішив.
У майбутньому планую покращити вигляд сайту на маленьких екранах (коли дизайн буде готовий до цього), а також, можливо, створити окремий лендінг, який описуватиме весь проєкт. Для цього теж потрібен дизайн, бо на власний погляд я роблю не дуже гарно, а розчаровувати свою дівчину не хочеться.
Проєкт повністю діючий, можете протестувати його за посиланням.
Всім дякую за увагу!
2 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівКласне рішення. Зараз я користуюсь Infinite Backlog. Дещо подобається, дещо ні. Але ваш варіант набагато кращий. Можливо колись дійдуть руки перенести все у ваш сервіс
Дуже дякую за ваш коментар! Буду радий якщо проєкт комусь буде потрібен😁