60 секунд как остановить таймер

Обновлено: 24.06.2024

1. Добавляйте в игру естественные ритмы: дни, недели, месяцы

Иногда в геймдизайне требуется добавить в игру таймеры на большие периоды времени, которые будут регулировать взаимодействие пользователя с определенной функцией в игре, и не дадут возможность слишком часто к этой функции обращаться. Во множестве F2P-игр этой цели служат долгосрочные таймеры: 4 часа, 8 часов или даже 12 часов. По своему опыту могу сказать, что такие таймеры могут вызывать ощущение, что пользователя ограничивают.

Вместо этого, спросите себя, можете ли вы взамен использовать циклы дня, недели или месяца.

Будет лучше, если вы вместо функции, доступной «раз в восемь часов», сделаете выбор в пользу функции, доступной «один раз в день». Это даст игрокам возможность по-своему выстраивать использование этой функции. Кроме того, у пользователей в таком случае будут более естественные ощущения, поскольку игра будет построена на врожденных у нас ощущениях цикла день/ночь.

Игроку гораздо проще вернуться один раз в день, чем возвращаться ровно через восемь часов. В результате, вы задаете ещё более жесткий темп (потому что один раз в день — это гораздо реже, чем один раз в восемь часов), но с этим проще свыкнуться.


Хорошим примером здесь является игра Hearthstone. В их системе миссий как раз задействован такой дневной ритм. Вместо использования таймеров они отрегулировали выпадение миссий, в игре появляется только одна новая миссия в день. В общей сложности их не может быть больше трех. Так что возвращаясь в приложение в любое время следующего дня, вы уже знаете, что получите одну дополнительную миссию.

Это самый простой источник получения бесплатных монет, поэтому игроки благодарны этой возможности, а кроме того, она привязана к базовому циклу покупки колод карт. Но, конечно, главное здесь — это дневной ритм. В игре нет какого-то чёткого таймера, который принуждает вернуться в игру через Х часов, вместо этого пользователь располагает всей гибкостью естественных дневных циклов. Он задается вопросом: «Я уже выполнил дневную миссию?»

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

Разбираемся с циклами выполнения (Run Loops)

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

Каждый раз, когда вы запускаете приложение, система создаёт главный поток приложения, у каждого потока есть автоматически созданный для него цикл выполнения.

Но почему вся эта информация важна для вас сейчас? Сейчас каждый таймер запускается в главном потоке и присоединяется к циклу выполнения. Вероятно, вы в курсе, что главный поток занимается отрисовкой пользовательского интерфейса, обработкой касаний и так далее. Если главный поток чем-то занят, интерфейс вашего приложения может стать «неотзывчивым» (подвисать).

Вы обратили внимание, что временна́я метка в ячейке не обновляется, когда вы тяните table view?

Решить эту проблему можно, указав циклу выполнения запускать таймеры в другом режиме.

Таймер в iOS

Представьте, что вы работаете над приложением, в котором нужно периодически выполнять некоторые действия. Именно для этого в Swift используется класс Timer.

Timer используется для планирования действий в приложении. Это может быть разовое действие или повторяющаяся процедура.

В этом руководстве вы разберётесь, как в iOS работает таймер, как он может влиять на отзывчивость UI, как оптимизировать потребление батареи при использовании таймера и как использовать CADisplayLink для анимации.

В качестве тестового полигона мы будем использовать приложение — примитивный планировщик задач.

CADisplayLink для плавной анимации

Timer — неидеальный выбор для управления анимацией. Вы могли заметить пропуск нескольких кадров анимации, особенно, если вы запускаете приложение в симуляторе.

Мы установили таймер на частоту 60Hz. Таким образом, таймер обновляет анимацию каждые 16 мс. Рассмотрим ситуацию внимательнее:


При использовании Timer мы не знаем точное время запуска действия. Это может случиться или в начале или в конце кадра. Скажем, таймер выполнится в середине каждого кадра (голубые точки на рисунке). Единственно, что мы знаем наверняка, что вызов будет каждые 16 мс.

