Розробка

Трагікомедія в NaN актах: як розробники зробили гру на JS і випустили її в Steam

“Ач дивина”, — скажете ви, — “топ-100 вашої гри немає, так що нещитово”. Теж правда. Натомість за рік розробки Protolife ми піднакопичили якийсь досвід, яким можемо поділитися з потенційними майбутніми игроделами. Ветерани індустрії, боюся, нічого цікавого для себе не знайдуть. Але, може бути, хоч повеселіться від душі.

Що за гра? І хто “ми”?

Ми — це команда з трьох чоловік (GRaAL, A333, icxon), волею доль названа Volcanic Giraffe без будь-якого умислу. Працювали довгий час разом, кілька разів втрьох брали участь у Ludum Dare (змагання з написання ігор за вихідні), і одного разу вирішили довести до релізу одну з наших виробів під назвою Protolife.

Якщо коротко: це незвичайна tower defense, де треба бігати героєм-курсором і вибудовувати оборону з блоків проти постійно зростаючої червоної біомаси.

З коментарів до чернеткою статті:
icxon: треба хоч трохи про геймплей спочатку написати. А то якісь скріншоти на яких хрін зрозумій що відбувається

Якщо розписати детальніше, то що є в грі:

  1. Є набір рівнів, на кожному рівні є наша база і наш аватар — робот-будівельник.
  2. Частина рівня заповнена червоної зростаючої біомасою, яка вивергає з себе тонни мобів.
  3. Моби, ясно справа, біжать до бази і намагаються її знищити. А ми будуємо оборону з веж і зробити це не даємо.

“Але що ж тут незвичайного?” — запитаєте ви. А основні відмінності від більшості tower defense такі:

  1. Роботом-будівельником ми управляємо безпосередньо з клавіш, тобто треба вручну носитися по рівню і встигати все будувати/лагодити.
  2. Все що вміє робот — це будувати і демонтувати сині блоки. Один такий блок не робить нічого корисного, але кілька блоків, викладені за певним шаблоном, перетворюються на корисне будова. Приклади шаблонів:

  • Шаблон 1: проста гармата
  • Шаблон 2: стіна
  • Шаблон 3: АА-гармата. тут доводиться задіяти ще й жовті кристали, які видобуваються вже поскладніше
  • Шаблон 4: кулемет

Так і проходить гра: бігаємо, будуємо, отримуємо відповідь, спішно чиним. Прямо візуалізація процесу розробки гри виходить.

Але такий гра вийшла не відразу. У далекому квітні 2017 року, на Ludum Dare 38 вона виглядала так:

Якщо не звертати уваги на різницю в графіку, то змінилося начебто не так вже й багато. Вже на LD-версії був курсор-будівельник, спорудження веж з блоків і протистояння з червоною біомасою. Це ми залишили, тому що це працювало і це подобалося людям.

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

Ось про такі рішення я й хотів би розповісти.

Відразу обмовлюся — статей, ймовірно, буде кілька, т. к. матеріалу, якщо почати копати, вилазить багато. У цій статті ви знайдете невеликий опитування — які теми вам були б цікаві. Ну і посилання на саму гру теж.

Нудна історія створення.

Не думаю, що реально комусь цікаво як гра взагалі придумалася, тому приберу під спойлер.
Заходять якось Конвей, Метісон і Петрі в бар…А бармен і каже: undefined is not a function.

Напередодні LD38 з’ясувалося жахливе: наш колега і єдиний художник A333 буде весь LD летіти над атлантикою, і допомогти нам не зможе. Тому треба було рештою силами зробити гру максимально нетребовательную до графіку.

