Содержание

Инвентарь игрока

Инвентарь — это сумка с предметами, которые игрок находит по ходу игры и применяет к вещам на уровне: ключ к замку, лом к крышке, монету к автомату. Предмет — это строка-ключ (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)

Основной способ, когда на картинке несколько мест применения. В тексте задания, в режиме «Источник»:

<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="🔑" .../>

Зоны, которых ещё нет

Набор зон обычно зависит от того, что игрок уже сделал: у вскрытого ящика зон нет, щель автомата появляется вместе с монетой. Разметку под это удобно собирать шаблонами {% 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});
//   %}
// %}

Что стоит знать

Чего у инвентаря нет: количества (предмет либо есть, либо нет — «три монеты» делаются тремя предметами или кодами), сортировки и перетаскивания предмета на предмет.

Примеры в игре

У обоих уровней внутри есть раздел «Как так сделать?» с разбором.

Первая версия

Работает как работала. Отличается тем, что инвентарь рисуется в отведённом автором блоке, зон нет, а у выбранного предмета две кнопки — «глаз» и «применить».

Инвентарь первой версии: предметы приходят за решённые бонусы

Выбранный предмет: «глаз» показывает картинку крупно, галочка — использует предмет

  1. В текст задания (в режиме «Источник») — блок, где показывать список:
<div id="inventory_container">Загрузка инвентаря...</div>
  1. В скрипт задания — вызов:
showInventoryItems('inventory_container');

Вторым параметром — та же привязка картинок к чужой игре: showInventoryItems('inventory_container', 1234).

Предметы выдаются и забираются так же, addInventoryItem() и removeInventoryItem() в «Тексте после решения» бонуса. Кнопка «применить» вбивает код, равный ключу предмета.

Предмет приходит за бонус, а «использовать» вбивает его код за игрока

Пример в игре: Инвентарь.

Смотрите также