Теперь у нас только 8 мс, чтобы исполнить анимацию, и этого может быть недостаточно для нашей анимации. Посмотрим на второй кадр на рисунке. Второй кадр не сможет быть выполнен за отведенное ему время, так что приложение сбросит второй кадр анимации.

Допустимый минимум: Таймеры должны иметь смысл

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


Почему во всех F2P-играх существует строительство зданий? Потому что строительство — это одно из немногих мест, в котором внедрение таймеров имеет смысл. Это основная причина, по которой в большинстве игр по умолчанию есть градостроительный компонент, даже если жанр кор-геймплея этого не требует.

Возьмем, к примеру, Walking Dead: Road to Survival, игру, которую недавно выпустила студия Scopely. Очевидно, что они добавили градостроительный компонент исключительно ради возможности использовать таймеры на строительство. Если бы в игре не было этого компонента, то им бы пришлось очень долго искать места, где было бы естественно внедрить долгосрочные таймеры.

Хорошим примером здесь является игра Agent Alice, создателям которой пришлось повозиться с внедрением таймеров.


Agent Alice — это недавно вышедшая игра в жанре «поиск предметов» от компании Wooga. Создатели Agent Alice пошли на огромный риск, убрав градостроительный компонент из игры Pearl's Peril, её предшественника. Из-за этого решения создателям пришлось повозиться, чтобы найти эффективную идею для таймеров. Там, где в Pearl's Peril применялись таймеры на строительство, чтобы регулировать продвижение игроков по игре, в Agent Alice пришлось использовать другие таймеры.

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


Этот таймер является неожиданным для игрока. Цель таймера — как можно лучше подходить теме игры. Каждый раз, когда пользователь сталкивается с таймером в игре, он должен думать: «Всё правильно, вполне понятно, почему это занимает столько времени».


Отправка улик в лабораторию имеет смысл, понятно, почему на это требуется время. Учтите это при создании структур для регулирования продвижения по игре. Внедрение таймеров в игру должно быть ожидаемо и логично для пользователей.

Разбираемся с режимами цикла выполнения

Режим цикла выполнения — это набор источников ввода, таких, как касания экрана или клики мышкой, за которыми может быть установлено наблюдение и набор «наблюдателей», получающих уведомления.

В iOS есть три режима цикла выполнения:

default: обрабатываются источники ввода, которые не являются NSConnectionObjects.
common: обрабатывается набор циклов ввода, для которых вы можете определить набор источников ввода, таймеров, «наблюдателей».
tracking: обрабатывается UI приложения.

Для нашего приложения наиболее подходящим выглядит режим common. Чтобы использовать его, замените содержимое метода createTimer() следующим:


Главное отличие от предыдущего кода состоит в том, что мы перед присваиванием таймера TaskListViewController‘а добавляем этот таймер в цикл выполнения в режиме common.

Скомпилируйте и запустите приложение.

Теперь временны́е метки ячеек обновляются даже в случае скроллинга таблицы.

Итоги

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

  • Подмена таймеров естественными циклами: один раз в день, один раз в неделю, один раз в месяц. Используйте естественные циклы, к которым мы все давно привыкли.
  • Применяйте анимацию вместо таймеров везде, где это кажется более естественным и имеет смысл.
  • Вместо того, чтобы просто использовать таймеры или систему энергии, постарайтесь найти способ управлять продвижением по игре, увеличивая начальную стоимость с помощью валют, геймплея и удачи.

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

3. Регулируйте стоимость

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


Лучшим примером является Fallout Shelter — игра, разработчики которой приложили много усилий, чтобы продвижение ощущалось по-другому, выделялось из «серой массы». Они решили добиться этого, изменив экономику игры. Вместо того, чтобы регулировать продвижение по игре с помощью таймеров на постройку комнат, они решили регулировать продвижение только с помощью начальной стоимости комнат.

В результате Fallout Shelter очень сильно отличается от большинства игр-симуляторов. Пользователи чувствуют постоянную благодарность и удовлетворение от возможности заработать монеты.


У Fallout Shelter все получилось, и игра отличается от большинства F2P-симуляторов. Но само увеличение стоимости покупки может принимать различные формы. Стоимость может быть реализована в форме сбора валюты (например, крышки от бутылок в Fallout Shelter), или быть комбинацией удачи и усилий, как это устроено в большинстве игр, использующих систему gatcha, включая Contest of Champions. Для того, чтобы регулировать продвижение через получение самых лучших персонажей, игроки должны прежде всего пройти через нудный сбор сотен кристаллов.


