Інвентар — це сумка з предметами, які гравець знаходить упродовж гри і застосовує до речей на рівні: ключ до замка, лом до кришки, монету до автомата. Предмет — це рядок-ключ (hammer123), і вся гра з ним зводиться до кодів: застосував предмет — рушій увів за гравця код.
Версій інвентаря дві, і вони живуть поруч.
Рівень обирає одну з двох: яку функцію покликав скрипт, та й малює.
Унизу екрана — кнопка-сумка. На ній загальна кількість предметів і окремо «+N» — скільки нових; поки нові є, сумка подригує.
За кнопкою розкривається смуга предметів; нові в ній підсвічені жовтим і перестають бути новими, щойно гравець їх торкнувся — навів мишу, тапнув, потягнув.
Застосувати предмет можна двома способами, і вони рівноправні:
Щойно предмет «у руках», на рівні загоряються всі місця, куди його можна застосувати, — зони. Поряд із сумкою тієї ж миті з’являються ще два приймачі: «застосувати» (без зони, просто саме по собі) і «око» — подивитися предмет великим.
Відпустив повз або натиснув порожнє місце — предмет повернувся в сумку.
Що вийшло із застосування, гравець бачить на самому предметі: підійшло — предмет підстрибує зеленим, не підійшло — хитає головою червоним. Якщо предмет забрав бонус, він розчиняється там, куди його донесли. Поки відповіді немає, предмет пульсує на місці.
Розкрита панель і місце, до якого гравець догортав смугу, переживають перезавантаження сторінки: код уведено, рівень змінився — сумка лишилася відкритою на тому самому місці.
Панель притиснута до низу вікна, а не стоїть у тексті завдання, тому на телефоні предмет і місце застосування не зобов’язані вміщатися на екран одночасно.
У скрипт завдання (поле внизу рівня):
showInventoryItems2({gameId: 1234});
Більше нічого не треба: ні блоку в тексті завдання, ні id — панель рушій малює сам.
gameId — гра, у теці якої лежать картинки предметів. Можна не вказувати, тоді береться поточна гра, — але див. про копіювання гри.
Виклик має стояти саме у скрипті завдання. Рушій під’єднує другу версію, побачившиshowInventoryItems2у скрипті рівня; з тексту завдання, з бонуса чи із спільної шапки гри він її не під’єднає — функції просто не буде.
І виклик має бути один: він забирає накопичений список предметів, і другий за те саме промальовування намалює порожню сумку.
Предмет видається з «Тексту після розв’язання» бонуса — у режимі «Джерело»:
<script>
addInventoryItem('hammer123');
</script>
Забирається звідти ж, бонусом пізнішого кроку:
<script>
removeInventoryItem('hammer123');
</script>
Тексти розв’язаних бонусів рушій виконує заново на кожному промальовуванні сторінки. Тому список предметів не зберігається ніде: він щоразу збирається з нуля з того, що вже розв’язано. Сервер про предмети не знає нічого.
Із цього ж випливає і зворотний прийом: видавати предмети не бонусами, а самим скриптом завдання, порахувавши за введеними кодами, що зараз має лежати в сумці. Так зроблено рівень-приклад із полицею — книжку можна поставити, зняти й замінити іншою, і окремих кодів на це не треба.
Наскрізний бонус. Предмет, виданий наскрізним бонусом, звичайним бонусом рівня не забрати — тільки наскрізним із більшим порядковим номером.
На наступний рівень предмет сам не переїжджає. Бонус рівня на іншому рівні не виконується, отже, і предмета там немає. Річ, яку носять через усю гру, видається наскрізним бонусом.
Картинка предмета шукається за іменем файлу: item_<ключ> — іконка у смузі, item_<ключ>_big — те, що відкривається за «оком».
Заливати обидві руками не треба. Залийте одну картинку з іменем item_hammer123.png у файли гри: якщо вона від 512 точок по обох сторонах, рушій сам розкладе її на іконку і велику версію. Дрібніша — лишиться одним файлом, і «око» покаже її ж.
У списку залитих файлів у такої картинки з’являється готовий рядок addInventoryItem('hammer123') — за кліком він копіюється в буфер. Вставляти в бонус треба саме його: предмет під’єднується ключем, а не адресою файлу.
Ім’я такого файлу рушій зберігає як є, навіть якщо галочку «Зберегти назву» знято: інвентар шукає картинку за іменем. Ключ береться з імені разом із регістром — це рівно той рядок, що йде в addInventoryItem().
Типово рушій шукає .jpg. Картинки в іншому форматі — зокрема прозорі png, заради яких у панелі й зроблено слот і тінь, — оголошуються один раз на гру, у «Спільному HTML-заголовку»:
<script>
setInventoryImageExt('png');
setInventoryBigExt('png');
</script>
Іконка і великий вигляд оголошуються окремо: вони можуть бути в різних форматах. Поштучний виняток — другим аргументом:
addInventoryItem('map7', {icon: 'png', big: 'png'});
Розширення не входить у ключ: ключ — це код, який іде на сервер.
Картинки предметів беруться з теки тієї гри, чий номер ви передали. Копіювання гри файли не копіює, тому в копії картинки зникнуть — якщо gameId не названо явно або він указує на саму копію.
Прив’яжіть інвентар до гри, у яку залито картинки:
showInventoryItems2({gameId: 3493});
Той самий номер можна задати один раз на всю гру, у «Спільному HTML-заголовку», — він копіюється разом із грою:
<script> setInventoryGameId(3493); </script>
Зона — місце на рівні, до якого застосовують предмет: двері, замок, торговець, слот полиці. У зони є код.
Коли гравець застосовує предмет до зони, рушій уводить складений код: код зони + ключ предмета. Лом (crowbar) на зону lid — це код lidcrowbar. Такий код і має стояти в бонуса, який відкриває кришку.
Зона, під яку коду немає, просто відповідає «не підійшло», і предмет повертається в сумку. Вигадувати «порожні» зони не потрібно: зона без відповідного предмета нічим не загрожує.
Застосування без зони — «застосувати» поряд із сумкою — уводить код, що дорівнює ключу предмета (як у першій версії).
Код зони і ключ склеюються без роздільника, тому імена варто розводити самому: зонаslotіз предметом1bookі зонаslot1із предметомbookдадуть один і той самий код.
Перевіряти на боці гравця, який предмет до якої зони підходить, рушій не вміє і не буде: тоді правильна відповідь лежала б у джерелі сторінки. Будь-який предмет на будь-яку зону — це просто код, і відповідає на нього сервер.
Чотири способи, і всі вони про одне й те саме — точку і радіус навколо неї. Різняться тим, звідки береться точка.
Основний спосіб, коли на картинці кілька місць застосування. У тексті завдання, у режимі «Джерело»:
<svg viewBox="0 0 800 600"> <image href="https://cdn.qeng.org/f/g/1234/room.jpg" width="800" height="600"/> <circle data-inventory-zone="door" cx="410" cy="255" r="40" fill="none"/> <circle data-inventory-zone="chest" cx="96" cy="470" r="55" fill="none"/> </svg>
Координати живуть у системі viewBox, тобто прив’язані до картинки, а не до екрана: телефон, десктоп, поворот, зум — точка лишається на тому самому місці малюнка. Перераховувати нічого не треба.
r — радіус застосування в одиницях картинки. На малому екрані картинка стискається, і разом із нею стискалася б зона, — рушій сам піднімає область влучання до пальця.
Годяться не тільки circle: у будь-якої фігури береться її габаритний прямокутник.
Картинка, вставлена як<img>, так не розмічається: у зовнішнього svg немає доступного рушієві вмісту. Для картинки в<img>— спосіб із відсотками нижче.
Найдешевший спосіб, коли svg розводити не хочеться:
<img src="..." data-inventory-zones="door:41,42 chest:12,78,6">
Через двокрапку — код зони, далі відсотки від розмірів самої картинки: x, y і (необов’язково) радіус. Зон в одному атрибуті може бути скільки завгодно, через пробіл.
Свій значок так задати не можна — тільки спільний на всі зони носія. Потрібен свій у кожної — беріть data-inventory-zone на окремих елементах або addInventoryZone().
<div data-inventory-zone="npc">Торговець</div> <img src="..." data-inventory-zone="lock">
Область влучання — прямокутник елемента цілком. Це відповідь для випадку «зона — це блок тексту, картинка цілком або кнопка».
Зона, якої на рівні спочатку немає, оголошується викликом — у скрипті завдання або в «Тексті після розв’язання» бонуса:
addInventoryZone('box', {el: '#room', x: 120, y: 150, r: 45});
Перший аргумент — код зони. Далі:
| Поле | Що означає |
|---|---|
el | носій: селектор або елемент. Обов’язковий — він задає систему координат |
x, y | позиція всередині носія; без них — центр елемента, а область влучання — весь його прямокутник |
r | радіус застосування; без нього — розмір значка, піднятий до пальця |
icon | свій значок зони; без нього спільний |
Одиниці визначає носій: інлайновий svg — координати його viewBox, будь-який інший елемент — відсотки його прямокутника (радіус — відсотки меншої сторони). Ті самі {x: 150, y: 262} на svg і на картинці означають різне. Абсолютних екранних координат немає навмисно: вони поїхали б на першому ж іншому екрані.
Повторний виклик із тим самим кодом не заводить другу зону, а оновлює наявну. Отже, зону можна й рухати: перерахував позицію — покликав ще раз.
Прибрати — removeInventoryZone('door'). Зону з розмітки цей виклик теж зніме, але тільки до найближчого автооновлення: html прийде із сервера знову, і зона повернеться. Прибирати її треба там само, де прибирається все інше на рівні, — шаблоном на сервері.
Зона зі скрипта перекриває зону з розмітки з тим самим кодом.
Типово в усіх зон один спільний напівпрозорий значок. Свій задається в розмітці атрибутом data-inventory-zone-icon або полем icon у addInventoryZone(), і вид його оголошувати не треба — рушій розрізняє сам:
<circle data-inventory-zone="door" data-inventory-zone-icon="fa-lock" .../> <circle data-inventory-zone="gate" data-inventory-zone-icon="/f/g/123/door.png" .../> <circle data-inventory-zone="safe" data-inventory-zone-icon="🔑" .../>
fa-lock — значок font-awesome;
Набір зон зазвичай залежить від того, що гравець уже зробив: у розкритої скрині зон немає, щілина автомата з’являється разом із монетою. Розмітку під це зручно збирати шаблонами {% match bonus_N %} — вони рахуються на сервері, тому в джерелі сторінки у гравця лежать тільки ті шари і зони, що видно зараз.
У скрипті завдання шаблони працюють так само, але рядки шаблона коментуються подвійним слешем — рушій знімає коментар сам:
// {% match bonus_2}:{ // {%= leverdncrowbar}:{ addInventoryZone('leverup', {el: '#quest_room', x: 288, y: 305, r: 45}); // %} // {%= ?}:{ addInventoryZone('leverdn', {el: '#quest_room', x: 315, y: 112, r: 45}); // %} // %}
<details>, на display: none або на ще не завантаженій картинці не з’явиться — доти, доки носій не покажеться. Перерахунок рушій робить сам: при зміні розмірів вікна, завантаженні картинки і тієї миті, коли гравець бере предмет.%hide% не видно в джерелі сторінки до розв’язання — ланцюжок застосувань не підглянути.Чого в інвентаря немає: кількості (предмет або є, або немає — «три монети» робляться трьома предметами чи кодами), сортування і перетягування предмета на предмет.
В обох рівнів усередині є розділ «Як так зробити?» з розбором.
Працює як працювала. Різниться тим, що інвентар малюється у відведеному автором блоці, зон немає, а у вибраного предмета дві кнопки — «око» і «застосувати».
<div id="inventory_container">Завантаження інвентаря...</div>
showInventoryItems('inventory_container');
Другим параметром — та сама прив’язка картинок до чужої гри: showInventoryItems('inventory_container', 1234).
Предмети видаються і забираються так само, addInventoryItem() і removeInventoryItem() у «Тексті після розв’язання» бонуса. Кнопка «застосувати» вводить код, що дорівнює ключу предмета.
Приклад у грі: Інвентар.
{% match bonus_N %} для сцени і зон, що залежать від стану рівня.