Темою до речі було “Small world”. А далі під час брейншторму все пішло приблизно так:

  • Художника немає — графіка проста, примітивна
  • Small world — невеликі розміри, чи може щось мікроскопічне, типу всяких мікробів
  • Мікроби — це типу мікро-життя
  • А life — це такий клітинний автомат. Ну ось, клітини у всіх сенсах. Давайте гравець буде воювати клітинним автоматом проти іншого клітинного автомата.
  • Потыкали Conway Life — не годиться. Гравець, який погано знайомий з правилами автомата, швидше за все побудує щось, що саме собою знищиться. Дуже складно контролювати. Давайте за правилами life буде тільки супротивник, а ми будемо будувати впорядковані структури.
  • … але тоді супротивник буде то і справа самознищуватися. Гаразд, підправимо для нього правила. Нехай він тільки зростає, а знищувати його треба вже нам.
  • Так, у нас вже є: червоні клітини противника, які тільки ростуть, і сині наші, які ми будуємо самі за заданими шаблонами. Ми будуватимемо вежі і стіни, тобто Виходить такий tower defense. Для різноманітності не вистачає ще рухомих ворогів (мобів).
  • Напередодні я перечитував в черговий раз “Я — легенда” Метісона. Так головний герой ночами тримає облогу від упирів, а вдень, коли вампіри неактивні, відновлює оборону, а так само розширює сферу впливу. Це звучало як непоганий геймплейний елемент, так що в грі з’явилися фази дня і ночі. Вночі ворожі клітини ділилися і набигали будиночки наповзали моби, вдень — все було тихо, можна було контратакувати.
  • Обзываем наші кольорові пікселі “мікроорганізмами” і засовуємо в круглу арену — чашку Петрі.
  • Беремо наш улюблений движок Phaser.js…
  • … і 31 місце у нас в кишені


Ось така історія, не дуже цікава, як я і попереджав.

“Але Олексій, якого біса ти її тоді написав?”. Резонне питання. Справа в тому, що чи не перший коментар до гри звучав як “лол, ви все здерли з Creeper World”. Ознайомившись з грою багато пізніше власне LD я розумію, чому люди так думають. Але все ще трохи прикро.

Якщо раптом вам теж стало прикро, що ви витратили час на це ниття, то в якості розради напишіть мені в лічку кодове слово “нудьга”, і я пришлю вам один з 10 ключів, якщо до того часу вони не закінчаться.

Вибір гри для повноцінної реалізації

Як це часто буває, одного разу нам просто подумалося всім втрьох — а чи не пора вже спробувати зробити повноцінну гру. Часто на цьому етапі береться якийсь концепт з голови, а-ля “гра мрії”, і з ним проводиться робота. А далі як пощастить вгадати з бажаннями аудиторії.

Наше завдання трохи спрощується за рахунок наявності невеликого “портфоліо” на Ludum Dare. Ми бачили, як люди реагують на різні ігри, і могли порівнювати. Protolife мав найбільший відгук, його хвалили за оригінальність і за цікавий геймплей — це при нульовому рівні графіки і всього 4х рівнях!

Крім того, прийняти рішення нам допоміг itch.io, на якому ми публікували наші вироби. Як виявилось, є люди, які ходять на itch.io пограти в веб-ігри. Деякі з них люблять жанр tower defense, і 5-10 чоловік заходили (і до цих пір заходять!) пограти в той старий Protolife кожен день.
Статистика відвідувань до релізу гри в Steam
Можна сказати, це було наше перше маркетингове дослідження. Ми подумали і вирішили, що “нестандартний tower defense” цілком може вистрілити. Tower defense-ів не так багато, багато з них схожі, як дві краплі води, і виділитися серед них ми цілком можемо.

Забігаючи вперед, можу сказати, що тактика себе виправдала.

Непроханий Порада №1: Краще не дійте наосліп. Ідеальна у вас в голові “гра мрії” може виявитися нікому не потрібним. Якщо вже не брати участь у всяких джемах, завжди є сенс накидати геймплейний(!) прототип і потестувати його на себе, друзів і знайомих.

Контрсовет: Якщо вас не лякає, що в “гру мрії” буде грати півтора людини, то яке кому діло? Робіть! Може і пощастити.

Движок: Не найкраще рішення