Если вы пытаетесь разработать новую механику продвижения по игре, учтите следующее: вместо того, чтобы гарантировать игроку награду в конце длинного цикла, вовлеките его в более короткие циклы, но внесите элемент случайности в продвижение к цели. Как учит нас психология, такая механика будет завлекать гораздо дольше, чем определенная стоимость.

Существует еще огромное количество возможностей регулировать продвижение по игре. Но я бы много раз подумал, прежде чем просто добавить таймер или высокую стоимость.

Добавляем допуск к таймеру

Увеличение количества таймеров приводит к худшей отзывчивости UI и большему потреблению батареи. Каждый таймер пытается исполниться точно в отведённое ему время, так как по умолчанию его допуск (tolerance) равен нулю.

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

Для таймеров, которые выполняются лишь однажды, значение допуска игнорируется.

В методе createTimer(), сразу за присвоением timer, добавьте эту строчку:


Запустите приложение. В этом конкретном случае эффект будет неочевиден (у нас только один таймер), однако в реальной ситуации нескольких таймеров ваши пользователи получат более отзывчивый интерфейс и приложение будет более энергоэффективным.

2. Добавьте анимацию

Ещё один удачный способ добавить в игру определенный ритм продвижения — использовать визуальную анимацию вместо таймеров.

Например, в игре Hay Day есть довольно умно сделанная механика, благодаря которой вы не можете заключить сделку на продажу товаров с персонажами, пока они не уйдут от вас и не придут снова. После заключения сделки персонаж медленно уходит, а вскоре возвращается для заключения новой сделки. В таком случае и заданный ритм имеет смысл, и нет необходимости тыкать таймером в лицо пользователя.


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

Есть ли в вашей игре какие-нибудь короткие таймеры, которые вы можете визуализировать анимацией, а не таймером? А может быть, есть долгосрочные таймеры, которые лучше представить в виде анимации?

Остановить таймер с помощью checknet js?

У меня проблема Я создаю онлайн-тест. Я проверяю, что интернет был отключен с помощью плагина jquery checknet. Если интернет отключается, тоже отключает все кнопки. Но я не могу остановить время, отведенное для теста. Или, когда интернет отключен, мне нужно добавить одну секунду к обратному отсчету к каждой секунде

Вот у меня интернет работает и все в порядке

Как можно остонавит или +1 за каждий секунд? Проблема в том, что один из них полный JS. другой - Jquery

Таймер, который нужно остановить:

checknet js (jquery plugin)

Athanor

ваш таймер работает через рекурсивный setTimeout . Каждую секунду вызывается функция updateExamTimer , которая в конце выполнения планирует отложенный вызов себя же через 1 секунду.

Особенность функции setTimeout заключается в том, что при вызове она возвращает timerId , который если успеть передать в clearTimeout , то таймер(на выполнение функции updateExamTimer ) отменится.

Следовательно, вам нужно изменить код так, чтоб при отключении интернета вызывался clearTimeout и в него передавался timerId , который вам нужно куда-то сохранять каждый раз при вызове setTimeout
После включения интернета, вам потребуется снова вызвать setTimeout('updateExamTimer()', 1000); , чтоб запустить таймер

НО так же у вас фигурирует загадочная переменная EXAM_TIME_LEFT , которая неизвестно откуда берется. Если это константа и она просто объявлена выше по коду, описанный мной вариант, скорее всего, будет работать, если она прилетает с сервера(и при обновлении страницы она меняется), то ничего работать не будет и отключать таймер придется на сервере в том числе.

Останавливаем таймер

Если вы посмотрите в консоль, вы увидите, что, хотя пользователь отметил все задачи выполненными, таймер продолжает работу. Это совершенно бессмысленно, так что имеет смысл остановить таймер, когда он не нужен.

Сначала создадим новый метод для остановки таймера:


