Як створити shmup для Apple Watch
Посібник зі створення shmup для Apple Watch: віртуальна роздільна здатність, Digital Crown, колізії прямокутниками, pooling, рівні як таблиці часу, генератор.

Shmup (shoot 'em up, стрілялка, у якій корабель летить проти хвиль ворогів) на Apple Watch стикається з трьома проблемами: малий дисплей, палець, що закриває половину гри, і годинники різних розмірів. Їх вирішують три рішення: уся гра працює на фіксованій сітці 84 × 48 точок, колізії перевіряються вручну прямокутниками, а рівні є таблицями з часом, а не кодом. Корабель керується коронкою Digital Crown і стріляє сам.
Приклади взято з гри Space Impact Watch, ремейку стрілялки Space Impact з телефона Nokia 3310 2000 року. Гра працює на годиннику, а також на iPhone та iPad. Вона побудована на SpriteKit, 2D-фреймворку для ігор від Apple, але принципи підходять для будь-якого shmup на малому екрані.
Фіксована віртуальна роздільна здатність 84 × 48
Спочатку вирішіть, у яких одиницях рахуватиме гра. Якщо взяти точки екрана, світ на кожному розмірі годинника буде різним. Краще вибрати малу віртуальну роздільну здатність і все рахувати в ній. У нашій грі це 84 × 48, точно як дисплей Nokia 3310. Позиції, швидкості, розміри sprite і траєкторії ворогів задані в цих одиницях. На старті сцени один раз обчислюється, скільки точок екрана має один віртуальний піксель (ширина сцени поділена на 84), і все просто множиться на це число.

Що ви отримуєте:
- Однаковий вигляд на кожному пристрої. Гра виглядає однаково на годиннику будь-якого розміру і на iPad, змінюється лише розмір блока.
- Level design у малих цілих числах. Ворог летить на висоті 13, ракета повертає в точці x = 20.
- Просту графіку. Sprite одноколірні й намальовані в десятикратному розмірі, тож один віртуальний піксель у вихідному зображенні є блоком 10 × 10. Колір вони отримують уже в грі: на світлих рівнях чорні на зеленому тлі, на темних навпаки.
Керування коронкою і автоматичний вогонь
На годиннику палець закриває половину гри, тому корабель не керується дотиком. Рух забезпечує Digital Crown: поворот коронки зсуває корабель угору або вниз. Додайте налаштування чутливості й можливість інвертувати напрямок.
Стрільба автоматична, у нашій грі кожні 0,4 секунди, з четвертого рівня кожні 0,3. Дисплей потрібен лише для одного жесту: свайпом праворуч гравець застосовує один із трьох видів спецзброї.

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

Game loop в одній сцені
Уся гра може бути однією сценою з одним циклом:
щокадру:
перемісти всі об'єкти
перевір колізії
залежно від часу на рівні додай нових ворогів
Додавайте частини в такому порядку, щоб якомога раніше їх тестувати:
- Стрільба і рух гравця. Без них нічого не протестуєш.
- Вороги і колізії. Першому ворогу достатньо летіти прямо ліворуч і гинути від одного влучання.
- Вібрація і анімації влучання. Без них на малому дисплеї гравець не знає, що влучив.
- Тло і HUD. Життя, рахунок, кількість спецзброї. Якщо HUD на якомусь рівні заважає, перенесіть його вниз.
У результаті маєте ігровий цикл із життями, рахунком, вибухами, тлом і respawn гравця. Якщо тримати ігрову логіку окремо від рендерингу та введення, її можуть спільно використовувати версії для годинника й телефона.
Колізії прямокутниками замість фізичного рушія
SpriteKit має фізичний рушій, і його кортить використати й для колізій. Кожен об'єкт отримує фізичне тіло з контуру свого sprite, точне виявлення колізій і категорію (гравець, ворог, куля, спецзброя), а рушій сам повідомляє пари, що торкнулися. Apple у документації про фізичні тіла зазначає, що тіла з альфа-каналу текстури найточніші й водночас найдорожчі для обчислень. У нашій грі ми це спробували й повернулися до ручної перевірки.
Ручна перевірка є однією функцією, яка по черзі проходить пари груп і порівнює прямокутники:
для кожної пари груп:
(гравець і вороги, гравець і кулі, кулі й вороги, кулі й кулі)
для кожного об'єкта A з першої групи та об'єкта B з другої:
якщо прямокутник A перекривається з прямокутником B:
обробити влучання

Переваги: порядок перевірок під вашим контролем, а все правило «що стається при влучанні» зібране в одному місці. Неточність мала, бо на сітці 84 × 48 sprite такі дрібні, що прямокутник і справжній контур відрізняються лише на кілька віртуальних пікселів.
Pooling лише там, де він окупається
Pooling означає, що об'єкт після знищення ви не видаляєте, а ховаєте й наступного разу використовуєте знову. Кортить застосувати pooling до всього, разом із вибухами та power-up. Але це має ціну: кожна колізія і кожне оновлення спершу мусить перевірити, чи об'єкт узагалі живий. Досить однієї забутої перевірки, і прихований ворог уб'є гравця або прихована куля зіб'є ракету. Коли ми в нашій грі прибирали pooling з усього, видаляли переважно саме ці перевірки.
Просте правило: застосовуйте pooling до того, чого багато і що завжди створюється однаково. Гравець на старті створює 21 кулю і далі лише використовує їх повторно. Вороги та їхні кулі теж використовуються повторно, а вибухи й power-up створюються і зникають звичайним способом.
Рівні як таблиці часу
Рівень не мусить бути кодом, досить списку записів. Кожен запис каже, що з'явиться, на якій секунді, на якій висоті і як рухатиметься. Рух є списком точок, через які ворог летить зі сталою швидкістю. За потреби додаються моменти пострілів, ривок боса або генератор менших ворогів.
щокадру:
час = зараз - початок рівня - час на паузі
для кожного запису в таблиці рівня:
якщо запис ще не використано і час > запис.час:
створи об'єкт за записом
познач запис як використаний

Такий рівень легко налаштовувати: зсунути хвилю на пів секунди означає змінити одне число. На часовій шкалі видно й ритм, якого треба досягти: коротка хвиля, пауза, інший тип ворога, power-up перед складнішою ділянкою.

Недолік відчувається при більших змінах. Найщільніший рівень нашої гри має 71 рядок, і таку таблицю без редактора читати важко. Вигідніше записувати рівні як хвилі (група ворогів з одним шаблоном руху), а не як окремі кораблі.
Нескінченний режим з одного псевдовипадкового числа
Для таблиці лідерів (у нашій грі Game Center) потрібен режим, який не закінчується. У ньому ручну таблицю замінює генератор, що створює її під час гри. Він генерує 20-значне псевдовипадкове число, і окремі пари цифр визначають одну хвилю:
число = наступне псевдовипадкове 20-значне число
тип = power-up, якщо цифри 4-5 > 90, інакше ворог
sprite = цифри 1-2 за модулем кількості видів ворогів
пауза перед хвилею = цифра 3 / 2 секунди (щонайменше 1)
висота = цифра 7, перерахована на висоту поля
шаблон руху = цифри 10-11 за модулем 10
кораблів у хвилі = цифра 17 / 2 + 1
швидкість = цифра 13 × (1 + номер хвилі / 10), у межах від 20 до 50
кожна тридцята хвиля = бос, генерація стоїть, поки бос живий
Залиште seed (початкове значення) генератора в грі фіксованим. Тоді кожен гравець отримає однакову послідовність хвиль, і результати в таблиці лідерів можна порівнювати.

Як розвивається такий режим, показує симуляція генератора поза грою (300 хвиль, той самий seed, що й у грі). Перший бос з'являється після 29 хвиль, тобто після 74 ворогів і 3 power-up, приблизно на 130-й секунді. Перші 30 хвиль летять із мінімальною швидкістю 20, бо одна цифра ще менша за нижню межу. Далі середня швидкість зростає до 28,5, 37,5 і 41, а від 120-ї хвилі більшість ворогів летить на максимумі. Power-up випадає приблизно кожну одинадцяту хвилю.

Слабке місце: генератор не знає ритму. Ручний рівень чергує тиск і відпочинок, генератор ні. Частково це компенсують пауза під час боса і зростання швидкості, але рівні, написані вручну, грати цікавіше.
Обмеження цього підходу
- Немає автоматичних тестів. Усе перевіряється вручну в симуляторі й на годиннику.
- Глобальний стан. Більша частина стану гри є глобальними змінними однієї сцени. Для гри лише на годинник це зручно, а при додаванні версії для iPhone вже ні.
- Рівні без редактора. Таблицю часу легко налаштовувати малими кроками, але для більших змін їй бракує редактора.
Якщо ви робите гру для годинника чи іншого маленького екрана і вам потрібна допомога, напишіть нам. Як ми генеруємо головоломки в іншому нашому застосунку, ми описали в статті про генератор судоку.


