{{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(широта, долгота, зум)'' рисует карту с вашими точками и радиусами и ставит перетаскиваемый маркер игрока. Куда вы перетащите маркер — там движок и будет считать игрока. Функция ждёт на странице контейнер ''
'' и работает на **Leaflet + OpenStreetMap**, без API-ключей. Для триггеров на сервере она не нужна — у тестера для этого есть вкладка «GPS». ===== Карта на уровне ===== Движок не подключает никаких картографических библиотек — карту автор добавляет сам в текст задания. В примере ниже используется бесплатный Leaflet с тайлами OpenStreetMap: подключаются css и js библиотеки, создаётся контейнер карты, а метка двигается по координатам игрока. {{ :authors_main:task_editor:advanced:gps_map.png?800 |Пример с Leaflet: карта уровня и метка игрока на ней}} Учтите, что задание перерисовывается при автообновлении, поэтому состояние карты (центр, зум, объект карты) удобно хранить в ''window'', чтобы она не создавалась заново и не «прыгала» под игроком. ===== Ссылки на координаты ===== Если по сюжету нужно просто отправить игрока в точку, а не проверять его приход, хватит обычной ссылки. Вставляется в режиме **«Источник»**: 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|Пример ссылок на координаты]]