Это обновит таймер и сбросит его в nil, чтобы мы могли правильно его создать вновь позже. invalidate() — это единственный способ удалить Timer из цикла выполнения. Цикл выполнения удалит сильную ссылку на таймер или непосредственно после вызова invalidate() или чуть позже.

Теперь заменим метод showCongratulationsIfNeeded() следующим образом:


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

Теперь таймер останавливается и рестартует, как надо.

Показываем анимацию

Как вы, наверно, догадались, нет ничего, чтобы «запустило» нашу новую анимацию. Чтобы сделать это, нам нужен еще один метод. Добавим этот код в экстеншн анимации TaskListViewController:


Этот метод мы будем вызывать всякий раз, когда пользователь отметит задачу выполненной, он проверяет, все ли задачи выполнены. Если да, то он вызовет showCongratulationAnimation().

В заключение, добавим вызов этого метода в конце tableView(_:didSelectRowAt:):


Запустите приложение, создайте пару задач, отметьте их как выполненные — и вы увидите нашу анимацию!

Три способа избавиться от механизмов таймеров и энергии во free-to-play-игре

Адам Телфер, геймдизайнер игровой студии Wooga, написал для своего блога материал о том, как можно избежать введения таймеров в свой проект и таким образом сделать приложение более приятным для аудитории.

Редакция рубрики «Рынок игр» публикует перевод заметки.

Free-to-play (F2P) для мобильных платформ меняется каждый день. Аудитория становится более зрелой. Её вкусы меняются. И я сейчас ощущаю затишье перед бурей. Застой на первых местах в App Store держится уже очень долго. Но вкусы аудитории мобильных игр изменятся, и дизайнерам предстоит решить, как под них подстроиться.


Так как вкусы аудитории меняются, приходится адаптировать дизайн. Нам придётся найти способы сделать так, чтобы старые системы ощущались по-новому.

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

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

Итак, я составил подборку из трёх тактик, которыми я пользуюсь, если приходится корректировать механики продвижения по игре. Эти три тактики помогут вам переосмыслить создание структуры продвижения так, чтобы она ощущалась по-новому, совершенно по-другому и казалась более естественной.

4 ответа

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

Я немного переставил твой код и добавил несколько заметок, посмотри:

Источник вашей проблемы в коде

Давайте еще раз посетим цель сохранения переменной 'session'. Я предполагаю, что это значение максимального значения, верхний предел минут, откуда начать отсчет, верно? С другой стороны, переменная 'minutes' должна хранить временное значение минут. Что вас смутило, так это то, что вы использовали «минуты» вместо его верхнего предела (какова на самом деле роль сеанса), например, в этом коде

Видите, вы обновляете html на значение 'minutes', а позже вы читаете это значение в 'session' этим злым кодом:

Так почему же? Зачем вам нужно обновить значение переменной «ваш» из HTML? Вы должны обновлять HTML только в соответствии со значением сеанса var, а не наоборот. Давайте продолжим и упростим жизнь . Давайте сохраним временное значение минут в «минутах», давайте также сохраним верхний предел в переменной сессии и, пожалуйста, давайте переименуем его в maxMinutes. Давайте обновим «maxMinutes» только тогда, когда пользователь нажимает «+» или «-» (НИКОГДА из html).

Обратите внимание, что единственное место, где maxLimits получает присвоенное значение, находится в увеличении () и уменьшении (). «Минуты» и HTML, в свою очередь, обновляются в соответствии с maxMinutes. Удачи!

А затем попытаться вычислить это как

Вам нужно либо поставить сеанс: секунды в другое место, либо сделать

Хорошо, я бы предложил другой подход. Используйте class для своего таймера, например так:

Нам поможет CADisplayLink

CADisplayLink вызывается один раз за кадр и пытается синхронизовать реальные кадры анимации, насколько это возможно. Теперь в вашем распоряжении будут все 16 мс и iOS не сбросит ни единого кадра.

Чтобы использовать CADisplayLink, вам нужно заменить animationTimer на новый тип.

Замените этот код


Вы заменили Timer на CADisplayLink. CADisplayLink — это представление таймера, который привязан к вертикальной развёртке дисплея. Это означает, что GPU устройства приостановит работу, пока экран не сможет дальше обрабатывать команды GPU. Таким образом мы получаем плавную анимацию.