Версію на Ludum Dare ми робили на движку Phaser.js. Ми непогано знаємо його, непогано знаємо javascript, веб-ігри отримують більше фідбек, він досить зручний і простий у вивченні — казка, а не движок.

І перед нами постало важливе питання: міняти движок або залишити все як є?

Питання було складне. Жодного іншого движка ніхто з нас не знав і не вивчав на той момент. Витрачати час на вивчення — штука хороша, але так можна було і весь ентузіазм розгубити. І потім — що взяти? Javascript — єдина мова, яку знав кожен з нас, брати C++/Java/C# движок — значить вмить позбутися половини розробників. Так і движки рівня Unity на той момент здавалися занадто громіздкими для “простий 2D гри”.

І потім: от же є вже гра. Залишилося оновити графен, доробити рівнів — і все. Роботи на пару місяців. А тут ще вивчати, переписувати…

Загалом, ми вирішили залишитися на Phaser.js. Ще гірше — ми вирішили залишитися на тієї ж кодової базі, тобто будувати гру поверх прототипу Ludum Dare.

З коментарів до статті чернеткою
a333: Шкода, у мене не залишилося тієї картинки з милицею замість Ейфелевої Вежі”

Непроханий порада №2: ніколи так не робіть! Особливо це стосується перевикористання прототипу. Код на джемах завжди пишеться швидко і брудно, без врахування майбутнього розвитку. Там милицю, сям милицю — і ось ви розумієте, що пишете відразу legacy-код, і моментально починаєте від нього страждати. Прототипи треба уважно читати, а потім спалювати в /dev/null і писати заново, тільки вже набіло і начисто.

Контрсовет: враховуйте, однак, особливості психології. Буває, що зволікання у пару тижнів вистачить на те, щоб “охолонути”. Краще зробити гру з поганою архітектурою, ніж не зробити взагалі.

З коментарів до чернеткою статті:
A333: я сильно схиляюся до цього варіанту. Наскільки я знаю, саме так і робиться велика частина ігор, тому що час і запал відіграють ключову роль. Якщо у вас є можливість переписати все начисто на новому движку — добре, але так буває далеко не завжди. Не бійтеся писати швидко і брудно, викочувати ранні прототипи, копіювати і вставляти шматки коду, і випускати забаго… *звуки ударів і приглушені стогони*

Власне, в чому проблема саме з Phaser.js

  • Движок, як можна здогадатися, вебовский. Знаєте, як релізувати веб-гру у Steam? Правильно, видавати її разом з Хромом, використовуючи nw.js або Electron. Думаю, мінуси такого підходу пояснювати не треба.
  • Продуктивність javascript звичайно вельми непогана, але нативний код виконувався б швидше, і можна було б не економити на сірниках.
  • Дуже слабкий контроль за рендером. Phaser сам все рендерить, і робить це в цілому непогано, але іноді хочеться влізти в процес або дорендерить щось на webGL своїми силами. На жаль, єдине що дозволяє Phaser — застосувати fragment shader до екрану цілком або якимось окремим спрайтам на екрані, причому теж у міру. Працювати з vertex shader він не дозволяє зовсім (та й взагалі працювати з vertices), а багато рішень в грі з ними були б куди простіше.
  • Проблеми з великим числом спрайтів (активних об’єктів) на екрані. Причому “велике число” — це пара тисяч, а не мільйони. І “активним об’єктом” буде навіть лежачий камінь без анімацій. На кожен тик Phaser буде проходити по всіх об’єктах і робити свою особливу фазеровскую магію, отжирая час у ігровий логіки.

Багато з цих проблем ми вирішували по ходу справи, деякі так і не вирішили до кінця. Складно судити без досвіду роботи з іншими движками, але мені здається, що будь-який інший движок дозволив би обробляти тисячі ігрових об’єктів без пекельних хитрощів. Втім, я можу й помилятися.

Війна стилів

Ви ж ще пригадуєте “оригінальний дизайн” вихідної версії гри?

