Содержание

Коды по GPS-координатам

Движок умеет сам вбивать код, когда игрок дошёл до нужной точки на местности. Сделать это можно двумя способами:

Важно: геолокация включается только с разрешения игрока. Если игрок откажет, ни одна зона не сработает, поэтому оставляйте запасной способ пройти уровень (обычный код, подсказка со сливом) или заранее предупредите команды, что GPS обязателен.
Трек команды. Пока слежение включено, движок записывает путь команды — по устройству, сегментами «стояли» и «шли», плюс срабатывания зон и отброшенные замеры. На странице игры у автора есть ссылка «Треки GPS»: там отчёт с признаками подмены геолокации по командам (вердикт — не приговор, у каждого признака есть честные объяснения), спорные моменты вроде «дошли до зоны, но код не вбился» и карта. Трек хранится 90 дней от последней записи; удалить раньше можно там же.

Включить слежение: gps_start()

Оба способа начинаются одинаково — с вызова gps_start() в поле «Скрипт задания»:

gps_start();

Без него браузер не спросит координат, и ни одна зона не сработает. Если в задании есть триггеры, а gps_start() нигде нет, редактор предупредит об этом при сохранении.

Библиотеку геолокации движок подключает только заданиям, которым она нужна, — отдельно включать ничего не надо.

Значок GPS в шапке задания

У задания с GPS рядом с названием стоит значок-спутник. Он показывает игроку, работает ли геолокация, и сам же её включает: на телефоне браузер даёт координаты только после нажатия, поэтому значок стоит с самого начала, ещё до первого замера.

Значок GPS в шапке задания: зелёный — можно идти

Цвет Что значит Что сделает нажатие
серый слежение не включено включит его
красный позиции нет: ищем спутники, игрок запретил доступ или геолокация недоступна начнёт поиск заново или подскажет, как разрешить доступ
жёлтый позиция есть, но точность хуже 30 метров — такой замер движок не засчитывает обновит позицию
зелёный всё работает, можно идти обновит позицию

Если навести на значок, подсказка назовёт состояние словами и текущую точность.

Триггер на сервере

Зона объявляется маркером в тексте задания:

{! on gps 51.5299 -0.1275 radius 30: code ньютон !}

Общие правила маркера — где он срабатывает, что проверяет сохранение, как пишутся несколько действий — на странице Серверные действия.

Зона произвольной формы

Двор, сквер, квартал — зона-многоугольник. После слова polygon идут координаты вершин подряд, парами «широта долгота», от 3 до 30 вершин. Радиуса у многоугольника нет — границы задают вершины.

{! on gps /* сквер */ polygon 51.5305 -0.1280 51.5320 -0.1262 51.5313 -0.1230 51.5291 -0.1238: code сквер !}

Как это работает

Пока игрок идёт, страница отправляет его координаты на сервер — когда он сдвинулся хотя бы на несколько метров, но не чаще, чем раз в пару секунд. Сервер отвечает, в какой зоне игрок стоит, и страница вбивает код этой зоны — молча, обычным ответом. В журнале ответов такой код записан за игроком, который дошёл.

Переключатель и приоритет

Обычный код зона вбивает один раз. Условие о поле в квадратных скобках позволяет вбивать снова — и здесь оно нужнее всего:

{! on gps /* северный двор */ 51.5221 -0.1072 radius 30: code [c1 !=] north !}
{! on gps /* южный двор */    51.5201 -0.1072 radius 30: code [c1 !=] south !}

Две зоны на один сектор — переключатель: в первом секторе стоит код той зоны, в которой команда была последней. Шаблон {% match code_1 %} показывает по нему, где команда сейчас. Зоны переключателя не должны пересекаться: стоя в двух сразу, игрок получит оба кода подряд.

{! on gps /* далеко */  51.5190 -0.1072 radius 30: code [c2 <] 1far !}
{! on gps /* у двери */ 51.5215 -0.1072 radius 30: code [c2 <] 4door !}

Набор зон с < — приоритет: код с бо́льшим числом в начале заменяет меньший, но не наоборот. Так в секторе остаётся лучшая из достигнутых точек.

Подробно про условие о поле — на странице Серверные действия.

Зона без кода: zone

Бывает, что по зоне ничего вбивать не нужно — надо только показать игроку что-то, пока он стоит у беседки. Для этого есть действие zone с именем:

{! on gps /* беседка */ 51.5241 -0.1072 radius 30: zone беседка !}

Кода у такой зоны нет. Страница сообщает скрипту задания, что игрок вошёл в зону или вышел из неё, — один раз на смену, а не на каждый замер:

QEng.Gps.onZone = function (name, inside) {
  if (name === 'беседка') $('#gazebo').toggle(inside);
};

Зона в бонусе и в подсказке

Маркер работает, пока виден текст, в котором он стоит. Поэтому зона в описании бонуса есть, пока бонус не решён, а зона в подсказке появляется вместе с подсказкой. Так набор зон меняется по ходу уровня без единой строчки скрипта.

Закончить игру по зоне

Сама зона игру не заканчивает. Это два шага: зона вбивает код, а finish_game стоит в ветке шаблона по этому коду:

{! on gps 51.5299 -0.1275: code финиш !}
{% match code_1 }:{ {%= финиш}:{ {! finish_game !} %} %}

Пример в игре: переключатель, многоугольник, приоритет, зона без кода, зоны в бонусе и в подсказке

Проверить, не выходя из дома: вкладка «GPS»

У тестера в панели тестирования внизу страницы есть вкладка «GPS» — она появляется на задании, скрипт которого зовёт gps_start().

Вкладка «GPS» панели тестирования: зоны уровня на карте и положение игрока

Что вбили маркеры на этой загрузке страницы, показывает соседняя вкладка «Действия» — см. Серверные действия.

Точки в скрипте: g_targets

Старый способ, и он работает как работал. Координаты точек лежат в исходном коде страницы, так что для загадки «найдите место» он не годится — игрок откроет исходник и увидит цель. Нужен он, когда браузеру самому надо знать, где цель: показать расстояние, нарисовать точку на своей карте.

Точки складываются в массив g_targets. Каждая точка — объект с координатами, радиусом и действиями:

gps_start();
 
window.target_lat1 = 51.5211367;   // Широта точки
window.target_lon1 = -0.10723867;  // Долгота точки
 
g_targets.push({
  'lat': window.target_lat1,
  'lon': window.target_lon1,
  'r': 30,  // Радиус в МЕТРАХ
  'action_inside': "enter_silent('finish_code_123')",
  'action_outside': "$('#distance_to').text(gps_distance(window.target_lat1, window.target_lon1).toFixed(0))"
});

Точек может быть сколько угодно — просто добавьте в g_targets несколько объектов.

Действия выполняются при каждом обновлении координат, а не один раз при входе в зону. Пока игрок стоит внутри радиуса, action_inside сработает много раз подряд. Для ввода кода это безопасно: enter() и enter_silent() отправляют код только один раз. А вот показ сообщения или проигрывание звука лучше защитить собственным флагом:
'action_inside': "if (!window.was_inside) { window.was_inside = 1; play_sound('limit'); }"

Расстояние до точки

Функция gps_distance(широта, долгота) возвращает расстояние от текущего положения игрока до указанной точки в метрах. Обычно её показывают игроку, чтобы он понимал, теплее или холоднее:

<p>Расстояние до точки: <span id="distance_to">???</span> м.</p>

Уровень с точкой в скрипте: расстояние до неё считает браузер игрока

Функцию можно вызывать только после того, как получены первые координаты, — до этого положение игрока неизвестно. Проще всего вызывать её из action_outside, как в примере выше: движок сам выполнит её, когда координаты появятся.

Своя реакция на обновление координат

Если нужно не просто проверить радиус, а нарисовать карту или свой индикатор, переопределите функцию on_update_position() — движок вызывает её каждый раз, когда пришли новые координаты. Текущее положение лежит в g_position (поля lat, lon, а также accuracy — точность замера в метрах, timestamp, altitude, speed, heading):

gps_start();
 
on_update_position = function () {
  $('#my_coords').text(g_position.lat.toFixed(5) + ', ' + g_position.lon.toFixed(5) + ' (±' + Math.round(g_position.accuracy) + 'м)');
};

Также можно отслеживать статус работы датчика через on_gps_status(status, details):

Для остановки отслеживания (например, при переходе между этапами для экономии аккумулятора) есть функция gps_stop(). Повторный вызов gps_start() не перезапускает чип GPS с нуля, а переиспользует активный наблюдатель и сразу применяет текущие координаты к новым целям.

Те же функции есть и под общим именем QEng.Gps: QEng.Gps.start() и stop(), distance(lat, lon), position, targets, addTarget({…}), onUpdatePosition, onStatus. Старые имена работают и будут работать.

Отладка

Служебная функция init_debug_map(широта, долгота, зум) рисует карту с вашими точками и радиусами и ставит перетаскиваемый маркер игрока. Куда вы перетащите маркер — там движок и будет считать игрока. Функция ждёт на странице контейнер <div id=«debug_map»> и работает на Leaflet + OpenStreetMap, без API-ключей. Для триггеров на сервере она не нужна — у тестера для этого есть вкладка «GPS».

Карта на уровне

Движок не подключает никаких картографических библиотек — карту автор добавляет сам в текст задания. В примере ниже используется бесплатный Leaflet с тайлами OpenStreetMap: подключаются css и js библиотеки, создаётся контейнер карты, а метка двигается по координатам игрока.

Пример с Leaflet: карта уровня и метка игрока на ней

Учтите, что задание перерисовывается при автообновлении, поэтому состояние карты (центр, зум, объект карты) удобно хранить в window, чтобы она не создавалась заново и не «прыгала» под игроком.

Ссылки на координаты

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

<a href="geo:?q=59.249,37.465">59.249, 37.465</a>

Схема geo: открывает координаты в навигаторе, установленном на телефоне. Для настольных браузеров, где такой схемы нет, подойдёт ссылка на карты:

<a href="https://www.google.com/maps/search/?api=1&query=49.940333,36.39305" target="_blank">49.940333, 36.39305</a>

Пример триггеров на сервере | Пример точки в скрипте | Пример с картой | Пример ссылок на координаты