Замените этот код

Вы заменили TimeInterval на CFTimeInterval, что необходимо для работы с CADisplayLink.

Замените текст метода showCongratulationAnimation() на этот:


Что мы тут делаем:

  1. Устанавливаем высоту анимации, координаты шарика и видимость — примерно так же, как делали раньше.
  2. Инициализируем startTime при помощи CACurrentMediaTime() (вместо of Date()).
  3. Создаём экземпляр класса CADisplayLink и добавляем его в цикл выполнения в режиме common.
  1. Добавляем objc к сигнатуре метода (у CADisplayLink параметр селектора требует такую сигнатуру).
  2. Заменяем инициализацию при помощи Date() на инициализацию даты CoreAnimation.
  3. Заменяем вызов animationTimer.invalidate() call на паузу CADisplayLink и invalidate. Это также удалит CADisplayLink из цикла выполнения.


Прекрасно! Мы успешно заменили анимацию, основанную на Timer, на более подходящий CADisplayLink — и получили анимацию более плавную, без рывков.

Начинаем

Загрузите исходный проект. Откройте его в Xcode, посмотрите его структуру, скомпилируйте и выполните. Вы увидите простейший планировщик задач:


Добавим в него новую задачу. Тапните на значке +, введите название задачи, тапните Ok.

В добавленных задачах есть метка времени. Новая задача, которую вы только что создали, отмечена нулём секунд. Как видите, это значение не увеличивается.

Каждую задачу можно отметить как выполненную. Тапните на задаче. Название задачи станет перечеркнутым и она будет помечена как выполненная.

Добавляем анимацию на выполнение всех задач

Теперь добавим поздравительную анимацию на выполнение пользователем всех задач — шарик будет подниматься снизу экрана до самого верха.

Добавьте эти переменные в начале TaskListViewController:


Назначение этих переменных:

  1. хранение таймера анимации.
  2. хранение времени начала и конца анимации.
  3. продолжительность анимации.
  4. высота анимации.


Здесь мы делаем следующее:

  • вычисляем высоту анимации, получая высоту экрана устройства
  • центрируем шарик за пределами экрана и устанавливаем его видимость
  • присваиваем время начала и конца анимации
  • стартуем таймер анимации и обновляем анимацию 60 раз в секунду
  1. проверяем, что endTime и startTime присвоены
  2. сохраняем текущее время в константе
  3. удостоверяемся в том, что конечное время еще не настало. Если уже настало, обновляем таймер и прячем наш шарик
  4. вычисляем новую y-координату шарика
  5. горизонтальное расположение шарика вычисляется относительно предыдущего положения


Теперь updateAnimation() вызывается всякий раз при событии таймера.

Ура, мы только что создали анимацию. Однако, при запуске приложения не происходит ничего нового…

Таймеры в бэкграунде

Интересно, а что происходит с таймерами, когда приложение уходит в бэкграунд? Чтобы разобраться с этим, добавим этот код в самом начале метода updateTimer():


Это позволит нам отследить события таймера в консоли.

Запустите приложение, добавьте задачу. Теперь нажмите на вашем устройстве кнопку Home, а затем вернитесь к нашему приложению.

В консоли вы увидите что-то вроде этого:


Как видите, когда приложение уходит в бэкграунд, iOS приостанавливает все работающие таймеры приложения. Когда же приложение становится активным, iOS возобновляет таймеры.

Создаём наш первый таймер

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

Откройте TaskListViewController.swift и добавьте в TaskListViewController эту переменную:


Затем там же добавьте экстеншн:


И вставьте этот код в экстеншн:


В этом методе мы:

  1. Проверяем, есть ли в таблице задач видимые строки.
  2. Вызываем updateTime для каждой видимой ячейки. Этот метод обновляем метку времени в ячейке (посмотрите TaskTableViewCell.swift).
  1. Проверяем, содержит ли timer экземпляр класса Timer.
  2. Если нет, создаём таймер, который каждую секунду вызывает updateTimer().

Запустите приложение и создайте пару новых задач. Вы увидите, что метка времени у каждой задачи меняется каждую секунду.

Читайте также: