{{indexmenu_n>33}}
====== Коды по GPS-координатам ======
Движок умеет сам вбивать код, когда игрок дошёл до нужной точки на местности. Сделать это можно двумя способами:
* **Триггер на сервере** — зона пишется маркером ''{! on gps ... !}'' в тексте задания. Координат цели нет в странице игрока нигде: ни в тексте, ни в скрипте, ни в ответах сервера. **Это основной способ.**
* **Точки в скрипте** — массив ''g_targets'' в «Скрипте задания». Проверка идёт в браузере, поэтому координаты цели лежат в исходном коде страницы. Способ старый, но нужен, когда браузеру самому надо знать, где цель: показать расстояние, нарисовать точку на карте.
> **Важно:** геолокация включается только с разрешения игрока. Если игрок откажет, ни одна зона не сработает, поэтому оставляйте запасной способ пройти уровень (обычный код, подсказка со сливом) или заранее предупредите команды, что GPS обязателен.
> **Трек команды.** Пока слежение включено, движок записывает путь команды — по устройству, сегментами «стояли» и «шли», плюс срабатывания зон и отброшенные замеры. На странице игры у автора есть ссылка «Треки GPS»: там отчёт с признаками подмены геолокации по командам (вердикт — не приговор, у каждого признака есть честные объяснения), спорные моменты вроде «дошли до зоны, но код не вбился» и карта. Трек хранится 90 дней от последней записи; удалить раньше можно там же.
===== Включить слежение: gps_start() =====
Оба способа начинаются одинаково — с вызова ''gps_start()'' в поле **«Скрипт задания»**:
Расстояние до точки: ??? м.
gps_start();
Без него браузер не спросит координат, и ни одна зона не сработает. Если в задании есть триггеры, а ''gps_start()'' нигде нет, редактор предупредит об этом при сохранении.
Библиотеку геолокации движок подключает только заданиям, которым она нужна, — отдельно включать ничего не надо.
===== Значок GPS в шапке задания =====
У задания с GPS рядом с названием стоит значок-спутник. Он показывает игроку, работает ли геолокация, и сам же её включает: на телефоне браузер даёт координаты только после нажатия, поэтому значок стоит с самого начала, ещё до первого замера.
{{ :authors_main:task_editor:advanced:gps_indicator.png |Значок GPS в шапке задания: зелёный — можно идти}}
^ Цвет ^ Что значит ^ Что сделает нажатие ^
| серый | слежение не включено | включит его |
| красный | позиции нет: ищем спутники, игрок запретил доступ или геолокация недоступна | начнёт поиск заново или подскажет, как разрешить доступ |
| жёлтый | позиция есть, но точность хуже 30 метров — такой замер движок не засчитывает | обновит позицию |
| зелёный | всё работает, можно идти | обновит позицию |
Если навести на значок, подсказка назовёт состояние словами и текущую точность.
===== Триггер на сервере =====
Зона объявляется маркером в тексте задания:
{! on gps 51.5299 -0.1275 radius 30: code ньютон !}
* **''51.5299 -0.1275''** — центр зоны: широта и долгота, в том виде, в каком их копируют из Google Maps или OpenStreetMap. Запятая между ними не обязательна.
* **''radius 30''** — радиус в метрах. Можно не писать — по умолчанию 30.
* **''code ньютон''** — что сделать, когда игрок в зоне: вбить код. Код должен быть кодом или бонусом этого задания.
* Подпись зоны для себя пишется комментарием внутри маркера: ''{! on gps /* задний двор */ 51.5299 -0.1275: code ньютон !}''. Игрок её не увидит, а тестер увидит на карте.
Общие правила маркера — где он срабатывает, что проверяет сохранение, как пишутся несколько действий — на странице [[authors_main:task_editor:advanced:server_actions|Серверные действия]].
==== Зона произвольной формы ====
Двор, сквер, квартал — зона-многоугольник. После слова ''polygon'' идут координаты вершин подряд, парами «широта долгота», от 3 до 30 вершин. Радиуса у многоугольника нет — границы задают вершины.
{! on gps /* сквер */ polygon 51.5305 -0.1280 51.5320 -0.1262 51.5313 -0.1230 51.5291 -0.1238: code сквер !}
==== Как это работает ====
Пока игрок идёт, страница отправляет его координаты на сервер — когда он сдвинулся хотя бы на несколько метров, но не чаще, чем раз в пару секунд. Сервер отвечает, в какой зоне игрок стоит, и страница вбивает код этой зоны — молча, обычным ответом. В журнале ответов такой код записан за игроком, который дошёл.
* **Расстояния до цели движок не показывает** — по расстояниям из нескольких точек цель легко вычислить. Если игроку нужна подсказка «теплее-холоднее», это старый способ, ''g_targets''.
* **Замеры с точностью хуже 30 метров не считаются вовсе.** Позиция по wifi или по адресу приезжает с точностью в сотни метров, и её центр случайно попадает в любую зону — код вбился бы человеку, который никуда не шёл.
* **Прыжки не считаются:** замер, по которому игрок переместился быстрее 150 км/ч, отбрасывается.
* Подделать геолокацию в браузере всё равно можно, и закрыть это нельзя. Смысл в другом: в странице нет цели, и обход зоны требует осознанных действий, а не одного взгляда в исходный код.
==== Переключатель и приоритет ====
Обычный код зона вбивает один раз. **Условие о поле** в квадратных скобках позволяет вбивать снова — и здесь оно нужнее всего:
{! 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 !}
Набор зон с ''<'' — **приоритет**: код с бо́льшим числом в начале заменяет меньший, но не наоборот. Так в секторе остаётся лучшая из достигнутых точек.
Подробно про условие о поле — на странице [[authors_main:task_editor:advanced:server_actions|Серверные действия]].
==== Зона без кода: zone ====
Бывает, что по зоне ничего вбивать не нужно — надо только показать игроку что-то, пока он стоит у беседки. Для этого есть действие ''zone'' с именем:
{! on gps /* беседка */ 51.5241 -0.1072 radius 30: zone беседка !}
Кода у такой зоны нет. Страница сообщает скрипту задания, что игрок вошёл в зону или вышел из неё, — один раз на смену, а не на каждый замер:
QEng.Gps.onZone = function (name, inside) {
if (name === 'беседка') $('#gazebo').toggle(inside);
};
* Имя зоны — всё, что после слова ''zone''; пробелы в нём можно. Приходит оно только тому, кто уже стоит в зоне, — где зона, страница по-прежнему не знает.
* Когда задание перерисовывается (вбит код, пришла подсказка), движок заново сообщает «вошёл» про зоны, в которых игрок так и стоит: перерисовка стирает то, что скрипт нарисовал в тексте.
* Зона, которая и вбивает код, и называет себя скрипту, пишется одним маркером: ''{! on gps 51.5299 -0.1275: code ньютон: zone двор !}''.
==== Зона в бонусе и в подсказке ====
Маркер работает, пока виден текст, в котором он стоит. Поэтому зона в **описании бонуса** есть, пока бонус не решён, а зона в **подсказке** появляется вместе с подсказкой. Так набор зон меняется по ходу уровня без единой строчки скрипта.
==== Закончить игру по зоне ====
Сама зона игру не заканчивает. Это два шага: зона вбивает код, а ''finish_game'' стоит в ветке шаблона по этому коду:
{! on gps 51.5299 -0.1275: code финиш !}
{% match code_1 }:{ {%= финиш}:{ {! finish_game !} %} %}
[[https://qeng.org/game.php?jump_to&gid=3493&task_id=83702|Пример в игре: переключатель, многоугольник, приоритет, зона без кода, зоны в бонусе и в подсказке]]
===== Проверить, не выходя из дома: вкладка «GPS» =====
У тестера в панели тестирования внизу страницы есть вкладка **«GPS»** — она появляется на задании, скрипт которого зовёт ''gps_start()''.
{{ :authors_main:task_editor:advanced:gps_tester_tab.png?800 |Вкладка «GPS» панели тестирования: зоны уровня на карте и положение игрока}}
* На карте — все зоны уровня, с номерами и подписями из комментариев. Показаны и зоны из веток шаблона, которые сейчас не открыты: так видно, не наехали ли зоны друг на друга. Зоны, которые работают сейчас, отмечены.
* **Своё положение можно подменить**: кликнуть по карте, перетащить маркер игрока или вписать широту и долготу и нажать «Применить». Движок сразу проверит зоны по новой точке. Проверка скорости тестеру не мешает — прыгать по карте можно сколько угодно.
* **«Сбросить подмену»** возвращает к настоящему датчику, **«Показать всё»** подгоняет карту под игрока и все зоны.
* Список зон обновляется вместе с заданием: решили бонус — его зона уходит и с карты.
Что вбили маркеры на этой загрузке страницы, показывает соседняя вкладка «Действия» — см. [[authors_main:task_editor:advanced:server_actions|Серверные действия]].
===== Точки в скрипте: 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))"
});
* **''lat''**, **''lon''** — координаты точки.
* **''r''** — радиус срабатывания **в метрах**.
* **''action_inside''** — строка с javascript, выполняется, когда игрок внутри радиуса.
* **''action_outside''** — строка с javascript, выполняется, когда игрок снаружи радиуса.
Точек может быть сколько угодно — просто добавьте в ''g_targets'' несколько объектов.
> **Действия выполняются при каждом обновлении координат**, а не один раз при входе в зону. Пока игрок стоит внутри радиуса, ''action_inside'' сработает много раз подряд. Для ввода кода это безопасно: ''enter()'' и ''enter_silent()'' отправляют код только один раз. А вот показ сообщения или проигрывание звука лучше защитить собственным флагом:
'action_inside': "if (!window.was_inside) { window.was_inside = 1; play_sound('limit'); }"
==== Расстояние до точки ====
Функция ''gps_distance(широта, долгота)'' возвращает расстояние от текущего положения игрока до указанной точки **в метрах**. Обычно её показывают игроку, чтобы он понимал, теплее или холоднее:
{{ :authors_main:task_editor:advanced:gps_distance.png?800 |Уровень с точкой в скрипте: расстояние до неё считает браузер игрока}}
Функцию можно вызывать только после того, как получены первые координаты, — до этого положение игрока неизвестно. Проще всего вызывать её из ''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)'':
* ''requesting'' — запрос разрешения у браузера;
* ''searching'' — ожидание захвата спутников GPS;
* ''locked'' — координаты получены и обновляются;
* ''timeout'' — поиск спутников затянулся (движок делает мягкие авто-повторы);
* ''denied'' — игрок запретил доступ к геопозиции;
* ''unavailable'' — геолокация недоступна или отключена.
Для остановки отслеживания (например, при переходе между этапами для экономии аккумулятора) есть функция ''gps_stop()''. Повторный вызов ''gps_start()'' не перезапускает чип GPS с нуля, а переиспользует активный наблюдатель и сразу применяет текущие координаты к новым целям.
Те же функции есть и под общим именем ''QEng.Gps'': ''QEng.Gps.start()'' и ''stop()'', ''distance(lat, lon)'', ''position'', ''targets'', ''addTarget({...})'', ''onUpdatePosition'', ''onStatus''. Старые имена работают и будут работать.
==== Отладка ====
Служебная функция ''init_debug_map(широта, долгота, зум)'' рисует карту с вашими точками и радиусами и ставит перетаскиваемый маркер игрока. Куда вы перетащите маркер — там движок и будет считать игрока. Функция ждёт на странице контейнер ''
59.249, 37.465
Схема ''geo:'' открывает координаты в навигаторе, установленном на телефоне. Для настольных браузеров, где такой схемы нет, подойдёт ссылка на карты:
49.940333, 36.39305
[[https://qeng.org/game.php?jump_to&gid=3493&task_id=83702|Пример триггеров на сервере]] | [[https://qeng.org/game.php?jump_to&gid=3493&task_id=76365|Пример точки в скрипте]] | [[https://qeng.org/game.php?jump_to&gid=3493&task_id=80987|Пример с картой]] | [[https://qeng.org/game.php?jump_to&gid=3493&task_id=54060|Пример ссылок на координаты]]