Безумовно він має свій шарм, але для серйозного продукту ніяк не підходив. Так і наш художник якраз повернувся з відрядження, що йому, без діла сидіти?
Він і не сидів, зробивши кілька начерків можливого дизайну. Історія зберегла два стільці стилю, між якими треба було зробити вибір.

Кандидат 1: мінімалістичний дизайн. По суті — ті ж “пікселі”, тільки симпатичніше. Все яскраве, контрастне. Виглядає пристойно, але, скажімо так, несолідно. У тому сенсі, що виглядає так гра добре буде виглядати на мобілках або ВКонтакте десь між тетрісом і арканоидом. Зате швидко і дешево.

Кандидат 2: реалістичний, фантастичний, суворий. Тут можна вже побачити якусь історію, контекст. Несподівано добре працює контраст не лише кольорів, а й форм — наші будівлі більш впорядковані і квадратні, ворожі будови — круглі, неправильної форми.

Не можна сказати, що ми дуже довго вагалися. Ми уявили собі, в який з “скріншотів” нам хотілося б пограти більше, і у першого кандидата просто не було шансів вижити. Ми поняття не мали що у нас буде за сюжет, де все це відбувається, що відбувається — але вибрали варіант два.

Звичайно, ні з чим не порівняти, але мені здається, що це був вдалий варіант. Так, перший варіант ми доробили швидше, він був би менш вимогливий до продуктивності і графіку, і напевно його навіть вдалося б перенести на мобілки.

Але ми хотіли грати в другий варіант.

Так мікроорганізми і чашка Петрі змінилися на далеку планету, роботів, і таємничий інопланетний організм.

Непроханий банальна порада №3: робіть те, що хочете грати самі.

Контрсовет: якщо у вас 3 іпотеки і немає іншої роботи, вас ніхто не буде звинувачувати за те, що ви зробите щось добре продається, але від чого хочеться помити очі і руки з милом.

Скрадлива біомаса, що крадеться черв’як

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

До речі, червона маса, як і майже все в грі, десь там всередині ігровий логіки укладається у сітку так само, як і блоки. Але виглядає вона при цьому хаотично, так, як ніби вона відв’язатися від сітки.

У мене збереглася демка, де я отлаживал зовнішній вид біомаси. Там все влаштовано таким чином: береться умовний “квадрат” і заповнюється колами різного розміру як можна більш щільно. Нижче — приклад такого заповнення. У самій грі заповнення більш щільне (і тому гірше читається).

Між колами є лінії зв’язку. Якщо біомасі треба вирости в якісь клітини, то підбираються відповідні кола, що перетинаються з потрібними клітинами, і зростання відбувається у них по лініях.

Ось як це виглядає в русі:

З коментарів до чернеткою статті:
icxon: вперше бачу цю демку, лол
У самій грі все працює точно так само, хіба що всі розміри, кольори і швидкості підігнані нашим художником, щоб виглядало трохи краще.

Ой, я здається забув розповісти звідки взялося саме таке рішення, як проходили етапи обговорення, відкидання кандидатів…

А ні, не забув. Їх не було. Спочатку ми планували робити все як у версії LD, тобто зростання квадратиками. Просто якось ввечері мені стало нудно, і я накидав цю демку, щоб потренуватися. А хлопцям зайшло. Так і зробили.

Непроханий Порада №4: плани-планами, але не бійтеся пробувати щось нове. Інтуїція може вам підказати якийсь вдале рішення.

Контрсовет: якщо у вас вже призначена дата релізу і підписаний контракт з видавцем — можливо, експериментувати не варто.

Анімація біомаси

На гіфці вище ви могли помітити idle-анімацію біомаси — навіть у спокої вона посилено “дихає”, червоні бутони ніби розкриваються і назад закриваються. В ідеальному світі, це були б дбайливо намальовані художником спрайт з анімацією, які розставлені по тій схемі з колами. В реальності ж це були б тисячі ігрових об’єктів, з якими Phaser.js просто не впорався б. Причому це вже перевірений факт — у версії з Ludum Dare я вже стикався з пекельними гальмами, коли біомаса заповнювала хоча б половину карти, адже там навіть не було ніякої idle анімації.

На допомогу приходить шейдер, який крутиться там собі на GPU і не займає процесор. Ще краще, якщо цей шейдер буде реально простий — він тоді і відеокарту не буде сильно навантажувати.

Шейдеру треба якось розповісти про те, що і як йому малювати. Які є способи передачі інформації шейдеру:

  • Хардкод в самому коді шейдера. У нашому випадку не підходить, але взагалі іноді такий варіант теж є сенс розглядати.
  • Через uniform змінні (це змінні, однакові для будь-якого пікселя зображення)
  • Через varying змінні (змінні, які інтерполюються між двома вершинами)
  • Через текстури (кодуючи кольором якісь значення)

Метод 3 нам міг би стати в нагоді, але у випадку з Phaser.js він нам недоступний. Через uniform багато не передаси (скажімо, масив всіх кружечків з їх радіусами в uniform-и не влізе — там є обмеження). Залишається текстура.

Трюк ось у чому: я спочатку малюю один стан (скажімо, закриті бутони) синім кольором:

Потім другий стан (відкриті бутони) — червоним:

Якщо їх скласти, то виходить фіолетове місиво:

Шейдер ж бачить текстуру, бачить поточний час, і з певним періодом показує нам “синє” стан, то “червоне”, плавно перетікаючи між ними. Ну і само собою застосовуючи потрібну палітру кольорів. Виходить ось так:

З коментарів до чернеткою статті:
a333: Ах ось як ця б***я е***а працює
icxon: це класно, тому що я все ще не зрозумів як вона працює

Те ж саме, але на прикладі прямокутників:

Текстура:

Підсумкова анімація:

Текстура оновлюється по мірі зростання/руйнування, в решту часу працює gpu-only анімація.

Турбота про гравця

У процесі розробки ми намагалися не забувати про гравців, які бачать нашу гру вперше. Це не так просто, як здається — через деякий час око замилюється, і починаєш вважати очевидним все, що тобі, як розробнику, вже відомо.

Ми це розуміли, і тому перший бета-тест був проведений аж за 8 місяців до релізу — як тільки у нас було готове перші 10 рівнів і 40-50% від контенту. Той бета-тест дав відмінний фідбек по дизайну рівнів, про який я розповім як-небудь наступного разу. Одночасно ми дізналися, що і самі непогано передбачили деякі моменти.

Ситуація: є база гравця, яку треба захищати — її руйнування призводить до провалу місії. Гравець не стежить за базою постійно — він стежить за своїм аватаром-роботом. Причому стежить постійно — темп гри досить швидкий, стояти і споглядати часу особливо немає. У результаті що ми, що тестери, іноді не помічали зайшов у тил ворога, бомбящего базу.

Рішення: по-перше, покажемо шкоди по базі концентричними колами, що йдуть через пів-карти.

По-друге, якщо база значно пошкоджена, то вона самостійно завдає удар, змітає ворогів, який добре помітний і озвучений. Це привертає увагу гравця до проблеми, і дає час вжити заходів.

Ситуація: як у типовому Tower defense, вороги йдуть протоптаним маршрутом. Однак цей маршрут не завжди вгадується. А розуміти маршрут — дуже важливо для успішної оборони: деякі вежі стріляють дуже недалеко, а надто близько поставлена башта буде знесена наступною хвилею.

Рішення: малюємо кров після вбивства ворогів. Через деякий час на землі з’являється чіткий слід, на який можна орієнтуватися:

Питання: а, власне, чому уторований маршрут? Ви що, не вмієте А* реалізовувати?

Відповідь: реалізовували купу разів 🙂 ми чесно експериментували з AI супротивника. У нас були розумні шукають дорогу черв’яки, і слаймы, уворачивающиеся від куль. Грати ставало неможливо. Гравець не міг побудувати ефективну оборону і не міг порадіти, спостерігаючи як вона працює. Задоволення від гри різко падав. Це не означає, що “розумні” вороги — це погано. Просто для обраної механіки — коли наші будови статичні — такі вороги не підходили. Для “розумних ворогів” потрібно то роботу приробити кулемет, то вежам — ноги. А це вже зовсім інша гра — не та, що сподобалася людям на LD і itchio.
З коментарів до чернеткою статті:
icxon: Але слаймы досі є. І вони таки ухиляються
GRaAL: подумаєш, трохи художнього вимислу

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

Рішення: летить снаряд видно над туманом війни. Це дає гравцеві можливість помітити загрозу і вжити заходів.

До речі про снаряди. Відволічемося від геймплейно-UX проблем.

Обробка колізій

У LD версії рухомих об’єктів було не так вже й багато — десяток кульок та десяток черв’ячків в кожен момент часу. У грі цього виявилося мало. Щоб відчувався challenge, доводилося підвищувати кількість супротивників, і одночасно давати гравцеві скорострільна зброя для протидії. Так що в грі не рідкість така ситуація:

Для всіх ігрових об’єктів потрібно вважати колізії. Кульки врізаються в черв’яків, черв’яки врізаються в блоки, і всіх їх на екрані — іноді кілька сотень. А ще гарматам треба б вибирати собі цілі в радіусі дії. Якщо у LD-версії ми могли собі дозволити итерироваться по всім ворогам в пошуках підходящих, то тут це вже починало гальмувати.

Загалом, у цієї проблеми є рішення, на хабре я не раз зустрічав статті на цю тему (ось одна з них — https://habr.com/post/334990/ ). Так як у нас є логічна сітка на екрані, а більшість активних об’єктів розміром не перевищують 2х2 ігрових клітини, ми вирішили використати цю сітку для визначення колізій.

З кожною клітиною пов’язаний список об’єктів, які знаходяться в цій клітці. Є статичні об’єкти (типи блоків або каміння), які займають рівно одну клітку цілком і нікуди не переміщуються. І є динамічні, які рухаються по сітці, “перетікаючи” з однієї клітини в іншу.

Т. к. об’єкт може знаходитися десь на кордоні двох клітин (хоча його центр однозначно лежить в межах лише однієї), то при обліку колізій оглядаються всі сусідні клітини. Тобто беремо ми скажемо пульку з координатами X, Y, і дивимося що лежить в клітинах (X,Y), (X+1,Y), (X-1,Y), (X,Y+1), (X,Y-1). Якщо там є об’єкти, з якими пулька може взаємодіяти, то вже для кожного з них точно розраховується колізія виходячи з форми і розміру.

Щоб два рази не вставати, ця сітка використовується для вибору цілей вежами. Башта після створення “підписується” на певні клітини сітки. Якщо в клітину потрапляє щось, що може бути підстрілене, вежа отримує повідомлення (і зазвичай після цього стріляє). Таким чином, якщо тисяча ворогів повзає завідомо далеко від вежі, башта не буде витрачати час на підрахунок відстані до них.

Якщо карта велика, то клітин в сітці стає теж багато, і всі апдейти і перевірки починають займати більше часу. Також проблеми починаються якщо є якісь будівлі/юніти розміром у кілька клітин — для них доводиться перевіряти більше клітин навколо.

Для нівелювання цих моментів у нас поверх дрібної сітки покладена велика, розміром 16х16 клітин. У неї вважається все те ж саме, і вона використовується для швидкого відсіву ситуацій, коли в радіусі декількох клітин навколо об’єктів нічого немає і колізії можна не перевіряти.

Обіцяні посилання опитування

Стаття вийшла вже досить довгою, а мій список тем в блокноті зменшився дай бог на чверть. Скажіть, про що вам цікавіше було б почути в наступній статті? Можна голосувати за кілька пунктів.

Related Articles

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Close